
Do sprawdzenia wartości null użyj whereNull() i whereNotNull(). Żeby dowiedzieć się, czy wiersz istnieje, sięgnij po exists() zamiast count() > 0: baza zwraca wartość logiczną, zamiast liczyć. Na już załadowanej kolekcji testuj isEmpty(), nigdy if ($collection), które zawsze jest prawdziwe.
Cztery pytania wracają w kółko, gdy tylko zaczynasz pisać zapytania Eloquent: czy ta kolumna jest nullem, czy ten rekord istnieje, ile ich jest i jak pobrać wyłącznie potrzebne kolumny. Ten przewodnik odpowiada na wszystkie cztery, z SQL-em faktycznie wysyłanym do bazy i pułapkami, które kosztują jedno zapytanie za dużo.
Ten artykuł należy do ścieżki Programowanie webowe. Wszystkie przykłady uruchomiono na Laravelu 13.30.1 z PHP 8.4.25, na tabeli tasks z kolumnami id, title, assignee (nullable), active, priority oraz kolumnami timestampów, których brak wywołuje błąd przy pierwszym zapisie, a których konwersja na obiekty Carbon dzieje się automatycznie.
Sprawdzanie, czy kolumna jest nullem: whereNull i whereNotNull
W SQL-u pisze się IS NULL i IS NOT NULL, bo = NULL nigdy nie zwraca prawdy. Eloquent wystawia oba warunki jako metody:
// zadania bez osoby odpowiedzialnej
Task::whereNull('assignee')->get();
// zadania, które go mają
Task::whereNotNull('assignee')->get();Wygenerowany SQL jest dokładnie taki, jakiego się spodziewasz:
select * from "tasks" where "assignee" is not nullTo samo działa na query builderze, bez pośrednictwa modelu:
use Illuminate\Support\Facades\DB;
DB::table('tasks')->whereNull('assignee')->get();Możesz przekazać tablicę, żeby sprawdzić kilka kolumn naraz, warunek obejmie wtedy wszystkie:
Task::whereNull(['assignee', 'closed_at'])->get();Często można przeczytać, że where('assignee', null) nie działa. To nieprawda, i to od dawna: Laravel wykrywa wartość null i generuje is null. Sprawdzone na Laravelu 13, Task::where('assignee', null) daje select * from "tasks" where "assignee" is null i zwraca te same wiersze co whereNull(). Mimo to sięgaj po whereNull(): intencja jest jawna i nie zależy od tej normalizacji.
Warianty trzymają się zwykłej logiki query buildera, z or i z negacją:
Task::whereNull('assignee')->orWhereNotNull('closed_at')->get();Sprawdzanie, czy rekord istnieje
Żeby dowiedzieć się, czy jakiś wiersz pasuje, nie pobieraj go. exists() prosi bazę o odpowiedź logiczną:
if (Task::where('title', 'Publier')->exists()) {
// co najmniej jeden wiersz pasuje
}
if (Task::where('title', 'Publier')->doesntExist()) {
// żadnego wiersza
}Różnicę wobec count() widać w logu zapytań. Oto co te trzy zapisy naprawdę wysyłają do bazy:
-- exists()
select exists(select * from "tasks" where "active" = ?) as "exists"
-- count() > 0
select count(*) as "aggregate" from "tasks" where "active" = ?
-- get()->isNotEmpty()
select * from "tasks" where "active" = ?Wszystkie trzy odpowiadają na to pytanie, ale trzeci ściąga wszystkie wiersze i hydratuje je do obiektów, żeby chwilę później wyrzucić ich zawartość. Na dużej tabeli to różnica między zapytaniem o stałym koszcie a zapytaniem, które rośnie razem z danymi.
Tworzenie tylko wtedy, gdy rekordu jeszcze nie ma
Para „najpierw sprawdzam, potem wstawiam” ma swoją nazwę: firstOrCreate(). Pierwsza tablica służy do szukania, druga podaje wartości używane wyłącznie przy tworzeniu.
$post = Post::firstOrCreate(
['slug' => $slug], // kryterium wyszukiwania
['title' => $title, 'body' => $body], // używane wyłącznie przy tworzeniu
);Jeśli wiersz już istnieje, druga tablica jest ignorowana: rekord wraca bez zmian, bez żadnego zapisu. Rodzinę uzupełniają dwa warianty:
// ta sama logika, ale bez zapisu w bazie
$post = Post::firstOrNew(['slug' => $slug], ['title' => $title]);
$post->exists; // false, dopóki nie wywołasz save()
// znajduje i aktualizuje albo tworzy
$post = Post::updateOrCreate(['slug' => $slug], ['title' => $title]);firstOrCreate() wykonuje SELECT, a potem INSERT. Dwa równoległe żądania mogą przejść przez SELECT w tej samej chwili i spróbować wstawić wiersz dwa razy. Załóż w bazie ograniczenie unikalności na tej kolumnie, przez migrację: to ono, a nie kod PHP, gwarantuje unikalność.
Zliczanie i wykrywanie pustej kolekcji
Trzeba rozróżnić dwa count(). Na query builderze to COUNT(*) wykonany przez bazę. Na już załadowanej kolekcji to zwykłe zliczenie w pamięci.
// COUNT(*) w bazie, żaden wiersz nie jest pobierany
Task::where('active', true)->count(); // 3
// wiersze są ładowane, a potem liczone w PHP
$tasks = Task::where('active', true)->get();
$tasks->count(); // 3Jeśli i tak potrzebujesz wierszy, właściwa jest druga forma: nie odpalaj osobnego zapytania zliczającego. Jeśli chcesz samej liczby, weź pierwszą.
Do sprawdzenia, czy kolekcja jest pusta, służą dwie jawne metody:
$tasks = Task::where('title', 'Inexistant')->get();
$tasks->isEmpty(); // true
$tasks->isNotEmpty(); // falseif ($tasks) jest zawsze prawdziwe, nawet gdy kolekcja nic nie zawiera: to obiekt. Test musi opierać się na isEmpty(), isNotEmpty() albo blank(). To najczęstszy błąd w tej sekcji.
Przy okazji: isEmpty() nie przyjmuje żadnego argumentu. Często widuje się $posts->isEmpty($posts), które działa przez przypadek, bo argument i tak jest ignorowany.
W widoku Blade @forelse obsługuje pusty przypadek bez jawnego warunku:
@forelse ($tasks as $task)
<li>{{ $task->title }}</li>
@empty
<li>Aucune tâche pour le moment.</li>
@endforelsePobieranie tylko potrzebnych kolumn
Domyślnie Eloquent robi select *. Na szerokiej tabeli albo wtedy, gdy wyświetlasz dwa pola, to transfer i hydratacja na darmo. Kolumny da się ograniczyć na trzy sposoby:
// 1. select() na query builderze
Task::select('title', 'priority')->where('active', true)->get();
// 2. jako argument get() albo first()
Task::where('active', true)->get(['title']);
Task::where('assignee', 'Damien')->first(['title', 'priority']);
// 3. przez dodanie do istniejącego wyboru
Task::select('id')->addSelect('title')->first();Kiedy potrzebujesz jednej wartości, dwie metody oszczędzają ci budowania pełnego modelu:
// pojedyncza wartość skalarna
Task::where('assignee', 'Damien')->value('title'); // 'Écrire le guide'
// lista wartości
Task::where('active', true)->pluck('title'); // ['Écrire le guide', 'Relire']
// lista indeksowana inną kolumną
Task::pluck('title', 'id'); // [1 => 'Écrire le guide', …]value() z powodzeniem zastępuje odruchowo pisane first(['title'])->title: zwraca od razu łańcuch znaków, a null, kiedy żaden wiersz nie pasuje, podczas gdy ten drugi zapis wywala się na pustym obiekcie.
Po first(['title']) atrybut priority nie istnieje na modelu. Odwołanie do niego zwraca null bez błędu, co ukrywa problem aż do wyświetlenia. Pamiętaj też o kluczu głównym w wyborze kolumn: bez id relacje i save() nie potrafią już odnaleźć wiersza.
Częste błędy
isEmpty(), isNotEmpty() albo blank().first(['title']) odczyt priority zwraca null bez błędu. Zawsze dołączaj klucz główny, inaczej relacje i save() przestają działać.count() na query builderze: to COUNT(*), które nie pobiera żadnego wiersza.$posts->isEmpty($posts) widuje się często i działa przez przypadek, bo argument jest ignorowany.

