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 seleccionasteMismo 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:
Activa logging en producción durante una semana
Analiza los logs y corrige los N+1 más frecuentes
Activa excepciones en desarrollo
Corrige el resto de violaciones
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 |
|---|---|---|
| N+1, rendimiento | Solo desarrollo |
| Datos no guardados | Siempre |
| 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á.
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.
star Incluido en cualquier suscripción