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.

1. Niente comandi distruttivi per il DB

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());
}

2. Modalità strict per i model

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.
  1. consente di identificare i problemi legati alle query N+1 e al lazy loading: se un model dichiara una relazione, ma questa viene usata senza che sia stata caricata tramite 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.
    Disabilitando il lazy loading, su ambienti non critici (es. in locale) verrà sollevata un'eccezione: ad esempio, 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 caso
  2. previene comportamenti indesiderati quando si aggiorna un model: ad esempio, se il model Customer 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
  3. evita che possano essere richiamati attributi di un model che non esistono. Ad esempio, se richiamo $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.

3. Forzare HTTPS

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.

4. CarbonImmutable di default

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);

4+1. Prefetch degli asset tramite Vite

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!

Articolo precedente Prossimo articolo