Mam Animal
model oparty na animal
stole.
Ta tabela zawiera type
pole, które może zawierać wartości, takie jak kot lub pies .
Chciałbym móc tworzyć takie obiekty jak:
class Animal extends Model { }
class Dog extends Animal { }
class Cat extends Animal { }
Jednak będąc w stanie przynieść takie zwierzę:
$animal = Animal::find($id);
Ale gdzie $animal
byłoby wystąpienie Dog
lub Cat
zależne od type
pola, które mogę sprawdzić za pomocą instance of
lub które będą działać z metodami podpowiedzi typu. Powodem jest to, że 90% kodu jest współdzielone, ale jeden może szczekać, a drugi miauczy.
Wiem, że mogę to zrobić Dog::find($id)
, ale nie tego chcę: mogę określić typ obiektu tylko po jego pobraniu. Mógłbym również pobrać Zwierzę, a następnie uruchomić find()
odpowiedni obiekt, ale wykonuje to dwa wywołania bazy danych, których oczywiście nie chcę.
Próbowałem znaleźć sposób na „ręczne” utworzenie elokwentnego modelu, takiego jak Dog from Animal, ale nie mogłem znaleźć żadnej metody odpowiadającej. Pomyliłem jakiś pomysł lub metodę?
*_type
nazwą, aby określić model podtypu. W moim przypadku tak naprawdę mam tylko jeden stół, więc chociaż jest to miła funkcja, nie w moim przypadku.Odpowiedzi:
Możesz używać związków polimorficznych w Laravel, jak wyjaśniono w oficjalnych dokumentach Laravel . Oto jak możesz to zrobić.
Zdefiniuj relacje w modelu, jak podano
Tutaj potrzebujesz dwóch kolumn w
animals
tabeli, pierwsza toanimable_type
kolejna, a druga toanimable_id
określenie typu modelu dołączonego do niej w czasie wykonywania.Możesz pobrać model psa lub kota, jak podano,
Następnie możesz sprawdzić
$anim
klasę obiektu, używającinstanceof
.Takie podejście pomoże ci w przyszłej ekspansji, jeśli dodasz inny typ zwierzęcia (tj. Lisa lub lwa) w aplikacji. Będzie działać bez zmiany bazy kodu. To jest właściwy sposób na spełnienie wymagań. Jednak nie ma alternatywnego podejścia do osiągnięcia polimorfizmu i chętnego ładowania razem bez stosowania zależności polimorficznej. Jeśli nie użyjesz relacji polimorficznej , otrzymasz więcej niż jedno wywołanie bazy danych. Jeśli jednak masz jedną kolumnę, która odróżnia typ modalny, być może masz niewłaściwy schemat strukturalny. Sugeruję, aby to poprawić, jeśli chcesz uprościć to również w przyszłości.
Przepisanie wewnętrznego modelu
newInstance()
inewFromBuilder()
nie jest dobrym / zalecanym sposobem i musisz go przerobić, gdy otrzymasz aktualizację z frameworka.źródło
Myślę, że możesz zastąpić
newInstance
metodę naAnimal
modelu i sprawdzić typ z atrybutów, a następnie zainicjować odpowiedni model.Musisz także zastąpić
newFromBuilder
metodę.źródło
type
w bazie danych?Jeśli naprawdę chcesz to zrobić, możesz zastosować następujące podejście w swoim modelu zwierząt.
źródło
Jak stwierdził OP w swoich komentarzach: Projekt bazy danych jest już ustawiony i dlatego relacje polimorficzne Laravela nie wydają się być tutaj opcją.
Podoba mi się odpowiedź Chrisa Neala, ponieważ ostatnio musiałem zrobić coś podobnego (napisanie własnego sterownika bazy danych do obsługi Eloquent dla plików dbase / DBF) i zdobyłem duże doświadczenie z elementami wewnętrznymi Eloquent ORM Laravela.
Dodałem do tego mój osobisty gust, aby kod był bardziej dynamiczny, a jednocześnie zachowywał wyraźne mapowanie dla poszczególnych modeli.
Obsługiwane funkcje, które szybko przetestowałem:
Animal::find(1)
działa zgodnie z pytaniemAnimal::all()
działa równieżAnimal::where(['type' => 'dog'])->get()
zwróciAnimalDog
-objects jako kolekcjęAnimal
-model w przypadku, gdy nie skonfigurowano mapowania (lub nowe mapowanie pojawiło się w DB)Niedogodności:
newInstance()
inewFromBuilder()
całkowicie model (skopiuj i wklej). Oznacza to, że jeśli będzie jakaś aktualizacja z frameworka do tych funkcji członkowskich, będziesz musiał ręcznie zaadaptować kod.Mam nadzieję, że to pomoże i jestem gotowy na wszelkie sugestie, pytania i dodatkowe przypadki użycia w twoim scenariuszu. Oto przykłady użycia i przykłady:
A to jest przykład tego, jak można go użyć i poniżej odpowiednich wyników:
co daje następujące wyniki:
A jeśli chcesz użyć
MorphTrait
tutaj, jest to oczywiście pełny kod:źródło
Myślę, że wiem, czego szukasz. Rozważ to eleganckie rozwiązanie, które wykorzystuje zakresy zapytań Laravela, zobacz https://laravel.com/docs/6.x/eloquent#query-scopes, aby uzyskać dodatkowe informacje:
Utwórz klasę nadrzędną, która ma wspólną logikę:
Utwórz dziecko (lub wiele) z globalnym zakresem zapytania i
saving
obsługą zdarzeń:(to samo dotyczy innej klasy
Cat
, wystarczy zastąpić stałą)Globalny zasięg zapytania działa jak domyślna modyfikacja zapytania, dzięki czemu
Dog
klasa zawsze będzie szukała rekordówtype='dog'
.Powiedzmy, że mamy 3 rekordy:
Teraz wywołanie
Dog::find(1)
spowodujenull
, ponieważ domyślny zakres zapytania nie znajdzie tego,id:1
co jestCat
. WywołanieAnimal::find(1)
iCat::find(1)
będzie działać, chociaż tylko ostatni daje rzeczywisty obiekt Cat.Zaletą tego ustawienia jest to, że możesz użyć powyższych klas do tworzenia relacji takich jak:
I ta relacja automatycznie da ci tylko wszystkie zwierzęta z
type='dog'
(w formieDog
klas). Zakres zapytania jest stosowany automatycznie.Ponadto wywołanie
Dog::create($properties)
automatycznie ustawi wartość natype
z'dog'
powodusaving
zdarzenia związanego z zdarzeniem (patrz https://laravel.com/docs/6.x/eloquent#events ).Pamiętaj, że dzwonienie
Animal::create($properties)
nie ma wartości domyślnej,type
dlatego musisz ustawić ją ręcznie (czego należy się spodziewać).źródło
Chociaż używasz Laravela, w tym przypadku myślę, że nie powinieneś trzymać się skrótów Laravela.
Problem, który próbujesz rozwiązać, to klasyczny problem, który wiele innych języków / frameworków rozwiązuje przy użyciu wzorca metody Factory ( https://en.wikipedia.org/wiki/Factory_method_pattern ).
Jeśli chcesz, aby Twój kod był łatwiejszy do zrozumienia i bez ukrytych sztuczek, powinieneś użyć dobrze znanego wzoru zamiast ukrytych / magicznych sztuczek pod maską.
źródło
Najłatwiejszym sposobem jest stworzenie metody w klasie Animal
Model rozstrzygający
Zwróci instancję klasy Animal, Dog lub Cat w zależności od typu modelu
źródło