Chez Xavier

Home / L'extensibilité de Symfony illustrée : les behaviors Propel

Excuse me, I think I'm naked: on a wall of Barcelona.

Excuse me, I think I'm naked: on a wall of Barcelona.

Cet article "L'extensibilité de Symfony illustrée : les behaviors Propel" a été initialement écrit pour le blog de Clever Age.

Tristan et moi avons publié il y a quelques temps des comportements Propel sous forme de plugins Symfony, tandis que l'ami NiKo en termine un qui, croyez-moi, n'est pas raté... Cependant, bien qu'ils soient très utiles, la documentation de Symfony demeure assez peu explicite au sujet du rôle des behaviors Propel, et de la manière de les développer.

A quoi sert un behavior Propel ?

Symfony propose un mécanisme, appelé "Behavior", qui permet l'extension de l'interface des objets générés par Propel, l'ORM par défaut du framework. En clair, ce mécanisme permet d'ajouter ou de redéfinir certaines méthodes des objets d'accès aux entrées de la base de données. En plus des accesseurs et modificateurs traditionnels, disponibles dans les BasePeer, un behavior permet donc de disposer de nouvelles méthodes, afin de répondre à des besoins fonctionnels précis.

Par exemple, le plugin sfPropelActAsNestedSetBehaviorPlugin (ouf !) ajoute un fonctionnement de Nested Set [1] aux objets Propel. Le plugin sfPropelActAsTaggableBehaviorPlugin (re-ouf !), lui, leur ajoute toutes les fonctionnalités de tagging dont vous aurez besoin si l'application que vous développez est bien Web 2.0-compliant (mais je ne m'engage pas pour le Web 3.0).

Ce qui est très intéressant, évidemment, c'est que ces comportements sont entièrement génériques, et ne dépendent a priori pas de la structure à laquelle on les applique. Ils constituent donc un moyen simple et réutilisable de rendre les éléments de votre modèle de données versionnables, commentables, notables, tagables, etc.

Le fonctionnement interne des behaviors Propel

Les behaviors Propel profitent directement des fonctionnalités de réflexion [2] et de surcharge, introduites par PHP5.

Le mécanisme global de fonctionnement d'un behavior est le suivant :

fonctionnement des behaviors Propel dans Symfony

  1. déclaration du behavior à Symfony, et attachement aux objets devant en adapter le comportement :
    1. déclaration des hooks [3] et des méthodes
    2. demande d'utilisation du behavior par une classe de modèle Propel
    3. enregistrement des mixins [4] par le biais de sfMixer
  2. utilisation d'une méthode introduite par un behavior :
    1. appel de la méthode __call du BasePeer;
    2. si vous avez activé les behaviors dans le fichier propel.ini, recherche d'un mixin convenable
    3. délégation du travail à la classe du behavior, à laquelle l'objet est passé en premier paramètre

Rien de bien magique, finalement, mais le résultat est cependant très pratique, puisqu'en quelques opérations il permet de disposer de nouvelles fonctionnalités au sein d'un développement.

Comment développer un behavior Propel sous forme de plugin Symfony ?

Le développement d'un Behavior Propel au sein de Symfony suit exactement le modèle décrit dans le paragraphe précédent.

Déclaration du behavior auprès de Symfony

La déclaration du behavior, c'est-à-dire des hooks et des méthodes proposés par le behavior, se fait par le biais de la classe sfPropelBehavior, qui propose deux méthodes statiques pour cela : registerHooks, et registerMethods. Pour comprendre leur emploi, on peut prendre pour exemple le plugin sfPropelActAsTaggableBehaviorPlugin :

  • les hooks sont enregistrés par le biais d'un tableau indexé, dont les indexes indiquent la méthode qui doit être observée ainsi que la position d'exécution du hook (avant, après, etc.). Ainsi, le hook suivant indique que la méthode postSave doit être appelée après l'exécution de la méthode save :
    sfPropelBehavior::registerHooks('sfPropelActAsTaggableBehavior', array (
    	':save:post' => array ('sfPropelActAsTaggableBehavior', 'postSave'),
    ));
  • les méthodes du comportement, que l'on souhaite donc ajouter aux objets Propel, sont enregistrées par le biais de registerMethods, sous la forme d'un simple tableau. Par exemple, ceci ajoutera les méthodes addTag et getTags aux classes qui adopteront le comportement sfPropelActAsTaggableBehavior :
    sfPropelBehavior::registerMethods('sfPropelActAsTaggableBehavior', array (
    	array ('sfPropelActAsTaggableBehavior', 'addTag'),
    	array ('sfPropelActAsTaggableBehavior', 'getTags')
    ));

Création du behavior

Le behavior proprement dit est constitué d'une classe qui implémente les méthodes enregistrées à l'étape précédente. Si on poursuit l'exemple précédent, la classe sfPropelActAsTaggableBehavior devra donc proposer une implémentation des méthodes addTag et getTags. Chacune de ces méthodes prend en premier paramètre un objet, qui correspond à une instance de la classe utilisant le behavior. Les autres paramètres de chaque méthode sont laissés à la discrétion du concepteur du plugin, et correspondent aux paramètres d'utilisation de chacune des méthodes ajoutées par le behavior. Ainsi, si on observe toujours le même plugin, le prototype de addTag est :

public function addTag(BaseObject $object, $tagname)

Et à l'utilisation, si par exemple "Post" est une classe ayant adopté le comportement :

$post = new Post();
$post->addTag('symfony, plugin, php5');

Ce qui manque à la gestion des behaviors Propel dans Symfony

  • un mécanisme de validation automatique du respect de certaines contraintes pour qu'une classe puisse employer un behavior donné. Tristan a proposé un patch à ce sujet
  • une mise en cache, ou une intégration des méthodes des behaviors dans les modèles générés par les propel-builders. Cela aurait l'avantage de la performance, en évitant l'attachement dynamique de nouvelles méthodes aux modèles.

Conclusions

  • Quoi qu'en disent certains, il existe d'autres critères que la seule performance pour comparer équitablement plusieurs frameworks PHP...
  • On remarque qu'un tel système de behaviors ne serait pas envisageable en PHP4 : alors si ce n'est pas déjà fait, passez à PHP5, maintenant !

Notes

  • [1] les Nested Sets constituent une méthode abstraite et efficace de représentation de données hiérarchisées au sein d'une base de données relationnelle.
  • [2] voir le chapitre du manuel de PHP consacré à la réflexion
  • [3] un hook et une partie de programme automatiquement exécutée juste avant ou juste après un évènement donné (typiquement, avant ou après l'appel d'une fonction). Le concept des hooks n'est pas propre à Symfony, on le retrouve dans divers domaines de l'informatique. Subversion, par exemple, propose un mécanisme de hooks pre/post-commit.
  • [4] Un mixin est une classe dont l'objectif est d'ajouter de nouvelles fonctionnalités à une ou d'autres classes, sans forcément employer le mécanisme d'héritage. Pour plus d'informations, voir la page de wikipédia au sujet des mixins.