Pour bientôt

J’ai vu passer un truc sur les attributs en PHP 8 qui m’a donné une idée pour la gestion des behaviors.

Pour l’instant, pour enregistrer une fonction de rappel pour un behavior donné, on faisait quelque chose comme :

/* Register favorite */
dcCore::app()->addBehavior('adminDashboardFavorites', [__NAMESPACE__ . '\Admin', 'adminDashboardFavorites']);

class Admin
{
    public static function adminDashboardFavorites($core, $favs)
    {
        $favs->register('debug', [
            'title'       => __('Debug'),
            'url'         => 'plugin.php?p=debug',
            'small-icon'  => urldecode(dcPage::getPF('debug/icon.svg')),
            'large-icon'  => urldecode(dcPage::getPF('debug/icon.svg')),
            'permissions' => 'usage,contentadmin',
        ]);
    }

Je me suis dit que ça serait pas mal de pouvoir s’abstenir de les ajouter (avec la fonction dcCore::app()->addBehavior(…)) et qu’un utilitaire se charge de les récupérer dans la classe et de les enregistrer automagiquement !

Alors j’ai fait quelques tests et j’ai une fonction qui fait exactement ça, juste en récupérant les attributs mentionnés plus haut. Je reprends l’exemple qui devient :

class Admin
{
    #[\Behavior]
    public static function adminDashboardFavorites($core, $favs)
    {
        $favs->register('debug', [
            'title'       => __('Debug'),
            'url'         => 'plugin.php?p=debug',
            'small-icon'  => urldecode(dcPage::getPF('debug/icon.svg')),
            'large-icon'  => urldecode(dcPage::getPF('debug/icon.svg')),
            'permissions' => 'usage,contentadmin',
        ]);
    }

Où j’ai juste viré l’enregistrement (lignes 1 à 3 du premier code) et ai ajouté un attribut #[\Behavior] (ligne 3 du second code) devant la déclaration d’une des fonctions de rappel.

Ça serait franchement plus simple !

J’aurais pu mettre #[\Behavior('adminDashboardFavorites')] pour indiquer le nom du behavior, mais comme la méthode porte le même nom, ce n’est pas utile.

L’utilitaire en question, version basique, est comme ceci :

/**
 * Scans behaviors callbacks in a class and register them (PHP 8+ only).
 *
 * Attributes:
 *
 *  #[\Behavior] or #[\Behavior(behavior_name)]
 *
 *  To be inserted just before static method declaration
 *
 *  Exemples:
 *
 *      #[\Behavior]                                The callback will be registered using method name as behavior name
 *      #[\Behavior('adminDashboardFavorites')]
 *
 * @param      string  $class     The class
 *
 * @return     int     Number of behaviors registered
 */
function scanBehaviors(string $class): int
{
    $counter = 0;
    if (class_exists($class)) {
        $reflection_class = new \ReflectionClass($class);
        // Loop on each method
        foreach ($reflection_class->getMethods(\ReflectionMethod::IS_STATIC) as $method) {
            $method_name = $method->getName();
            // Get method Behavior attributes if any
            $attributes = $method->getAttributes('Behavior');
            if (count($attributes)) {
                $attribute     = $attributes[0];
                $arguments     = $attribute->getArguments();
                $behavior_name = count($arguments) && $arguments[0] ? $arguments[0] : $method_name;
                $class_name    = $method->getNamespaceName() . '\\' . $reflection_class->getName();
                // Add behavior
                dcCore::app()->addBehavior($behavior_name, [$class_name, $method_name]);
                $counter++;
            }
        }
    }

    return $counter;
}

On pourrait même aller plus loin en définissant, éventuellement, des contextes (public, admin, …) où les fonctions de rappel doivent être enregistrées ; dans ce cas il suffirait d’indiquer si nécessaire les seuls contextes autorisés pour la fonction de rappel, en deuxième argument de l’attribut, comme par exemple #[\Behavior('', 'admin')].

Dans ce cas il faudrait compléter l’utilitaire de cette manière :

/**
 * Scans behaviors callbacks in a class and register them (PHP 8+ only).
 *
 * Attributes:
 *
 *  #[\Behavior] or #[\Behavior(behavior_name[, contexts])]
 *
 *  To be inserted just before static method declaration
 *
 *  Exemples:
 *
 *      #[\Behavior]
 *      #[\Behavior('adminDashboardFavorites')]
 *      #[\Behavior('adminDashboardFavorites', 'admin')]    Registered only if admin is given in <var>$contexts</var>
 *      #[\Behavior('', 'admin')]                           Same as above, using method name as behavior name
 *      #[\Behavior('', 'admin,public')]                    Same as above, in these two contexts
 *
 * @param      string  $class     The class
 * @param      string  $contexts  The authorized contexts, comma separated, if not given, all callbacks will be registered
 *
 * @return     int     Number of behaviors registered
 */
function scanBehaviorsForContexts(string $class, string $contexts = ''): int
{
    $counter = 0;
    if (class_exists($class)) {
        $reflection_class = new \ReflectionClass($class);
        // Loop on each method
        foreach ($reflection_class->getMethods(\ReflectionMethod::IS_STATIC) as $method) {
            $method_name = $method->getName();
            // Get method Behavior attributes if any
            $attributes = $method->getAttributes('Behavior');
            if (count($attributes)) {
                $attribute    = $attributes[0];
                $arguments    = $attribute->getArguments();
                $add_behavior = true;
                if ($contexts && count($arguments) > 1) {
                    // Check if 2nd attribute arguments is in given contexts
                    if (!in_array($arguments[1], explode(',', $contexts))) {
                        // Not one of the given contexts, don't register this behavior
                        $add_behavior = false;
                    }
                }
                if ($add_behavior) {
                    $behavior_name = count($arguments) && $arguments[0] ? $arguments[0] : $method_name;
                    $class_name    = $method->getNamespaceName() . '\\' . $reflection_class->getName();
                    // Add behavior
                    dcCore::app()->addBehavior($behavior_name, [$class_name, $method_name]);
                    $counter++;
                }
            }
        }
    }

    return $counter;
}

Ensuite il n’y aurait plus qu’à l’appeler via un scanBehaviorsForContexts('myClass', 'admin,public'), par exemple.

Bon, ça ne sera pas pour tout de suite puisque c’est spécifique à PHP 8, mais c’est à garder sous le coude !

Ajouter un commentaire

Les commentaires peuvent être formatés en utilisant la syntaxe Markdown Extra.

Ajouter un rétrolien

URL de rétrolien : https://open-time.net/trackback/15472

Haut de page