Annotation de classe @link
-
Redirected from Classe de lien
Définition et principe
Une classe de lien permet de substituer une classe à un lien implicite, et ainsi de rajouter des propriétés à ce lien.
Un exemple par la pratique :
Rappel : exemple de lien implicite
Avant toute chose, soyez familier avec les notions présentées dans la documentation de l’Annotation de propriété link.
Vocabulaire métier utilisé pour notre exemple :Order: une commandeSalesman: un commercial
Une commande concerne un ou plusieurs commerciaux : on réalise une agrégation sans appartenance dans la classe
Order, de manière à pouvoir accéder aux commerciaux liés depuis une commande :class Order { /** * @link Map * @var Salesman[] */ public $salesmen; }
De même dans la classe
Salesmanon peut (éventuellement) accéder aux commandes qu’il a réalisée par un lien similaire :class Salesman { /** * @link Map * @var Orders[] */ public $orders; }
Ces déclarations suffisent pour que le framework implémente une table
orders_salesmencontenant deux champsid_orderetid_salesman, et utilise automatiquement cette table de lien lors des lectures et écritures de ces deux propriétés depuis les deux classes. Inutile d’implémenter une classe de lien superflue à ce stade.Ajout de propriétés et transformation en classe de lien
Au fil de l’évolution de notre logiciel, nous recevons un nouveau besoin client : il souhaite en plus de pouvoir attribuer plusieurs commerciaux à chaque commande signée associer à cette attribution un pourcentage d’implication de chacun dans cette vente.
Il y a plusieurs façons de gérer ça en programmation objets :
- On pourrait créer une classe supplémentaire représentant un lien bi-directionnel entre commande et commercial : cette classe comporterait deux propriétés
$salesmanet$order, et on modifierait la déclaration côtéOrderetSalesmanpour mettre cette nouvelle classe.
Problème : le lien étant maintenant indirect, partout dans notre logiciel où on accédai auparavant à$order->salesmen[0]pour obtenir un commercial, on doit maintenant accéder à$order->salesmen[0]->salesman, car chaque objet de la liste n’est plus unSalesmanmais unOrder_Salesman. Cette évolution locale a donc des impacts importants sur l’ensemble du logiciel, encore plus s’il y a des développements tiers qui devraient également être mis à jour.
- On pourrait considérer comme nécessaire de créer systématiquement des classes de lien dès l’origine, pour éviter le problème ci-dessus. Mais on se retrouverait vite avec de très nombreuses classes qui ont toujours le même schéma, donc redondantes, donc inutiles, notamment si on cherche à respecter les principes de programmation SOLID. On multiplierait considérablement le nombre de classes inutiles. De plus adopter de manière généralisée une écriture comme
$order->salesmen[0]->salesmanpour accéder au premier commercial de la commande est toujours plus lourd que$order->salesman[0], et comporterait une répétition dans le texte, pratique que la philosophie du framework cherche à bannir.
Il nous faut donc ajouter une propriété associée à ce lien, comme dans l’exemple ici un pourcentage d’attribution du dossier à chaque commercial, tout en s’assurant que
$order->salesmenreste dans le fond de typeSalesman[]. On ne peut alors plus fonctionner en lien implicite, puisse que cette propriété doit être ajoutée dans une classe. La classe de lien est un procédé qui permet de résoudre cette problématique.Pour se faire, il faut créer une classe de lien explicite pour chaque propriété source, de façon à avoir accès depuis cette propriété à un objet toujours du même type, mais avec les propriétés associées au lien en plus.
Pour éviter le copier-coller dans le cas où se lien se fait depuis les deux côtés
OrderetSalesman, il faut placer les propriétés de liens dans un trait pour pouvoir l’utiliser dans les deux classes. Par convention on nommera le trait dans l’ordre alphabétique des deux classes reliées. On trouve forcément parmi ces propriétés un lien vers les deux classes correspondant à la relation, plus les propriétés du lien. Les deux propriétés formant le lien peuvent être marquées de l’annotation @composite, par souci d’optimisation et pour éviter tout risque de confusion si plusieurs propriétés sont du même type, bien que it.rocks “devinera” tout seul quelle est la bonne propriété composite si une seule est de la classe recherchée./** * Defines the link between an order and a salesman * * @store_name orders_salesmen */ trait Order_Salesman_Trait { use Component; /** * @composite * @link Object * @var Order */ public $order; /** * @integer */ public $percentage; /** * @composite * @link Object * @var Salesman */ public $salesman; }
Le nom de stockage @store_name est défini ici car il est identique pour les deux classes qui utiliseront ce trait. Dans cet exemple on a respecté le nommage automatique par le framework de la table
orders_salesmentel qu’il avait été réalisé lors de la création automatique de cette table dans la version initiale sans classe de lien. On peut imaginer rendre ce nommage plus métier, par exempleorder_salesmenpour respecter la grammaire anglo-saxonne, mais cela nécessitera une maintenance de vos données dans le cas présent (renommer la table).On va utiliser ce trait dans les deux classes liées. Pour chacune on précisera explicitement :
- la classe parente héritée, de façon à ce que notre classe de lien dispose bien de ses propriétés,
- le trait comportant les propriétés et éventuels traitements particuliers à la relation.
/** * Salesman seen from an order * * @link Salesman */ class Order_Salesman extends Salesman { use Order_Salesman_Trait; } /** * Order seen from a salesman * * @link Order */ class Salesman_Order extends Order { use Order_Salesman; }
Il faut enfin modifier les propriétés dans les classes
OrderetSalesmandans leur classe respective pour qu’elles pointent maintenant sur les classes de lien à la place des classes liées.Dans la classe
Order:
class Order { /** * @link Collection * @var Order_Salesman[] */ public $salesmen; }
Dans la classe
Salesman:
class Salesman { /** * @link Collection * @var Salesman_Order[] */ public $orders; }
Avec cette méthode, la structure de données stockée et l’utilisation des propriétés reste inchangée, aucune autre modification n’est donc nécessaire dans le logiciel. On peut ainsi relativement aisément passer d’un lien implicite à un lien explicite.
ATTENTION : on a déclaré dans le trait des propriétés
$orderet$salesman: dans le cas de la classe de lienSalesman_Orderqui hérite de la classe liéeOrder, il convient de ne JAMAIS dans votre code métier utiliser la propriété$orderpour accéder à la commande ou la modifier, carSalesman_OrderEST une commande. Cet objet fait doublon et n’est présent qu’à usage de référence technique interne au framework, pour assurer le bon fonctionnement de ce procédé. Il en est de même pour la propriété$salesmandeOrder_Salesman.Cas particulier : besoin d’une seule classe de lien
Dans le cas particulier où une seule propriété établi le lien, dans une seule des deux classes concernées, on peut simplifier en remplaçant le trait par une classe unique, de même nom, représentant la seule classe de liaison. Par exemple si seule la propriété
Order::$salesmenest utilisée, etSalesman::$ordersn’existe pas car n’a aucune utilité, le code de la classe de lien sera :/** * @link Salesman * @store_name orders_salesmen */ class Order_Salesman extends Salesman { use Component; /** * @composite * @link Object * @var Order */ public $order; /** * @integer */ public $percentage; /** * @link Object * @var Salesman */ public $salesman; }
Notes :
- La seule propriété @composite est alors
$order, puisse que notre classe ne peut pas représenter une composante d’une collection de commandes dans un commercial (ben oui, elle représente une composante d’une collection de commerciaux dans une commande).
La propriété modifiée dans la classe
Ordersera :class Order { /** * @link Collection * @var Order_Salesman[] */ public $salesmen; }
La classe
Salesmanreste inchangée.Cas particulier : homonymie de propriétés dans la classe de lien
Dans le cas particulier où la classe liée ou la classe de lien embarque une propriété qui porte le même nom que la propriété de lien, il faut alors nommer différemment la propriété dans la classe de lien. Dans ce cas le champ en base de données sera différent du nom employé en cas de classe implicite, et il faudra récupérer les données en cas de migration. Par convention, on suffixera au nom de la propriété
_link, pour la rendre facilement reconnaissable./** * @link Salesman * @store_name orders_salesmen */ class Order_Salesman extends Salesman { // (...) /** * @link Object * @var Salesman */ private $salesman_link; }
Pas besoin d’@alias ici pour le nommage utilisateur : comme vu plus haut on n’est pas censé utiliser directement cette propriété, ni dans notre code ni dans les formulaires. Dans les formulaires auto-générés d’ailleurs le framework masquera automatiquement cette propriété de lien.
Autres documentations utiles
- Annotation de propriété link
- Conventions de nommage des classes de lien
- Internals – Annotation de classe @link : pour tracer le fonctionnement de ce procédé dans le framework