Panoramica
Ogni volta che un modello Eloquent viene istanziato esegue due fasi: boot (una volta per classe) e initialize (una volta per istanza). Quando definisci metodi specifici in un trait, Eloquent li rileva e li invoca automaticamente. È sufficiente aggiungere il trait al modello.
Laravel stesso utilizza questo pattern in modo estensivo in trait come SoftDeletes, HasUuids e HasUlids.
boot vs. initialize
| bootXxx() | initializeXxx() |
|---|
| Quando viene chiamato | Una volta, al primo avvio della classe modello | Ogni volta che viene creata un’istanza |
| Tipo di metodo | static | Metodo di istanza |
| Uso tipico | Registrare event listener, aggiungere global scope | Impostare valori di default, unire cast |
Convenzioni di denominazione
bootXxx()
Definisci un metodo static con prefisso boot seguito dal nome della classe del trait. Eloquent lo invoca una sola volta per ciclo di vita della classe.
namespace App\Models\Concerns;
trait HasSlug
{
public static function bootHasSlug(): void
{
static::creating(function ($model) {
if (empty($model->slug)) {
$model->slug = str($model->name)->slug()->toString();
}
});
}
}
Utilizzo del trait in un modello:
use App\Models\Concerns\HasSlug;
class Article extends Model
{
use HasSlug;
// bootHasSlug() viene chiamato automaticamente
}
initializeXxx()
Definisci un metodo di istanza con prefisso initialize seguito dal nome della classe del trait. Eloquent lo invoca ogni volta che viene creata una nuova istanza.
namespace App\Models\Concerns;
trait HasDefaultStatus
{
public function initializeHasDefaultStatus(): void
{
$this->attributes['status'] ??= 'draft';
}
}
Anche l’unione dinamica dei cast è un uso comune:
trait HasMetadata
{
public function initializeHasMetadata(): void
{
$this->mergeCasts([
'metadata' => 'array',
]);
}
}
Attribute PHP
A partire da Laravel 11, puoi utilizzare gli Attribute PHP invece di affidarti alle convenzioni di denominazione.
| Attribute | Scopo |
|---|
#[Boot] | Marca un metodo statico da eseguire una volta all’avvio della classe |
#[Initialize] | Marca un metodo di istanza da eseguire ad ogni istanziazione |
use Illuminate\Database\Eloquent\Attributes\Boot;
use Illuminate\Database\Eloquent\Attributes\Initialize;
trait HasAuditLog
{
#[Boot]
public static function registerAuditListeners(): void
{
static::created(function ($model) {
AuditLog::record('created', $model);
});
static::updated(function ($model) {
AuditLog::record('updated', $model);
});
}
#[Initialize]
public function setAuditDefaults(): void
{
$this->attributes['audit_enabled'] ??= true;
}
}
L’uso degli Attribute PHP libera i nomi dei metodi dal vincolo del nome del trait. Puoi anche definire più metodi #[Boot] o #[Initialize] all’interno dello stesso trait.
Esempi pratici
Aggiunta automatica di un global scope
trait HasTenant
{
public static function bootHasTenant(): void
{
static::addGlobalScope(new TenantScope);
}
}
Unione di cast di default
trait HasJsonSettings
{
public function initializeHasJsonSettings(): void
{
$this->mergeCasts([
'settings' => 'array',
]);
}
}
Archiviazione automatica alla cancellazione
trait Archivable
{
#[Boot]
public static function bootArchivable(): void
{
static::deleting(function ($model) {
Archive::store($model->toArray());
});
}
}
Esempi nel framework
I trait di Laravel dimostrano chiaramente il pattern:
| Trait | Metodo | Cosa fa |
|---|
SoftDeletes | bootSoftDeletes() | Registra il global scope SoftDeletingScope |
HasUlids | bootHasUlids() | Imposta l’ULID nell’evento creating |
HasUuids | bootHasUuids() | Imposta l’UUID nell’evento creating |
Leggere queste implementazioni è un ottimo modo per comprendere il pattern in profondità.
Avvertenze
Pulire i modelli bootati nei test
Poiché boot* viene eseguito una sola volta per classe, i test che modificano il comportamento del modello potrebbero dover reimpostare la cache di boot:
protected function tearDown(): void
{
Model::clearBootedModels();
parent::tearDown();
}
Ordine di esecuzione
Quando un modello usa più trait, bootXxx() e initializeXxx() vengono chiamati nell’ordine di risoluzione dei trait di PHP (ordine di dichiarazione). Fai attenzione all’ordine quando i trait dipendono l’uno dall’altro.
Riepilogo
| Pattern | Come definirlo | Quando usarlo |
|---|
| Boot della classe | static bootXxx() o #[Boot] | Event listener, global scope |
| Init dell’istanza | initializeXxx() o #[Initialize] | Valori di default, cast dinamici |
Implementare bootXxx() e initializeXxx() in un trait ti permette di iniettare comportamenti in qualsiasi modello Eloquent con una singola istruzione use — il pattern di riferimento per gli autori di package.