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 à 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.
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 :
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.
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 :
sfPropelBehavior::registerHooks('sfPropelActAsTaggableBehavior', array (
':save:post' => array ('sfPropelActAsTaggableBehavior', 'postSave'),
));sfPropelBehavior::registerMethods('sfPropelActAsTaggableBehavior', array (
array ('sfPropelActAsTaggableBehavior', 'addTag'),
array ('sfPropelActAsTaggableBehavior', 'getTags')
));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');[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.
Sur son blog, François Zaninotto propose un excellent tutorial sur le même sujet : http://redotheweb.com/2007/09/02/understanding-behaviors/
On y découvre comment sont implémentés les behaviors dans symfony et comment transformer son behavior en plugin.