
Per testare un valore nullo usa whereNull() e whereNotNull(). Per sapere se una riga esiste, exists() invece di count() > 0: il database restituisce un booleano invece di contare. Su una collection già caricata usa isEmpty() e mai if ($collection), che è sempre vero.
Quattro domande tornano in continuazione appena si scrivono query Eloquent: questa colonna è nulla, questo record esiste, quanti ce ne sono, e come recuperare solo le colonne che servono davvero. Questa guida le affronta tutte e quattro, con il SQL realmente prodotto e le trappole che costano una query di troppo.
Questo articolo fa parte del percorso Sviluppo web. Tutti gli esempi sono stati eseguiti su Laravel 13.30.1 con PHP 8.4.25, su una tabella tasks che contiene le colonne id, title, assignee (nullable), active, priority e le colonne di timestamp, la cui assenza provoca un errore al primo salvataggio, e la cui conversione in oggetti Carbon è automatica.
Verificare se una colonna è nulla: whereNull e whereNotNull
In SQL si scrive IS NULL e IS NOT NULL, perché = NULL non è mai vero. Eloquent espone le due condizioni sotto forma di metodi:
// le task senza responsabile
Task::whereNull('assignee')->get();
// le task che ne hanno uno
Task::whereNotNull('assignee')->get();Il SQL prodotto è esattamente quello che ci si aspetta:
select * from "tasks" where "assignee" is not nullLa stessa cosa funziona sul query builder, senza passare da un modello:
use Illuminate\Support\Facades\DB;
DB::table('tasks')->whereNull('assignee')->get();Puoi passare un array per testare più colonne insieme: la condizione vale allora su tutte.
Task::whereNull(['assignee', 'closed_at'])->get();Si legge spesso che where('assignee', null) non funziona. È falso da parecchio tempo: Laravel riconosce il valore nullo e genera is null. Verificato su Laravel 13, Task::where('assignee', null) produce select * from "tasks" where "assignee" is null e restituisce le stesse righe di whereNull(). Meglio comunque whereNull(): l’intenzione è esplicita e non dipende da questa normalizzazione.
Le varianti seguono la logica abituale del query builder, con or e la negazione:
Task::whereNull('assignee')->orWhereNotNull('closed_at')->get();Verificare se un record esiste
Per sapere se esiste una riga corrispondente, non andarla a caricare. exists() chiede al database una risposta booleana:
if (Task::where('title', 'Publier')->exists()) {
// almeno una riga corrisponde
}
if (Task::where('title', 'Publier')->doesntExist()) {
// nessuna riga
}La differenza rispetto a count() si vede nel log delle query. Ecco che cosa mandano davvero al database le tre scritture:
-- 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" = ?Tutte e tre rispondono alla domanda, ma la terza carica e idrata tutte le righe in oggetti per buttarne via subito il contenuto. Su una tabella grande è la differenza fra una query a costo costante e una query che cresce insieme ai dati.
Creare solo se il record non esiste
La coppia « prima controllo, poi inserisco » ha un nome: firstOrCreate(). Il primo array serve a cercare, il secondo fornisce i valori usati soltanto in caso di creazione.
$post = Post::firstOrCreate(
['slug' => $slug], // criterio di ricerca
['title' => $title, 'body' => $body], // usato solo in fase di creazione
);Se la riga esiste già, il secondo array viene ignorato: il record viene restituito così com’è, senza scrittura. Due varianti completano la famiglia:
// stessa logica, ma senza salvare nel database
$post = Post::firstOrNew(['slug' => $slug], ['title' => $title]);
$post->exists; // false finché non chiami save()
// trova e aggiorna, oppure crea
$post = Post::updateOrCreate(['slug' => $slug], ['title' => $title]);firstOrCreate() esegue un SELECT e poi un INSERT. Due richieste simultanee possono superare il SELECT nello stesso momento e tentare due inserimenti. Metti un vincolo di unicità sulla colonna interessata, con una migration: è quello, e non il codice PHP, a garantire l’unicità.
Contare e riconoscere una collection vuota
Bisogna distinguere due count(). Sul query builder è un COUNT(*) eseguito dal database. Su una collection già caricata è un semplice conteggio in memoria.
// COUNT(*) nel database, nessuna riga caricata
Task::where('active', true)->count(); // 3
// le righe vengono caricate, poi contate in PHP
$tasks = Task::where('active', true)->get();
$tasks->count(); // 3Se ti servono comunque le righe, la seconda forma è quella giusta: non lanciare una query di conteggio in più. Se vuoi solo il numero, prendi la prima.
Per capire se una collection è vuota ci sono due metodi espliciti:
$tasks = Task::where('title', 'Inexistant')->get();
$tasks->isEmpty(); // true
$tasks->isNotEmpty(); // falseif ($tasks) è sempre vero, anche quando la collection non contiene niente: è un oggetto. Il test va fatto con isEmpty(), isNotEmpty() o blank(). È l’errore più frequente di questa sezione.
Nota di passaggio che isEmpty() non accetta argomenti. Si incontra spesso $posts->isEmpty($posts), che funziona per caso perché l’argomento viene ignorato.
In una vista Blade, @forelse gestisce il caso vuoto senza condizione esplicita:
@forelse ($tasks as $task)
<li>{{ $task->title }}</li>
@empty
<li>Aucune tâche pour le moment.</li>
@endforelseSelezionare solo le colonne utili
Per impostazione predefinita Eloquent fa un select *. Su una tabella larga, o quando mostri solo due campi, sono trasferimento e idratazione buttati via. Tre scritture permettono di restringere le colonne:
// 1. select() sul query builder
Task::select('title', 'priority')->where('active', true)->get();
// 2. come argomento di get() o di first()
Task::where('active', true)->get(['title']);
Task::where('assignee', 'Damien')->first(['title', 'priority']);
// 3. aggiungendo a una selezione esistente
Task::select('id')->addSelect('title')->first();Quando ti serve un valore soltanto, due metodi evitano di passare da un modello completo:
// un solo valore scalare
Task::where('assignee', 'Damien')->value('title'); // 'Écrire le guide'
// un elenco di valori
Task::where('active', true)->pluck('title'); // ['Écrire le guide', 'Relire']
// un elenco indicizzato da un'altra colonna
Task::pluck('title', 'id'); // [1 => 'Écrire le guide', …]value() sostituisce con vantaggio il first(['title'])->title che si scrive per riflesso: restituisce direttamente la stringa, e null se nessuna riga corrisponde, là dove l’altra scrittura provoca un errore su un oggetto nullo.
Dopo un first(['title']), l’attributo priority non esiste sul modello. Leggerlo restituisce null senza errore, e il problema resta nascosto fino alla visualizzazione. Ricordati anche di includere la chiave primaria nella selezione: senza id, le relazioni e save() non riescono più a ritrovare la riga.
Errori frequenti
isEmpty(), isNotEmpty() o blank().first(['title']), leggere priority restituisce null senza errore. Includi sempre la chiave primaria, altrimenti relazioni e save() smettono di funzionare.count() sul builder: è un COUNT(*) che non porta indietro nessuna riga.$posts->isEmpty($posts) si vede spesso e funziona per caso, dato che l'argomento viene ignorato.

