Fajne ćwiczenie. Choć nie wiemy jeszcze co to has one through i has many through, zrobimy sobie coś co odpowiada relacji has many throuh many. Dzięki temu lepiej zrozumiemy relację many to many i tabele piwotalne.
Ok, o co chodzi?
- Tabela movies, klucz głowny id, zero kluczy obcych
- Tabela reviews, klucz obcy movie_id wskazuje na movies (relacja hasMany-belongsTo)
- Tabela tags, tylko tagname i id, zero kluczy obcych
- Tabela review_tag, id, klucze obce: tag_id oraz review_id będące parami, wskazującymi które tagi z którymi recenzjami są powiązane
I teraz chcemy, aby z movies zaciągnąć nazwy tagów. Czyli movies ma recenzje, recenzje przez tabelę piwotalną są połączone z tagami, które mają swoją tabelę i tam tagname.
Ok, czyli tak to wygląda. Daj mi nazwy tagów, które są pod recenzjami, które należą do danego filmu, pamiętaj, że recenzje i tagi nie mają kluczy obcych u siebie tylko są połączone tabelą piwotalną.
Cóż, łatwe to nie jest.
Najpierw szkielet komendy:
Artisan::command('m-tags {id}', function (int $id) {
$movie = Movie::findOrFail($id);
$this->comment("Movie title: {$movie->title}");
});
Ok, teraz tak – mamy id movie. W tabeli movie nie mamy żadnych kluczy obcych (zobaczmy sobie te tabelki raz jeszcze, inaczej trudno będzie zrozumieć), ale w tabeli reviews mamy movie_id.
Do czego nam ta wiedza? Cóż, możemy pobrać numery id recenzji, które należą do filmu o podanym id:
Artisan::command('m-tags {id}', function (int $id) {
$ids_r = DB::table('reviews')
->select('id')
->where('movie_id', $id)
->pluck('id')
->toArray();
dd($ids_r);
}
Ok, przyjęło id filmu, zwróciło jako tablicę numery id recenzji, których movie_id wskazuje na ten film. Co dalej?
Dalej musimy pozyskać jako numery id tagów powiązanych z recenzjami, należącymi do tego filmu. Problem polega na tym, że tabela tags nie ma review_id.
Rzućmy okiem na tabelę piwotalną. Tam mamy id rekordów, timestampy, tag_id, review_id, czyli asocjacje tych dwóch modeli.
Numery id recenzji, które nas interesują już mamy. Nie mamy numerów id tagów. Do dzieła:
Artisan::command('m-tags {id}', function (int $id) {
$ids_r = DB::table('reviews')
->select('id')
->where('movie_id', $id)
->pluck('id')
->toArray();
$ids_t = DB::table('review_tag')
->select('tag_id')
->whereIn('review_id', $ids_r)
->pluck('tag_id')
->toArray();
dd($ids_r);
}
Ok, podsumujmy. Dostaliśmy numer id filmu. Pobraliśmy numery id recenzji, które należą do tego filmu. Pobraliśmy (z piwotalnej) numery id tagów, które należą do recenzji należących do tego filmu.
Mając numery tagów, które nas interesują, możemy pobrać teraz nazwy tych tagów:
Artisan::command('m-tags {id}', function (int $id) {
$ids_r = DB::table('reviews')
->select('id')
->where('movie_id', $id)
->pluck('id')
->toArray();
$ids_t = DB::table('review_tag')
->select('tag_id')
->whereIn('review_id', $ids_r)
->pluck('tag_id')
->toArray();
$tagnames = DB::table('tags')
->select('tagname')
->whereIn('id', $ids_t)
->pluck('tagname')
->toArray();
dd($tagnames);
}
Zobaczmy, jak to działa. Wrzucamy id filmu i dostajemy nazwy tagów? Przypisanych tylko do recenzji posiadanych przez ten film? Duplikatów nie ma?
Ok, to teraz tylko estetyka:
Artisan::command('m-tags {id}', function (int $id) {
$ids_r = DB::table('reviews')
->select('id')
->where('movie_id', $id)
->pluck('id')
->toArray();
$ids_t = DB::table('review_tag')
->select('tag_id')
->whereIn('review_id', $ids_r)
->pluck('tag_id')
->toArray();
$tagnames = DB::table('tags')
->select('tagname')
->whereIn('id', $ids_t)
->pluck('tagname')
->toArray();
$movie = Movie::findOrFail($id);
$tagnamesAsString = implode(", ", $tagnames);
$this->comment("Movie title: {$movie->title}");
$this->comment("Tags: {$tagnamesAsString}");
});
Przetestujmy to sobie bardzo dokładnie i upewnijmy się, że rozumiemy. Niedługo powiemy sobie o relacjach has many through oraz has one through.
Jak mi się poszczęści, to napiszemy sobie własne manyThroughMany. Trzymajcie kciuki.