Kontynuacja lekcji poprzednich – robimy queries na relacjach, poznajemy lepiej dostępne nam narzędzia. Do dzieła.

Ok, rzućmy okiem na te, już napisane komendy:

Artisan::command('hasaddr', function () {
    $cnt = Person::has('address')->count();
    $this->comment("There are $cnt profiles with address");
});

Artisan::command('hasnoaddr', function () {
    $cnt = Person::doesntHave('address')->count();
    $this->comment("There are $cnt profiles without address");
});

Artisan::command('peoplecount', function () {
    $all = Person::count();
    $this->comment("There are $all people");
});

Swoją drogą Laravel to nie jest narzędzie do nauki SQL, tylko framework upraszczający wiele rzeczy (m. in. nie musimy czystego SQL używać). Natomiast warto znać SQL, zanim się zabierzemy za Laravela.

Wspominam o tym, bo gdybyśmy chcieli wywołać ->toSql() na count, to dostaniemy, że int nie posiada metody toSql. A count to pochodzi z klasy Collection (o ile się nie mylę), z warstwy abstrakcji.

Nieważne, te metody powinniśmy znać. Z has jest jeszcze jedna fajna sztuczka, pod warunkiem, że relacja jest jeden do wielu:

$posts = Post::has('comments', '>=', 3)->get();

Tutaj możemy stawiać warunki, ale tylko względem count comments (czyli inaczej można zrobić Post::withCount(’comments’)->where(’comments_count’ i tak dalej.

Ok, przypomnijmy sobie jeszcze whereIn oraz whereNotIn:

Artisan::command('wherein', function () {

    $sql = Address::whereIn('city', ["London", "Warsaw"])->get();

    foreach($sql as $addr){
        $this->comment("ID: {$addr->id}");
        $this->comment("City: {$addr->city}");
        $this->comment("Street: {$addr->street}");
        $this->comment("----------------------------------");
    }
    
});

Artisan::command('wherenotin', function () {

    $sql = Address::whereNotIn('city', ["Paris", "Budapest"])->get();

    foreach($sql as $addr){
        $this->comment("ID: {$addr->id}");
        $this->comment("City: {$addr->city}");
        $this->comment("Street: {$addr->street}");
        $this->comment("----------------------------------");
    }
    
});

Zanim poznamy garść nowych metod, przypomnijmy sobie jeszcze whereKey i whereKeyNot (przez key rozumiemy primary key):

Artisan::command('wherekey', function () {

    $sql = Address::whereKey([1,2])->get();

    foreach($sql as $addr){
        $this->comment("ID: {$addr->id}");
        $this->comment("City: {$addr->city}");
        $this->comment("Street: {$addr->street}");
        $this->comment("----------------------------------");
    }
    
});


Artisan::command('wherekeynot', function () {

    $sql = Address::whereKeyNot([2,3,4])->get();

    foreach($sql as $addr){
        $this->comment("ID: {$addr->id}");
        $this->comment("City: {$addr->city}");
        $this->comment("Street: {$addr->street}");
        $this->comment("----------------------------------");
    }
    
});

Ok, teraz wyświetlimy osoby, które mają adres:

Artisan::command('person-hasaddr', function () {
    $people = Person::has('address')->get();
    foreach($people as $person){
        $this->comment($person->firstName);
    }
    
});

A teraz te, które go nie mają:

Artisan::command('person-noaddr', function () {
    $people = Person::doesntHave('address')->get();
    foreach($people as $person){
        $this->comment($person->firstName);
    }
    
});

A teraz poznamy coś mocniejszego od has, czyli whereHas, który pozwala nam stawiać warunki relacji:

Artisan::command('person-wherehas', function () {
    $people = Person::whereHas('address', function ($query) {
        $query->where('city', 'London');
    })
    ->get();
    foreach($people as $person){
        $this->comment($person->firstName);
    }
    
});

Mimo wszystko, choć zapewne to do nas nie dotarło, choć relacja address jest sprawdzana, to nie jest obecna w result secie. Zresztą, sprawdźmy:

Artisan::command('person-wherehas-inspect', function () {
    $people = Person::whereHas('address', function ($query) {
        $query->where('city', 'London');
    })
    ->toSql();
    $this->comment($people);
    
});

Co dostaliśmy?

select * 
from `people`
 where exists 
(select * from `addresses` 
where `people`.`id` = `addresses`.`person_id` 
and `city` = ?)

Ok, jak doładować address to chyba wiemy:

Artisan::command('person-wherehas-with', function () {
    $people = Person::whereHas('address', function ($query) {
        $query->where('city', 'London');
    })
    ->with('address')
    ->get();

    foreach($people as $person){
        $this->comment($person->firstName);
        $this->comment($person->address->city);
    }
    
});

Oczywiście bez tego with odniesienie się do address wykona lazy-loading, z with jest eager loading. Natomiast bez with w result secie nie będzie address.

Ok, ale teraz coś ciekawego, czyli withWhereHas, zatem robimy query na relacji jak w whereHas + robimy with, czyli eager loading tej relacji:

Artisan::command('person-withWhereHas', function () {
    $people = Person::withWhereHas('address', function ($query) {
        $query->where('city', 'London');
    })
    ->get();

    foreach($people as $person){
        $this->comment($person->firstName);
        $this->comment($person->address->city);
    }
    
});

Jeżeli to wszystko brzmi dla nas jak bełkot to musimy albo więcej poćwiczyć, albo cofnąć się i przerobić materiał raz jeszcze.

Osobiście w nauce frameworków zalecam robienie wielu małych ćwiczeń, które mają za zadanie wykonać jakąś jedną funkcjonalność i ją sprawdzić zanim pójdziemy dalej.