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 !