Annotation de classe @link
-
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
Salesman
on 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_salesmen
contenant deux champsid_order
etid_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
$salesman
et$order
, et on modifierait la déclaration côtéOrder
etSalesman
pour 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 unSalesman
mais 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]->salesman
pour 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->salesmen
reste 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
Order
etSalesman
, 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_salesmen
tel 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_salesmen
pour 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
Order
etSalesman
dans 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
$order
et$salesman
: dans le cas de la classe de lienSalesman_Order
qui hérite de la classe liéeOrder
, il convient de ne JAMAIS dans votre code métier utiliser la propriété$order
pour accéder à la commande ou la modifier, carSalesman_Order
EST 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é$salesman
deOrder_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::$salesmen
est utilisée, etSalesman::$orders
n’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
Order
sera :class Order { /** * @link Collection * @var Order_Salesman[] */ public $salesmen; }
La classe
Salesman
reste 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