W bazie danych najczęściej będziemy mieli tabele, które są ze sobą powiązane. Na przykład wpis może mieć wiele komentarzy oraz może należeć do kategorii. Wpis może też mieć wiele tagów i każdy z tych tagów może należeć do wielu wpisów. Albo adres może należeć tylko do użytkownika i użytkownik może mieć tylko jeden adres.
Eloquent pozwala w łatwy sposób definiować oraz wykonywać operacje na takich relacjach pomiędzy modelami (tabelami).
Jeden do jednego (one-to-one)
Zacznijmy od relacji jeden do jednego. Użyjemy prostego przykładu: użytkownik ma tylko jeden adres. Definujemy metodę address()
w modelu `User':
// app/User.php
class User extends Model
{
public function address()
{
return $this->hasOne(Address::class);
}
}
Używamy wbudowanej w Eloquent metody hasOne()
, do której przekazujemy wskazanie na klasę powiązanego modelu. Musimy się upewnić, że tabela addresses
zawiera kolumnę user_id
. Będzie ona przechowywała informację, do którego użytkownika należy adres.
Eloquent sam domyśli się, żeby użyć do powiązania kolumny user_id
na podstawie nazwy modelu. Jeśli nazwaliśmy kolumnę inaczej, możemy podać jej nazwę jako drugi parametr do metody hasOne()
:
return $this->hasOne(Address::class, 'owner_id');
Gdy zdefiniowaliśmy relację, możemy się do niej odwołać w następujący sposób:
$user = User::find(1);
$address = $user->address;
Zawartością zmiennej $address
będzie obiekt klasy Address
.
Warto zwrócić uwagę na to, że chociaż zdefiniowaliśmy w modelu User
metodę address()
, to odwołujemy się do niej jak do pola w klasie. Eloquent daje nam taką możliwość. Kilkanaście akapitów niżej znajduje się wytłumaczenie dlaczego to tak działa.
Jeśli mamy adres i chcielibyśmy odwołać się do jego właściciela (użytkownika), to musimy zdefiniować relację odwrotną na modelu Address
:
// app/Address.php
class Address extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
Tak jak wcześniej definiujemy metodę, która zwraca wbudowaną metodę Eloquenta. Tym razem używamy metody belongsTo()
. Podobnie jak wcześniej jako jej parametr przekazujemy wskazanie na klasę powiązanego modelu. Możemy się teraz do niej odwołać w podobny sposób:
$address = Address::find(1);
$user = $address->user;
Jeden do wielu (one-to-many)
Relacji jeden do wielu używamy, gdy jeden model ma wiele innych modeli. Posłużymy się przykładem bloga: jeden wpis może mieć wiele komentarzy. Podobnie jak wcześniej definiujemy relację poprzez dodanie metody w modelu:
// app/Post.php
class Post extends Model
{
public function comments()
{
return $this->hasMany(Comment::class);
}
}
W metodzie comments()
zwracamy metodę hasMany()
. Eloquent spodziewa się w tabeli comments
kolumny post_id
. Jeśli nazwaliśmy ją inaczej, to podobnie jak przy metodzie hasOne()
możemy przekazać nazwę jako drugi parametr metody.
Odwołujemy się do relacji, by uzyskać kolekcję (tablicę) komentarzy powiązanych z danym wpisem:
$post = Post::find($id);
$comments = $post->comments;
Relację odwrotną definiujemy identycznie jak przy metodzie jeden do jednego:
// app/Comment.php
class Comment extends Model
{
public function post()
{
return $this->belongsTo(Post::class);
}
}
I dokładnie tak jak przy relacji jeden do jednego możemy odwołać się do wpisu mając konkretny komenatrz w następujący sposób:
$comment = Comment::find($id);
$post = $comment->post;
Wiele do wielu (many-to-many)
Wyobraźmy sobie, że chcemy do naszych wpisów dodawać tagi. Jeden wpis może mieć wiele tagów. Ale jednocześnie, każdy tag może też mieć wiele wpisów. To właśnie przykład relacji wiele do wielu.
Żeby zbudować taką relację pomiędzy modelami Post
i Tag
będziemy potrzebowali tabeli pośredniej, która będzie zawierała informację o powiązaniach. Konwencja Eloquenta dla nazewnictwa tej tabeli jest następująca: nazwy powiązanych tabel w liczbie pojedynczej, ustawione alfabetycznie, oddzielone podkreślnikiem. W naszym przypadku tabela powinna nazywać się post_tag
. Powinna też zawierać kolumny post_id
i tag_id
.
Zdefiniujmy teraz tę relację w modelu Post
:
// app/Post.php
class Post extends Model
{
public function tags()
{
return $this->belongsToMany(Tag::class);
}
}
Tym razem używamy metody belongsToMany()
. Jako jej drugi parametr możemy przekazać nazwę tabeli pośredniczącej. W następujący sposób możemy znaleźć tagi przypisane do wpisu:
$post = Post::find($id);
$tags = $post->tags;
W tym przypadku relacja odwrotna jest identyczna:
// app/Tag.php
class Tag extends Model
{
public function posts()
{
return $this->belongsToMany(Post::class);
}
}
Podobniej jak wcześniej, w następujący sposób możemy znaleźc wpisy przypisane do taga:
$tag = Tag::find($id);
$posts = $tag->posts;
Filtrowanie wyników relacji, czyli różnica pomiędzy $post->comments a $post->comments()
Do tej pory używaliśmy zapisu $post->comments
by dostać wszystkie komentarze przypisane do danego wpisu. Co jeśli chcemy pobrać z bazy tylko, niektóre komentarze? Zobaczmy następujący przykład:
$post = Post::find($id);
$comments = $post->comments()->where('votes', '>', 5)->orderBy('votes', 'desc')->get();
Jeśli zamiast $post->comments
użyjemy rzeczywiście metody $post-comments()
dostaniemy z powrotem instancję Query Buildera, o którym nauczyliśmy się w jednym z poprzednich postów. Teraz możemy do niego dołączyć metody typu where()
czy orderBy()
. Trzeba pamiętać, żeby zakończyć metodą get()
, która wykonuje skonstruowane wcześniej zapytanie.
$post->comments
to pod spodem po prostu $post->comments()->get()
.
Zliczanie liczby powiązanych modeli
Eloquent udostępnia nam metodę withCount()
, która pozwala nam zliczyć liczbę powiązanych modeli:
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
$count = $post->comments_count;
}
Lazy loading a eager loading
Eloquent domyślnie używa metody "lazy loading" do pobierania rekordów relacji. To znaczy, że rekordy powiązanego modelu są pobierane z bazy danych dopiero gdy się do nich odwołamy. Ta metoda okazuje się bardzo nieekonomiczna gdy pobieramy wiele powiązanych rekordów. Spójrzmy na przykład:
$posts = Post::get();
foreach ($post as $post) {
echo $post->author->email;
}
Powyższy kod najpierw wykona jedno zapytanie, żeby pobrać wszystkie wpisy z bazy. Następnie w każdej iteracji pętli wykona zapytanie, żeby pobrać autora dla danego wpisu. Przy stu wpisach wykonanych zostanie aż sto jeden zapytań. To tak zwany problem N+1.
Jeśli z góry wiemy, że będziemy dla każdego wpisu potrzebowali informacji o jego autorze, możęmy poinstruować Eloquent, żeby od razu je gorliwie załadował, czyli zmienił metodę działania na "eager loading". Dodajmy do naszego kodu metodę with()
:
$posts = Post::with('comments')->get();
foreach ($post as $post) {
echo $post->author->email;
}
Niezależnie od liczby wpisów, wykonane zostaną tylko dwa zapytania. Jedno żeby pobrać wszystkie wpisy i jedno, żeby pobrać wszystkich powiązanych autorów.
Zapisywanie powiązanych modeli
Wyobraźmy sobie, że chcemy zapisać nowy komentarz i powiązać go ze wpisem. Nie musimy ręcznie ustawiać wartości kolumny post_id
w modelu Comment
, zamiast tego możemy użyć metody save()
na relacji:
$comment = new Comment(['body' => 'A new comment']);
$post = Post::find(1);
$post->comments()->save($comment);
Wartość kolumny post_id
zostanie ustawiona automatycznie. Możemy też użyć metody saveMany()
do zapisania wielu komentarzy naraz:
$post = App\Post::find(1);
$post->comments()->saveMany([
new Comment(['body' => 'A newer comment']),
new Comment(['body' => 'Another new comment']),
]);
Możemy też użyć metod create()
lub createMany()
:
$post = Post::find(1);
$post->comments()->create([
'body' => 'A new comment',
]);
// lub
$post->comments()->createMany([
[
'body' => 'A new comment',
],
[
'body' => 'Another new comment',
],
]);
Różnica polega na tym, że do metod z przedrostkiem save
przekazujemy obiekt modelu Eloquent, natomiast do metod z przedrostkiem create
przekazujemy tablice atrybutów.
Najczęściej zapisujemy powiązane modele używając metody save()
na rodzicu, na przykład wpisie. Istnieją jednak metody associate()
oraz dissociate()
, których możemy użyć bezpośrednio na dziecku, na przykład komentarzu, gdy chcemy je przywiązać lub odwiązać od rodzica:
$comment = Comment::find(1);
// przywiązanie:
$comment->post()->associate(Post::find(1));
$comment->save();
// odwiązanie:
$comment->post()->dissociate();
$comment->save();
Przy relacji wiele do wielu używamy z kolei metod attach()
oraz detach()
:
$post = Post::find(1);
$post->tags()->attach(1);
$post->tags()->attach([1, 2, 3]);
$post->tags()->detach(1);
$post->tags()->detach([1, 2, 3]);
$post->tags()->detach(); // odwiązanie wpisu od wszystkich tagów, do których jest przywiązany
Możemy też użyć metody sync()
, która najpierw odwiąże wpis od wszystkich tagów, a następnie przywiąże go do wskazanych:
$post->tags()->sync(1);
$post->tags()->sync([1, 2, 3]);
W tej części tutoriala dowiedzieliśmy się jak definiować i używać relacji pomiędzy modelami Eloquenta. W następnej części nauczymy się używać kontrolerów oraz obsługiwać formularze.