Passer au contenu principal
Pour continuer à lire les données stockées des relations polymorphes après avoir déplacé ou renommé un modèle de votre package, séparez le nom de classe du modèle du nom de type stocké en base de données. Un alias de morph map est moins un raccourci qu’un contrat sur le format de stockage que vous maintenez d’une version à l’autre. Cette page s’appuie sur l’implémentation officielle de Laravel Framework 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 de commentable_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.
Le chargement du provider se configure avec la découverte automatique des packages. Pendant l’enregistrement, vous n’avez pas besoin de sauvegarder des modèles ni de mettre à jour les noms de type en base. La migration des données existantes est une tâche distincte.
$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.
Si l’application cliente ajoute un autre alias pour la même classe, l’ordre d’enregistrement peut changer les valeurs stockées par la suite et les conditions des requêtes de relation. Enregistrer les deux noms ne garantit pas que la compatibilité soit préservée. Même si vous autorisez le remplacement de la classe, vérifiez dans le cadre du contrat public que les données stockées restent lisibles avec le même alias.
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 une ClassMorphViolationException pour les modèles non enregistrés. La classe générique intégrée Pivot bé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 via morphTo() 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é.
Lorsque le mode strict est désactivé, les noms de type absents de la table sont traités comme des noms de classe. Cela ne garantit toutefois ni que la classe existe ni qu’il s’agit du modèle lié attendu. Côté package, on enregistre généralement uniquement ses propres modèles avec 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 avec morphTo() 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.
Cette étape seule ne suffit pas à terminer le déploiement.
  1. Inventoriez les colonnes *_type concerné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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
Si vous conservez un alias déjà figé et ne changez que le namespace du modèle, modifiez la valeur de la table vers la nouvelle classe : les noms de type restent utilisables tels quels. En revanche, modifier l’alias lui-même est un changement qui implique une migration de données. Alignez aussi la procédure de mise à jour sur la Gestion de la compatibilité de versions de package et Publier et mettre à jour les migrations d’un package.

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.
La table et l’indicateur de contrainte sont des états statiques distincts : ne supposez pas que reconstruire l’application les réinitialise. Cet exemple sauvegarde et restaure les deux. En production non plus, ne les réécrivez pas par requête ou par tenant, et limitez-vous à un enregistrement stable au démarrage. Les relations déjà créées conservent leur nom de type, si bien qu’un changement d’enregistrement en cours de route ne se répercute pas uniformément sur les instances existantes. Ajoutez aussi les cas de régression suivants à la CI du package.
  • 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

Dernière modification le 11 novembre 2026