Saltar al contenido

Eloquent es permisivo por defecto. Demasiado. Permite lazy loading silencioso, ignora atributos que no puede asignar, y devuelve null cuando accedes a columnas que no existen. Estos errores silenciosos se acumulan hasta que explotan en producción.

Laravel incluye Eloquent Strict Mode para forzar excepciones cuando cometes estos errores. Así los detectas en desarrollo, no cuando tu cliente te llama un domingo.

Activación rápida

<?php

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Model::shouldBeStrict(! $this->app->isProduction());
    }
}

Con una línea activas tres protecciones. Veamos qué hace cada una.

preventLazyLoading: Adiós al N+1

El problema clásico:

<?php

$posts = Post::all();

foreach ($posts as $post) {
    echo $post->user->name; // Query por cada post
}

Si tienes 100 posts, ejecutas 101 queries. Sin Strict Mode, funciona perfectamente. Con él:

Attempted to lazy load [user] on model [App\Models\Post] but lazy loading is disabled.

La solución es eager loading:

<?php

$posts = Post::with('user')->get();

foreach ($posts as $post) {
    echo $post->user->name; // Sin queries adicionales
}

Cuándo activarlo

Solo en desarrollo. En producción, un N+1 es un problema de rendimiento, no un error crítico. No querrás que tu aplicación explote por esto.

<?php

Model::preventLazyLoading(! app()->isProduction());

preventSilentlyDiscardingAttributes: Mass Assignment real

Tienes un modelo con $fillable definido:

<?php

class User extends Model
{
    protected $fillable = ['name', 'email'];
}

Ahora intentas asignar un campo que no está en fillable:

<?php

User::create([
    'name' => 'Juan',
    'email' => '[email protected]',
    'role' => 'admin', // No está en $fillable
]);

Sin Strict Mode, role se ignora silenciosamente. El usuario se crea sin rol y tú sin enterarte.

Con preventSilentlyDiscardingAttributes:

Add fillable property [role] to allow mass assignment on [App\Models\User].

Cuándo activarlo

Siempre. En todos los entornos. Si intentas guardar datos que no se guardan, eso es un bug, no un problema de rendimiento.

<?php

Model::preventSilentlyDiscardingAttributes();

preventAccessingMissingAttributes: Typos y selects parciales

Dos escenarios donde esto salva:

Typos en atributos

<?php

$user = User::find(1);
echo $user->naem; // Typo: "naem" en vez de "name"

Sin Strict Mode devuelve null. Con él:

The attribute [naem] either does not exist or was not retrieved for model [App\Models\User].

Selects parciales

<?php

$user = User::select('id', 'name')->first();
echo $user->email; // No lo seleccionaste

Mismo error. Te obliga a ser explícito sobre qué columnas necesitas.

Cuándo activarlo

Siempre. Si accedes a un atributo que no existe o no recuperaste, es un bug.

<?php

Model::preventAccessingMissingAttributes();

Configuración recomendada

La estrategia óptima separa rendimiento de corrección:

<?php

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Corrección: siempre activos
        Model::preventSilentlyDiscardingAttributes();
        Model::preventAccessingMissingAttributes();
        
        // Rendimiento: solo en desarrollo
        Model::preventLazyLoading(! $this->app->isProduction());
    }
}

Logging en vez de excepciones

Para proyectos legacy o cuando quieres detectar problemas en producción sin romper nada:

<?php

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Model::preventLazyLoading();
        
        Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) {
            Log::warning('Lazy loading detectado', [
                'model' => get_class($model),
                'relation' => $relation,
            ]);
        });
    }
}

Puedes enviarlo a Sentry, Bugsnag o cualquier servicio de monitoreo:

<?php

Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) {
    if (app()->bound('sentry')) {
        \Sentry\captureMessage("N+1 potencial: [{$relation}] en " . get_class($model));
    }
});

Migración gradual en proyectos legacy

Estrategia para proyectos existentes:

  1. Activa logging en producción durante una semana

  2. Analiza los logs y corrige los N+1 más frecuentes

  3. Activa excepciones en desarrollo

  4. Corrige el resto de violaciones

  5. Opcionalmente, activa excepciones en producción

Desactivar por modelo específico

Si tienes un modelo problemático que no puedes refactorizar inmediatamente:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class LegacyReport extends Model
{
    public $preventsLazyLoading = false;
}

Esto desactiva la protección solo para ese modelo. Útil para migraciones graduales.

Strict Mode en tests

Strict Mode también aplica en tests, lo cual es útil para detectar problemas:

<?php

namespace Tests\Feature;

use App\Models\Post;
use App\Models\User;
use Tests\TestCase;

class PostTest extends TestCase
{
    public function test_posts_list_loads_users_correctly(): void
    {
        User::factory()->has(Post::factory()->count(3))->create();
        
        // Si olvidas ->with('user'), el test falla
        $posts = Post::with('user')->get();
        
        foreach ($posts as $post) {
            $this->assertNotNull($post->user->name);
        }
    }
}

Resumen

Método

Problema

Entorno

preventLazyLoading

N+1, rendimiento

Solo desarrollo

preventSilentlyDiscardingAttributes

Datos no guardados

Siempre

preventAccessingMissingAttributes

Typos, datos no recuperados

Siempre

Activa Strict Mode en cada proyecto nuevo. Para proyectos existentes, usa el handler de logging para detectar problemas sin romper producción.

Tu yo del futuro debuggeando un domingo te lo agradecerá.

school Curso completo

Curso Laravel 12
Completo 2026

El único curso 100% actualizado que incluye Laravel 12, Livewire 3, Vue 3, React 19 e Inertia 2. Aprende con proyectos reales y las últimas funcionalidades.

access_time 8 horas de contenido
layers 4 tecnologías en 1
update 100% actualizado
code Proyectos prácticos
Ver Curso Laravel 12 arrow_forward

star Incluido en cualquier suscripción

Rutas de aprendizaje