Laravel krok po kroku - część 7 - Relacje pomiędzy modelami Eloquenta

Paweł Mysior
27 lutego 2019

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 PostTag 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_idtag_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.

Wygodny hosting zapewnia duet DigitalOceanLaravel Forge.
Copyright © laravelpolska.com

Drogi Użytkowniku!

Dalsze korzystanie z serwisu bez zmiany ustawień dotyczących cookies w przeglądarce oznacza akceptację plików cookies, co będzie skutkowało zapisywaniem ich na Twoich urządzeniach przez serwis internetowy laravelpolska.com. Jeśli nie wyrażasz zgody na przyjmowanie cookies, prosimy o zmianę ustawień w przeglądarce lub o opuszczenie serwisu. więcej

Stosujemy pliki cookies (tzw. ciasteczka) i inne pokrewne technologie, które mają na celu:

  • dostosowanie zawartości stron internetowych Serwisu do Twoich preferencji oraz optymalizacji korzystania ze stron internetowych; w szczególności pliki te pozwalają rozpoznać Twoje urządzenie i odpowiednio wyświetlić stronę internetową, dostosowaną do Twoich indywidualnych potrzeb;
  • utrzymanie Twojej sesji w Serwisie (po zalogowaniu), dzięki czemu nie musisz na każdej podstronie Serwisu ponownie wpisywać loginu i hasła,
  • zapewnienie bezpieczeństwa podczas korzystania z Serwisu,
  • ulepszenie świadczonych przez nas usług poprzez wykorzystanie danych w celach analitycznych i statystycznych,
  • poznanie Twoich preferencji na podstawie sposobu korzystania z naszych serwisów.

Wykorzystanie cookies pozwala nam zapewnić maksymalną wygodę przy korzystaniu z naszego Serwisu poprzez zapamiętanie Waszych preferencji i ustawień na naszych stronach. Więcej informacji o zamieszczanych plikach cookie oraz o możliwości zmiany ustawień przeglądarki oraz polityce przetwarzania danych znajdziesz w polityce prywatności.

Masz możliwość samodzielnej zmiany ustawień dotyczących cookies w swojej przeglądarce internetowej. Z poziomu przeglądarki internetowej, z której korzystasz, możliwe jest zarządzanie plikami cookies. W najpopularniejszych przeglądarkach istnieje m.in. możliwość:

  • zaakceptowania obsługi cookies, co pozwala na pełne korzystanie z opcji oferowanych przez witryny internetowe;
  • zarządzania plikami cookies na poziomie pojedynczych, wybranych przez użytkownika witryn;
  • określania ustawień dla różnych typów plików cookies, na przykład akceptowania plików stałych, jako sesyjnych itp.;
  • blokowania lub usuwania cookies.

Akceptuję pliki cookies