In ogni applicazione Laravel, ci sono alcuni elementi che non devono mai mancare.
Solitamente vanno definiti nel metodo boot()
di AppServiceProvider
(o di un altro service provider, a seconda di come strutturi le tue applicazioni) che si presenta quindi molto simile a questo esempio:
<?php
declare(strict_types=1);
namespace App\Providers;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Vite;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
$isProduction = app()->isProduction();
// Evita comandi distruttivi in produzione
DB::prohibitDestructiveCommands($isProduction);
// Abilita la modalità "strict" per i modelli in ambienti non di produzione
Model::shouldBeStrict(!$isProduction);
// Imposta lo schema degli URL su HTTPS
URL::forceScheme('https');
// Utilizza CarbonImmutable per la gestione delle date
Date::use(CarbonImmutable::class);
// Abilita il prefetching degli asset generati da Vite
Vite::prefetch();
}
}
Analizziamo gli elementi uno per uno, in modo da capire perché è opportuno usarli.
La sicurezza prima di tutto! A chi non è mai capitato, per distrazione o stanchezza, di eseguire una query o un comando convinto di essere in un ambiente locale o di test, per poi scoprire di aver eliminato dati di produzione? A me è capitato ✋🏻
DB::prohibitDestructiveCommands($isProduction)
impedisce che vengano eseguiti comandi distruttivi sul database.
Ok, ma quali sono questi comandi distruttivi, per i quali è importante proibire l'esecuzione in determinate circostanze? Quelli che fanno uso del trait Illuminate\Console\Prohibitable
. Laravel definisce alcuni comandi come distruttivi: ad esempio, db:wipe
, migrate:fresh
, migrate:refresh
, migrate:reset
.
È possibile aggiungere il trait Prohibitable
anche ai propri comandi:
use Illuminate\Console\Command;
use Illuminate\Console\Prohibitable;
class DeleteDocumentCommand extends Command
{
use Prohibitable;
// ...
}
In un service provider, è sufficiente indicare a Laravel se l'esecuzione di quel comando debba essere proibita o meno, tipicamente con una condizione che risulta vera sull'ambiente di produzione o sugli ambienti considerati "critici":
public function boot(): void
{
// Impedisce l'esecuzione di questo comando in produzione
DeleteDocumentCommand::prohibit($this->app->isProduction());
}
public function boot(): void
{
// ...
Model::shouldBeStrict(! $this->app->isProduction());
}
Questa riga di codice abilita la modalità strict per i model se l'applicazione non è in esecuzione in produzione. In ambienti non critici, è opportuno mantenerla disattivata, in modo che Laravel sollevi le opportune eccezioni, consentendo di correggere il codice; viceversa, sugli ambienti critici l'esecuzione del codice non si deve interrompere e non devono comparire errori.
In cosa consiste esattamente la modalità strict? Eseguire Model::shouldBeStrict(...)
corrisponde a richiamare queste tre metodi:
Model::preventLazyLoading(); // 1.
Model::preventAccessingMissingAttributes(); // 2.
Model::preventSilentlyDiscardingAttributes(); // 3.
with()
(metodo che consente di sfruttare l'eager loading), nel momento in cui utilizzi tale relazione (ad esempio in un ciclo), si verifica il lazy loading. Laravel, per accedere ai valori di cui hai bisogno tramite la relazione, è costretto ad eseguire una query aggiuntiva per ogni record presente nella relazione: se la relazione ha ritornato N record, si traduce in N query che, sommate alla query principale sul model, fanno appunto N+1 query.Attempted to lazy load [user] on model [App\Models\Post] but lazy loading is disabled.
; su ambienti critici, invece, è opportuno lasciare abilitato il lazy loading, in modo che il codice venga comunque eseguito senza ritornare errori. Se la scelta è fra "l'applicazione in produzione si rompe e non ritorna risultati" e "l'applicazione ritorna risultati ma lentamente ed eseguendo un sacco di query", meglio il secondo casoCustomer
non riporta il campo code
nell'array $fillable
e proviamo ad aggiornare un'istanza di quel model, referenziando code
, verrà sollevata un'eccezione tramite la quale Laravel si lamenta e suggerisce di aggiungere $code
all'array $fillable
$user->las_name
e l'attributo non esiste, verrà sollevata un'eccezione che ci avvisa che l'attributo non esiste o non è stato caricato per quel model. In questo esempio, si tratta di un errore di battitura; in altri casi, l'attributo potrebbe essere previsto (sul db, nel model) ma potrebbe essere stato escluso dalla query.HTTPS è il protocollo standard de facto sul web. Inizialmente era considerata "una sicurezza in più"; da qualche anno, ormai, è obbligatorio, tant'è che i moderni browser addirittura impediscono o rendono difficoltoso l'accesso a contenuti serviti su HTTP.
<?php
use Illuminate\Support\Facades\URL;
// nel metodo boot() di un service provider
URL::forceScheme('https');
Questa semplice riga di codice indica a Laravel di utilizzare HTTPS in tutte le rotte del vostro applicativo.
È comunque buona pratica configurare il web server che eroga il tuo applicativo Laravel affinché venga effettuato redirect (permanente) sulla versione HTTPS di un URL, nel caso in cui questo venga richiesto su HTTP.
Carbon è un package PHP molto diffuso e consente di gestire e manipolare date. La classe Carbon
, purtroppo, nasce come mutabile (mutable): questo significa che, se richiamiamo un metodo su un'istanza di Carbon, il risultato modifica l'istanza iniziale. Questo può dar luogo a errori "inaspettati", dovuti al fatto che è facile scordarsi che l'istanza è mutable. Esempio:
// L'helper now() di Laravel, di default, restituisce un'istanza di Carbon
$start = now();
// ...
$end = $start->addHour();
dump($start, $end);
Potresti essere portato a pensare che il codice sia corretto, ovvero che $start
contenga una specifica data/ora e $end
sia di un'ora successiva a $start
. In realtà, $start
e $end
conterranno la stessa data/ora. Provare per credere:
$start->equalTo($end); // true
Questo si verifica proprio perché $start
contiene un'istanza mutabile: $start->addHour()
aggiunge sì un'ora a $start
e ritorna il risultato al chiamante, ma causa anche l'effetto collaterale di modificare il contenuto stesso di $start
.
Vediamola sotto questa luce: Carbon può essere considerato un enorme value object e questi, per loro natura, dovrebbero essere immutabili. Se vogliamo, aver creato Carbon come oggetto mutabile può essere considerato un errore di progettazione. Per correre al riparo, è stato quindi introdotta la sua versione immutabile: CarbonImmutable. Ti consiglio vivamente di usarla al posto di Carbon, per evitare gli errori di cui sopra.
Per fare in modo che Laravel di default utilizzi CarbonImmutable
per le date e, ad esempio, per evitare di doverti ricordare di usare now()->toImmutable()
, è sufficiente fare in questo modo:
<?php
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Date;
// nel metodo boot() di un service provider
Date::use(CarbonImmutable::class);
Se la tua applicazione Laravel fornisce solo endpoint che ritornano JSON, ha poco senso parlare di Vite: la gestione degli asset di frontend è sicuramente gestita da un'altra applicazione che invoca le API fornite dall'applicazione Laravel.
Se, invece, gestisci anche la parte di frontend generando HTML, è sicuramente utile istruire Vite affinché esegua il prefetch degli asset:
use Illuminate\Support\Facades\Vite;
// nel metodo boot() di un service provider
Vite::prefetch();
Cos'è questo prefetch? Vite può essere configurato per suddividere gli asset JavaScript e CSS in piccoli chunk (porzioni), in modo che in ogni pagina venga caricato solo ciò che serve, senza portarsi dietro enormi bundle di classi o funzioni che in quel contesto non servono, ma che verrebbero comunque caricati dal browser, sprecando banda e potenzialmente aumentando i tempi di attesa.
Il prefetch consente di precaricare i chunk Javascript e CSS generati da Vite, in modo da ridurre i tempi di attesa durante la navigazione di una Single Page Application.
Consulta la guida di Laravel e la relativa Pull Request per ulteriori dettagli e opzioni che è possibile utilizzare. Ad esempio, è possibile specificare quale strategia usare:
<?php
use Illuminate\Support\Facades\Vite;
Vite::usePrefetchStrategy('aggressive');
Quali sono gli elementi che non mancano mai in AppServiceProvider delle tue applicazioni Laravel? Fammelo sapere nei commenti!