Eloquent pod pokličkou: with()

Metoda with() slouží jako rychlý způsob jak snížit počet dotazů z vaší aplikaci na databázi. Metoda přijímá název definované relace v modelu a načte ho v jednom dotazu, namísto toho aby se musel volat separátně dotaz na databázi kdykoliv kdy je potřeba získat i danou relaci. V tomto článku se důkladněji podíváme jak Eloquent s touto metodou pracuje a jak vůbec funguje.

Metoda je deklarována ve třídě Illuminate\Database\Eloquent\Builder, což je klíčová třída, která je využívána jakmile začnete pracovat s dotazy pomocí Eloquent modelů. Bez použití with() se setkáme s takzvaným n+1 problémem. To jednoduše znamená, že jeden dotaz se vykoná pro získání všech rodičů (+1) a poté se volají dotazy pro každý jednotlivý rodič (n), abychom získali jeho děti. Pro lepší pochopení uvedu příklad:

Pohled tak může vypadat například takto:

@foreach ($authors as $author) {
    <h1>Články autora {{ $author->name }}:</h1>
    <table>
	    @foreach ($author->articles as $article) {
            <tr>
                <td>{{ $article->title }}</td>
                <td>{{ $article->description}}</td>
            </tr>
        @endforeach
    </table>
@endforeach

Nyní předpokládejme, že aktuálně na stránce nejsou žádné jiné interakce s databází a zobrazíme pět autorů a jejich články. Eloquent vykoná tyto dotazy:

V tomto okamžiku se to nezdá jako moc náročné vykonat 6 databázových dotazů, zvláště pokud jsou takto jednoduché. Co se ale stane když se náš počet autorů rozroste do bodu, kde budeme potřebovat zobrazit na stránce 500 autorů? Na základě uvedené logiky se vykoná jeden dotaz pro získání všech autorů a poté se provede dalších 500 dotazů pro získání článků autorů.

Když bychom znovu načetli stránku, získali bychom následující dva dotazy, které se vykonají:

Změnou dotazu, kdy využíváme IN namísto standardního WHERE, Eloquent jednoduše vkládá pole s ID autory aby získal všechny články, které patří kterémukoliv zadanému autorovi.

Jakmile jsou tyto dva dotazy vykonány, má Eloquent všechny potřebné články získané z druhého dotazu. Nicméně v tomto stádiu je celý výsledek z tabulky Article obsažen v jediném setu. Proto Eloquent využije metodu match(), která přiřadí článek k danému autorovi. V našem případě tedy Eloquent projde každého autora z tabulky Author a vyfiltruje pouze články, kde author_id tabulky Article se shoduje s ID autora. Výsledná kolekce článků je pak přiřazena do relace $author->articles.

Metodu match() použitou v tomto příkladu můžete najít ve třídě Illuminate\Database\Eloquent\Relations\BelongsTo.

Nyní pro jednoduchost předpokládejme, že máme pět autorů, kdy každý napsal tři články, které jsou ohodnoceny jako "1,2,3". Pokud bychom chtěli opět dostat výsledek, museli bychom vykonat tyto dotazy:

To je 21 dotazů jen pro seznam pěti autorů!

Nicméně, pokud opět využijeme takzvaný eager loading (jednoduše řečeno že výsledek chceme dostat okamžitě) abychom získali články i hodnocení, stačí napsat tento kód:

Author::with('articles.rank')->get();

A namísto toho uvidíme vykonat pouze tyto tři dotazy:

Například předpokládejme, že chceme seřadit články autora podle abecedy v opačném pořadí:

Author::with([
    'articles' => function ($query) {
        $query->orderBy('title', 'desc')
            ->with('rank');
    }
])->get();

Díky tomu získáme tyto SQL dotazy: