J'ai un Animal
modèle, basé sur le animal
tableau.
Cette table contient un type
champ, qui peut contenir des valeurs telles que chat ou chien .
J'aimerais pouvoir créer des objets tels que:
class Animal extends Model { }
class Dog extends Animal { }
class Cat extends Animal { }
Pourtant, pouvoir aller chercher un animal comme celui-ci:
$animal = Animal::find($id);
Mais où $animal
serait une instance de Dog
ou en Cat
fonction du type
champ, que je peux vérifier en utilisant instance of
ou qui fonctionnera avec des méthodes de type hinted. La raison en est que 90% du code est partagé, mais l'un peut aboyer et l'autre miauler.
Je sais que je peux le faire Dog::find($id)
, mais ce n'est pas ce que je veux: je ne peux déterminer le type de l'objet qu'une fois récupéré. Je pourrais également récupérer l'animal, puis exécuter find()
sur le bon objet, mais cela fait deux appels à la base de données, ce que je ne veux évidemment pas.
J'ai essayé de trouver un moyen d'instancier "manuellement" un modèle Eloquent comme Dog from Animal, mais je n'ai trouvé aucune méthode correspondante. Une idée ou une méthode que j'ai ratée s'il vous plaît?
*_type
nom pour déterminer le modèle de sous-type. Dans mon cas, je n'ai vraiment qu'une seule table, alors même si c'est une fonctionnalité intéressante, pas dans mon cas.Réponses:
Vous pouvez utiliser les relations polymorphes dans Laravel comme expliqué dans les documents officiels Laravel . Voici comment procéder.
Définissez les relations dans le modèle comme indiqué
Ici, vous aurez besoin de deux colonnes dans le
animals
tableau, la première estanimable_type
et une autre consisteanimable_id
à déterminer le type de modèle qui lui est attaché lors de l'exécution.Vous pouvez récupérer le modèle Dog ou Cat comme indiqué,
Après cela, vous pouvez vérifier la
$anim
classe de l' objet à l'aide deinstanceof
.Cette approche vous aidera pour une expansion future si vous ajoutez un autre type d'animal (c.-à-d. Renard ou lion) dans l'application. Cela fonctionnera sans changer votre base de code. C'est la bonne façon de répondre à vos besoins. Cependant, il n'y a pas d'approche alternative pour réaliser le polymorphisme et le chargement avide ensemble sans utiliser une relation polymorphe. Si vous n'utilisez pas de relation polymorphe , vous vous retrouverez avec plus d'un appel de base de données. Cependant, si vous avez une seule colonne qui différencie le type modal, vous avez peut-être un schéma structuré incorrect. Je vous suggère de l'améliorer si vous souhaitez également le simplifier pour un développement futur.
Réécrire l'interne du modèle
newInstance()
et cenewFromBuilder()
n'est pas un bon moyen / recommandé et vous devez le retravailler une fois que vous aurez la mise à jour du framework.la source
Je pense que vous pouvez remplacer la
newInstance
méthode sur leAnimal
modèle et vérifier le type à partir des attributs, puis lancer le modèle correspondant.Vous devrez également remplacer la
newFromBuilder
méthode.la source
type
dans la base de données?Si vous voulez vraiment le faire, vous pouvez utiliser l'approche suivante dans votre modèle animal.
la source
Comme l'OP l'a déclaré dans ses commentaires: La conception de la base de données est déjà définie et, par conséquent, les relations polymorphes de Laravel ne semblent pas être une option ici.
J'aime la réponse de Chris Neal parce que j'ai dû faire quelque chose de similaire récemment (écrire mon propre pilote de base de données pour prendre en charge Eloquent pour les fichiers dbase / DBF) et j'ai acquis beaucoup d'expérience avec les composants internes de Laroquel's Eloquent ORM.
J'y ai ajouté ma saveur personnelle pour rendre le code plus dynamique tout en conservant un mappage explicite par modèle.
Fonctionnalités prises en charge que j'ai rapidement testées:
Animal::find(1)
fonctionne comme demandé dans votre questionAnimal::all()
fonctionne aussiAnimal::where(['type' => 'dog'])->get()
renverraAnimalDog
-objects en tant que collectionAnimal
Retour à -model dans le cas où aucun mappage n'est configuré (ou si un nouveau mappage apparaît dans la base de données)Désavantages:
newInstance()
etnewFromBuilder()
complète du modèle (copier-coller). Cela signifie que s'il y aura une mise à jour du cadre vers ces fonctions membres, vous devrez adopter le code à la main.J'espère que cela aide et je suis prêt à toute suggestion, question et cas d'utilisation supplémentaires dans votre scénario. Voici les cas d'utilisation et des exemples:
Et ceci est un exemple de la façon dont il peut être utilisé et ci-dessous les résultats respectifs pour cela:
ce qui donne les résultats suivants:
Et au cas où vous voudriez utiliser le
MorphTrait
voici bien sûr le code complet:la source
Je pense que je sais ce que vous cherchez. Considérez cette élégante solution qui utilise les étendues de requête Laravel, voir https://laravel.com/docs/6.x/eloquent#query-scopes pour plus d'informations:
Créez une classe parent contenant une logique partagée:
Créez un enfant (ou plusieurs) avec une portée de requête globale et un
saving
gestionnaire d'événements:(il en va de même pour une autre classe
Cat
, il suffit de remplacer la constante)La portée de requête globale agit comme une modification de requête par défaut, de sorte que la
Dog
classe recherchera toujours les enregistrements avectype='dog'
.Disons que nous avons 3 enregistrements:
L'appel
Dog::find(1)
entraînerait maintenantnull
, car la portée de requête par défaut ne trouverait pas leid:1
qui est unCat
. L'appelAnimal::find(1)
etCat::find(1)
fonctionnera tous les deux, bien que seul le dernier vous donne un véritable objet Cat.La bonne chose de cette configuration est que vous pouvez utiliser les classes ci-dessus pour créer des relations comme:
Et cette relation ne vous donnera automatiquement que tous les animaux avec le
type='dog'
(sous forme deDog
classes). La portée de la requête est automatiquement appliquée.De plus, l'appel
Dog::create($properties)
définira automatiquement letype
en'dog'
raison dusaving
hook d'événement (voir https://laravel.com/docs/6.x/eloquent#events ).Notez que l'appel
Animal::create($properties)
n'a pas de valeur par défauttype
, vous devez donc le définir manuellement (ce qui est normal).la source
Bien que vous utilisiez Laravel, dans ce cas, je pense que vous ne devriez pas vous en tenir aux raccourcis Laravel.
Ce problème que vous essayez de résoudre est un problème classique que de nombreux autres langages / frameworks résolvent à l'aide du modèle de méthode Factory ( https://en.wikipedia.org/wiki/Factory_method_pattern ).
Si vous voulez que votre code soit plus facile à comprendre et sans astuces cachées, vous devez utiliser un modèle bien connu au lieu d'astuces cachées / magiques sous le capot.
la source
Le moyen le plus simple consiste à faire de la méthode en classe Animal
Résolution du modèle
Cela renverra une instance de classe Animal, Dog ou Cat selon le type de modèle
la source