v13.42.0. Pour les bases des relations, consultez l’Introduction aux relations Eloquent. Pour ajouter des relations depuis l’extérieur aux modèles de l’application cliente, consultez l’Enregistrement de relations dynamiques pour les packages.
Séparer les noms de type des noms de relation
Une relation polymorphe combine la clé primaire du modèle lié et un nom de type pour référencer plusieurs sortes de modèles. Par exemple, si les commentaires peuvent appartenir à des articles ou à des vidéos, vous avez besoin decommentable_type en plus de commentable_id.
Sans enregistrement, un nom de classe pleinement qualifié comme
Acme\Publishing\Models\Article est normalement utilisé comme type. Une fois un alias enregistré, acme-publishing.article est utilisé, par exemple lors de la création de nouvelles relations. L’enregistrement seul ne réécrit pas les valeurs des lignes existantes.
Une morph map ne crée ni méthodes de relation ni tables. Préparez les modèles, colonnes et index comme d’habitude.
N’enregistrer que des alias propres au package
Dans l’exemple,Acme\Publishing\ correspond à src/ via le PSR-4 de Composer. Commencez par définir src/Models/Article.php.
src/Models/Comment.php définit la relation inverse.
acme_articles contient id, title et les horodatages, et acme_comments contient id, body, commentable_type, commentable_id et les horodatages. Les clés primaires de cet exemple sont des entiers. Vous pouvez utiliser les colonnes et l’index composite créés par $table->morphs('commentable'), mais avec des UUID ou des configurations similaires, alignez les types de colonnes sur le modèle lié.
Ensuite, enregistrez l’alias dans boot() d’un service provider ordinaire, non différé, src/PublishingServiceProvider.php.
$alias vaut acme-publishing.article et $class vaut Article::class. Tant que Comment ne devient pas lui-même le modèle lié d’une autre relation polymorphe, vous n’avez pas besoin d’enregistrer d’alias pour Comment dans cet exemple.
Comprendre les règles de fusion et de collision de noms
Relation::morphMap() agit sur une table de correspondance statique. La fusion par défaut combine le nouveau tableau et le tableau existant avec $map + static::$morphMap.
Si un package passe
false en second argument, il efface les enregistrements des autres packages et de l’application cliente. Pour un enregistrement normal, conservez la table existante et choisissez un alias qui identifie l’éditeur et le package plutôt qu’un nom générique comme article. Ne réutilisez pas le même alias pour une autre signification.
Si vous ne passez qu’une liste de noms de classes, Laravel instancie chaque modèle et utilise son nom de table comme clé. Les packages qui veulent aussi découpler le format de stockage des changements de nom de table doivent déclarer explicitement la correspondance entre alias et classe, comme dans l’exemple ci-dessus.
Le getMorphClass() par défaut recherche la classe concrète du modèle. Utiliser une sous-classe d’Article n’hérite pas automatiquement de l’alias de la classe parente. Les packages qui permettent de remplacer les modèles ont besoin d’une conception qui enregistre dans la table les classes réellement utilisées.
Faire du mode strict une politique de toute l’application
enforceMorphMap(), dans la documentation officielle, enregistre les alias et appelle requireMorphMap(true) en une seule étape. Ce n’est pas une contrainte limitée au package.
Dans l’implémentation par défaut de
v13.42.0, le mode strict agit dans les deux sens.
- Du modèle vers le nom de type :
getMorphClass()lève uneClassMorphViolationExceptionpour les modèles non enregistrés. La classe générique intégréePivotbénéficie d’une exception, mais les modèles ordinaires et les pivots personnalisés n’ont pas cette dérogation. - Du nom de type stocké vers la classe :
getActualClassNameForMorph()lève la même exception pour les valeurs qui ne sont pas des clés de la table. Elle est aussi utilisée par la récupération normale viamorphTo()et par l’eager loading. Même si la classe figure comme « valeur » dans la table, un ancien nom de classe pleinement qualifié qui n’est pas une « clé » ne peut pas être chargé.
morphMap() et on laisse l’application cliente décider de l’imposer à toute l’application. Si l’application cliente l’impose déjà, ne la désactivez pas depuis le package avec requireMorphMap(false).
Migrer les données existantes avant de juger la compatibilité
L’introduction de l’enregistrement d’alias n’implique pas les mêmes conditions pour une nouvelle installation et pour la mise à jour d’une application existante. La documentation officielle signale aussi qu’il faut, lors de l’introduction, convertir les noms de classe pleinement qualifiés existants en alias.Vérifier uniquement la récupération inverse ne suffit pas
Lorsque le mode strict est désactivé, il est possible de récupérer encore l’article d’un commentaire avecmorphTo() tant que la classe de l’ancien commentable_type existe. Mais le morphMany() côté article filtre selon la valeur de getMorphClass(). Après l’enregistrement de l’alias, il recherche acme-publishing.article, si bien que les lignes portant l’ancien nom de classe disparaissent de la liste des commentaires de l’article.
MorphOneOrMany conserve le nom de type à sa création et l’utilise à la fois dans les conditions de requête et lors de la création de nouveaux enregistrements. Même si anciens et nouveaux noms de type coexistent, il ne recherche pas automatiquement toutes les valeurs avec un OR.
Inclure la procédure de mise à jour dans la version
Voici un fragment qui convertit l’ancien nom de classe en alias. Implémentez-le comme une migration de données pour l’application cible et ne l’exécutez pas automatiquement depuis un service provider.- Inventoriez les colonnes
*_typeconcernées et les valeurs stockées, et préparez une sauvegarde, un comptage des lignes et une procédure de restauration avant la mise à jour. Si d’autres tables référencent le même modèle, incluez-les aussi. - Pour la première mise en place, définissez une procédure de bascule, y compris l’arrêt des écritures ou une maintenance, afin que des processus écrivant l’ancien nom de classe et des processus écrivant l’alias ne coexistent pas. Pour les grandes tables, envisagez aussi les verrous de mise à jour et le traitement par lots.
- Basculez la table et les données en convertissant les noms de type sans modifier les valeurs des clés primaires. Figez l’ancien nom de classe dans le code de migration comme la chaîne réellement stockée par le passé, et non
Article::class, dont le sens change lors de futurs renommages. - Vérifiez le nombre d’anciennes valeurs restantes, les nouveaux enregistrements, les récupérations dans les deux sens et l’eager loading, puis mettez à jour les workers de longue durée avant de reprendre les écritures.
- Pour revenir en arrière, vérifiez que le code restauré peut résoudre l’alias stocké. Revenir à l’ancienne version Composer ne rétablit pas les noms de type en base.
Tester les valeurs stockées et les récupérations dans les deux sens
Avec Orchestra Testbench, vérifiez non seulement la table, mais aussi les valeurs stockées en base et les récupérations dans les deux sens. L’exemple suivant suppose que le package est chargé automatiquement par Composer et que le pilote PDO SQLite est disponible. Il enregistre le provider explicitement dans le test, séparément de la vérification de la découverte automatique.- Enregistrer le provider ne désactive pas le mode strict lorsque l’application cliente l’a déjà activé.
- Le format de stockage publié ne change pas en cas de conflit sur un même alias, de plusieurs alias pour une même classe ou de modèle concret remplacé.
- Avec des fixtures stockées sous le nom de classe antérieur à la mise à jour, aucune ancienne valeur ne subsiste après la migration, et les récupérations dans les deux sens ainsi que les comptages concordent.
- La procédure réelle de mise à jour, workers de file d’attente compris, peut être restaurée avec la combinaison du code et des données restaurés.
Sources primaires consultées
- Documentation officielle de Laravel : Custom Polymorphic Types
- Documentation officielle de Laravel : Package Development
- Relation : fusion de la table et mode strict
- HasRelationships : obtention des noms de type et résolution des classes
- MorphOneOrMany : conservation du nom de type, conditions de requête et enregistrement
- MorphTo : résolution des types lors de l’eager loading
- Tests officiels de Laravel : enregistrement et résolution des morph maps
- Tests officiels de Laravel : lecture et écriture en mode strict