Resumen
Cada vez que se instancia un modelo Eloquent se ejecutan dos fases: el “arranque” (boot) y la “inicialización” (initialize). Si defines determinados métodos en un trait, basta con que el modelo lo declare mediante use para que Eloquent los descubra y los invoque automáticamente.
Gracias a este mecanismo puedes implementar traits que inyectan funcionalidad en tus modelos de forma limpia. El propio framework Laravel aprovecha este patrón en muchos traits como SoftDeletes, HasUuids o HasUlids.
Diferencia entre boot e initialize
| bootXxx() | initializeXxx() |
|---|
| Momento de invocación | La primera vez que arranca la clase del modelo (una vez por clase) | Cada vez que se crea una instancia |
| Tipo de método | static | Método de instancia |
| Uso típico | Registrar listeners de eventos, añadir global scopes, etc. | Establecer valores por defecto, inicializar propiedades, etc. |
Convenciones de nomenclatura
bootXxx()
Si defines un método static con el prefijo boot seguido del nombre del trait, se invocará una sola vez al arrancar el modelo.
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();
}
});
}
}
Uso desde el modelo:
use App\Models\Concerns\HasSlug;
class Article extends Model
{
use HasSlug;
// bootHasSlug() se invoca automáticamente
}
initializeXxx()
Si defines un método de instancia con el prefijo initialize seguido del nombre del trait, se invocará cada vez que se haga new sobre el modelo.
namespace App\Models\Concerns;
trait HasDefaultStatus
{
public function initializeHasDefaultStatus(): void
{
$this->attributes['status'] ??= 'draft';
}
}
También sirve para añadir dinámicamente entradas a $casts:
trait HasMetadata
{
public function initializeHasMetadata(): void
{
$this->mergeCasts([
'metadata' => 'array',
]);
}
}
A partir de Laravel 11 puedes indicar los métodos explícitamente con PHP Attributes, sin depender de la convención de nomenclatura.
| Attribute | Objetivo |
|---|
#[Boot] | Método que se ejecuta una única vez al arrancar la clase |
#[Initialize] | Método que se ejecuta cada vez que se crea una instancia |
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;
}
}
Al usar PHP Attributes, el nombre del método deja de estar atado al nombre del trait, así que puedes elegir nombres realmente significativos. Además, puedes definir varios métodos #[Boot] o #[Initialize] dentro del mismo trait.
Ejemplos prácticos: uso en el desarrollo de paquetes
Añadir un global scope automáticamente
trait HasTenant
{
public static function bootHasTenant(): void
{
static::addGlobalScope(new TenantScope);
}
}
Añadir casts por defecto
trait HasJsonSettings
{
public function initializeHasJsonSettings(): void
{
$this->mergeCasts([
'settings' => 'array',
]);
}
}
trait Archivable
{
#[Boot]
public static function bootArchivable(): void
{
static::deleting(function ($model) {
Archive::store($model->toArray());
});
}
}
Ejemplos dentro del framework Laravel
El propio framework Laravel adopta este patrón en muchos de sus traits.
| Trait | Método | Qué hace |
|---|
SoftDeletes | bootSoftDeletes() | Añade el global scope para soft delete |
HasFactory | bootHasFactory() | Nada relevante (mecanismo fuera del alcance) |
HasUlids | bootHasUlids() | Asigna automáticamente un ULID en el evento creating |
HasUuids | bootHasUuids() | Asigna automáticamente un UUID en el evento creating |
Leer estas implementaciones como referencia ayuda a entender el patrón con más profundidad.
Consideraciones
Limpiar la caché de modelos
Como boot* solo se ejecuta una vez por clase, hay que tener cuidado en el entorno de pruebas. Puedes resetear la caché de arranque con Model::clearBootedModels().
// Resetea el estado boot de los modelos tras cada test
protected function tearDown(): void
{
Model::clearBootedModels();
parent::tearDown();
}
Orden de ejecución
Cuando un mismo modelo hace use de varios traits, los métodos bootXxx() / initializeXxx() se invocan en el orden de resolución de traits de PHP (el orden de declaración). Si existen dependencias entre ellos, ten en cuenta el orden en el use.
Resumen
| Patrón | Cómo se define | Cuándo aplicarlo |
|---|
| Arranque de la clase | static bootXxx() o #[Boot] | Registro de eventos, adición de global scopes |
| Inicialización de instancia | initializeXxx() o #[Initialize] | Valores por defecto, adición de casts |
Implementar bootXxx() / initializeXxx() en un trait permite inyectar funcionalidad en un modelo con una sola línea use. Es el patrón habitual para añadir funciones a modelos desde un paquete.