Eloquent: Comenzando
- Introducción
- Generando Clases de Modelo
- Convenciones de Modelo Eloquent
- Recuperando Modelos
- Recuperando Modelos Individuales / Agregados
- Insertando y Actualizando Modelos
- Eliminando Modelos
- Poda de Modelos
- Replicando Modelos
- Alcances de Consultas
- Comparando Modelos
- Eventos
Introducción
Laravel incluye Eloquent, un mapeador objeto-relacional (ORM) que hace que sea agradable interactuar con tu base de datos. Al usar Eloquent, cada tabla de la base de datos tiene un "Modelo" correspondiente que se utiliza para interactuar con esa tabla. Además de recuperar registros de la tabla de la base de datos, los modelos Eloquent te permiten insertar, actualizar y eliminar registros de la tabla también.
[!NOTA] Antes de comenzar, asegúrate de configurar una conexión a la base de datos en el archivo de configuración
config/database.php
de tu aplicación. Para obtener más información sobre la configuración de tu base de datos, consulta la documentación de configuración de la base de datos.
Laravel Bootcamp
Si eres nuevo en Laravel, siéntete libre de unirte al Laravel Bootcamp. El Laravel Bootcamp te guiará para construir tu primera aplicación Laravel utilizando Eloquent. Es una excelente manera de explorar todo lo que Laravel y Eloquent tienen para ofrecer.
Generando Clases de Modelo
Para comenzar, vamos a crear un modelo Eloquent. Los modelos suelen vivir en el directorio app\Models
y extienden la clase Illuminate\Database\Eloquent\Model
. Puedes usar el comando make:model
de Artisan artesano para generar un nuevo modelo:
php artisan make:model Flight
Si deseas generar una migración de base de datos cuando generes el modelo, puedes usar la opción --migration
o -m
:
php artisan make:model Flight --migration
Puedes generar varios otros tipos de clases al generar un modelo, como fábricas, sembradores, políticas, controladores y solicitudes de formulario. Además, estas opciones se pueden combinar para crear múltiples clases a la vez:
# Generate a model and a FlightFactory class...php artisan make:model Flight --factoryphp artisan make:model Flight -f # Generate a model and a FlightSeeder class...php artisan make:model Flight --seedphp artisan make:model Flight -s # Generate a model and a FlightController class...php artisan make:model Flight --controllerphp artisan make:model Flight -c # Generate a model, FlightController resource class, and form request classes...php artisan make:model Flight --controller --resource --requestsphp artisan make:model Flight -crR # Generate a model and a FlightPolicy class...php artisan make:model Flight --policy # Generate a model and a migration, factory, seeder, and controller...php artisan make:model Flight -mfsc # Shortcut to generate a model, migration, factory, seeder, policy, controller, and form requests...php artisan make:model Flight --allphp artisan make:model Flight -a # Generate a pivot model...php artisan make:model Member --pivotphp artisan make:model Member -p
Inspeccionando Modelos
A veces puede ser difícil determinar todos los atributos y relaciones disponibles de un modelo solo con revisar su código. En su lugar, prueba el comando Artisan model:show
, que ofrece una visión general conveniente de todos los atributos y relaciones del modelo:
php artisan model:show Flight
Convenciones de Modelos Eloquent
Los modelos generados por el comando make:model
se colocarán en el directorio app/Models
. Examinemos una clase de modelo básica y discutamos algunas de las principales convenciones de Eloquent:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ // ...}
Nombres de Tablas
Después de echar un vistazo al ejemplo anterior, es posible que hayas notado que no le dijimos a Eloquent qué tabla de base de datos corresponde a nuestro modelo Flight
. Por convención, se utilizará el nombre plural en "snake case" de la clase como el nombre de la tabla, a menos que se especifique otro nombre de manera explícita. Así que, en este caso, Eloquent asumirá que el modelo Flight
almacena registros en la tabla flights
, mientras que un modelo AirTrafficController
almacenaría registros en una tabla air_traffic_controllers
.
Si la tabla de base de datos correspondiente a tu modelo no se ajusta a esta convención, puedes especificar manualmente el nombre de la tabla del modelo definiendo una propiedad table
en el modelo:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * The table associated with the model. * * @var string */ protected $table = 'my_flights';}
Claves Primarias
Eloquent también asumirá que la tabla de base de datos correspondiente a cada modelo tiene una columna de clave primaria denominada id
. Si es necesario, puedes definir una propiedad $primaryKey
protegida en tu modelo para especificar una columna diferente que sirva como la clave primaria de tu modelo:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * The primary key associated with the table. * * @var string */ protected $primaryKey = 'flight_id';}
Además, Eloquent asume que la clave primaria es un valor entero en incremento, lo que significa que Eloquent convertirá automáticamente la clave primaria a un entero. Si deseas usar una clave primaria no numérica o no incremental, debes definir una propiedad pública $incrementing
en tu modelo que esté configurada en false
:
<?php class Flight extends Model{ /** * Indicates if the model's ID is auto-incrementing. * * @var bool */ public $incrementing = false;}
Si la clave primaria de tu modelo no es un entero, debes definir una propiedad $keyType
protegida en tu modelo. Esta propiedad debe tener un valor de string
:
<?php class Flight extends Model{ /** * The data type of the primary key ID. * * @var string */ protected $keyType = 'string';}
Claves Primarias "Compuestas"
Eloquent requiere que cada modelo tenga al menos un "ID" que lo identifique de manera única y que pueda servir como su clave primaria. Las claves primarias "compuestas" no son compatibles con los modelos de Eloquent. Sin embargo, puedes agregar índices únicos de múltiples columnas a tus tablas de base de datos además de la clave primaria identificativa única de la tabla.
Claves UUID y ULID
En lugar de utilizar enteros autoincrementales como las claves primarias de tu modelo Eloquent, puedes optar por usar UUIDs. Los UUID son identificadores alfanuméricos universalmente únicos que tienen 36 caracteres de longitud.
Si deseas que un modelo utilice una clave UUID en lugar de una clave entera auto-incremental, puedes usar el trait Illuminate\Database\Eloquent\Concerns\HasUuids
en el modelo. Por supuesto, debes asegurarte de que el modelo tenga una columna de clave primaria equivalente a UUID:
use Illuminate\Database\Eloquent\Concerns\HasUuids;use Illuminate\Database\Eloquent\Model; class Article extends Model{ use HasUuids; // ...} $article = Article::create(['title' => 'Traveling to Europe']); $article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5"
Por defecto, el rasgo HasUuids
generará "UUIDs "ordenados" para tus modelos. Estos UUIDs son más eficientes para el almacenamiento en bases de datos indexadas porque se pueden ordenar lexicográficamente.
Puedes sobrescribir el proceso de generación de UUID para un modelo dado definiendo un método newUniqueId
en el modelo. Además, puedes especificar qué columnas deben recibir UUIDs definiendo un método uniqueIds
en el modelo:
use Ramsey\Uuid\Uuid; /** * Generate a new UUID for the model. */public function newUniqueId(): string{ return (string) Uuid::uuid4();} /** * Get the columns that should receive a unique identifier. * * @return array<int, string> */public function uniqueIds(): array{ return ['id', 'discount_code'];}
Si lo deseas, puedes optar por utilizar "ULIDs" en lugar de UUIDs. Los ULIDs son similares a los UUIDs; sin embargo, solo tienen 26 caracteres de longitud. Al igual que los UUIDs ordenados, los ULIDs son ordenables lexicográficamente para un indexado eficiente en bases de datos. Para utilizar ULIDs, deberías usar el trait Illuminate\Database\Eloquent\Concerns\HasUlids
en tu modelo. También debes asegurarte de que el modelo tenga una columna de clave primaria equivalente a ULID:
use Illuminate\Database\Eloquent\Concerns\HasUlids;use Illuminate\Database\Eloquent\Model; class Article extends Model{ use HasUlids; // ...} $article = Article::create(['title' => 'Traveling to Asia']); $article->id; // "01gd4d3tgrrfqeda94gdbtdk5c"
Tiempos de marca
Por defecto, Eloquent espera que las columnas created_at
y updated_at
existan en la tabla de base de datos correspondiente a tu modelo. Eloquent establecerá automáticamente los valores de estas columnas cuando se creen o actualicen modelos. Si no deseas que estas columnas sean gestionadas automáticamente por Eloquent, deberías definir una propiedad $timestamps
en tu modelo con un valor de false
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * Indicates if the model should be timestamped. * * @var bool */ public $timestamps = false;}
Si necesitas personalizar el formato de las marcas de tiempo de tu modelo, establece la propiedad $dateFormat
en tu modelo. Esta propiedad determina cómo se almacenan los atributos de fecha en la base de datos, así como su formato cuando el modelo se serializa a un array o JSON:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * The storage format of the model's date columns. * * @var string */ protected $dateFormat = 'U';}
Si necesitas personalizar los nombres de las columnas utilizadas para almacenar las marcas de tiempo, puedes definir constantes CREATED_AT
y UPDATED_AT
en tu modelo:
<?php class Flight extends Model{ const CREATED_AT = 'creation_date'; const UPDATED_AT = 'updated_date';}
Si deseas realizar operaciones en el modelo sin que se modifique su timestamp updated_at
, puedes operar en el modelo dentro de una función anónima dada al método withoutTimestamps
:
Model::withoutTimestamps(fn () => $post->increment('reads'));
Conexiones a la Base de Datos
Por defecto, todos los modelos de Eloquent utilizarán la conexión a la base de datos predeterminada que está configurada para tu aplicación. Si deseas especificar una conexión diferente que se debe usar al interactuar con un modelo en particular, debes definir una propiedad $connection
en el modelo:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * The database connection that should be used by the model. * * @var string */ protected $connection = 'mysql';}
Valores de Atributo Predeterminados
Por defecto, una nueva instancia de modelo instanciada no contendrá ningún valor de atributo. Si deseas definir los valores predeterminados para algunos de los atributos de tu modelo, puedes definir una propiedad $attributes
en tu modelo. Los valores de atributo colocados en el array $attributes
deben estar en su formato "almacenable" en crudo, como si acabaran de ser leídos de la base de datos:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * The model's default values for attributes. * * @var array */ protected $attributes = [ 'options' => '[]', 'delayed' => false, ];}
Configurando la Estrictez de Eloquent
Laravel ofrece varios métodos que te permiten configurar el comportamiento y la "estrictez" de Eloquent en una variedad de situaciones.
Primero, el método preventLazyLoading
acepta un argumento booleano opcional que indica si se debe prevenir la carga perezosa. Por ejemplo, es posible que desees desactivar la carga perezosa solo en entornos no de producción para que tu entorno de producción continúe funcionando con normalidad incluso si una relación cargada perezosamente está presente accidentalmente en el código de producción. Típicamente, este método debe invocarse en el método boot
del AppServiceProvider
de tu aplicación:
use Illuminate\Database\Eloquent\Model; /** * Bootstrap any application services. */public function boot(): void{ Model::preventLazyLoading(! $this->app->isProduction());}
También puedes instruir a Laravel para que lance una excepción al intentar llenar un atributo no rellenable invocando el método preventSilentlyDiscardingAttributes
. Esto puede ayudar a prevenir errores inesperados durante el desarrollo local al intentar establecer un atributo que no ha sido añadido al array fillable
del modelo:
Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
Recuperando Modelos
Una vez que hayas creado un modelo y su tabla de base de datos asociada, estás listo para comenzar a recuperar datos de tu base de datos. Puedes pensar en cada modelo Eloquent como un potente constructor de consultas que te permite consultar de forma fluida la tabla de base de datos asociada con el modelo. El método all
del modelo recuperará todos los registros de la tabla de base de datos asociada al modelo:
use App\Models\Flight; foreach (Flight::all() as $flight) { echo $flight->name;}
Construyendo Consultas
El método all
de Eloquent devolverá todos los resultados en la tabla del modelo. Sin embargo, dado que cada modelo Eloquent actúa como un constructor de consultas, puedes añadir restricciones adicionales a las consultas y luego invocar el método get
para recuperar los resultados:
$flights = Flight::where('active', 1) ->orderBy('name') ->take(10) ->get();
[!NOTA] Dado que los modelos de Eloquent son construcciones de consultas, deberías revisar todos los métodos proporcionados por el constructor de consultas de Laravel. Puedes usar cualquiera de estos métodos al escribir tus consultas Eloquent.
Actualizando Modelos
Si ya tienes una instancia de un modelo Eloquent que fue recuperado de la base de datos, puedes "refrescar" el modelo utilizando los métodos fresh
y refresh
. El método fresh
volverá a recuperar el modelo de la base de datos. La instancia del modelo existente no se verá afectada:
$flight = Flight::where('number', 'FR 900')->first(); $freshFlight = $flight->fresh();
El método refresh
rehidratará el modelo existente utilizando datos frescos de la base de datos. Además, todas sus relaciones cargadas se actualizarán también:
$flight = Flight::where('number', 'FR 900')->first(); $flight->number = 'FR 456'; $flight->refresh(); $flight->number; // "FR 900"
Colecciones
Como hemos visto, métodos de Eloquent como all
y get
recuperan múltiples registros de la base de datos. Sin embargo, estos métodos no devuelven un array PHP sencillo. En su lugar, se devuelve una instancia de Illuminate\Database\Eloquent\Collection
.
La clase Collection
de Eloquent extiende la clase base Illuminate\Support\Collection
de Laravel, que proporciona una variedad de métodos útiles para interactuar con colecciones de datos. Por ejemplo, se puede usar el método reject
para eliminar modelos de una colección en función de los resultados de una función anónima
invocada:
$flights = Flight::where('destination', 'Paris')->get(); $flights = $flights->reject(function (Flight $flight) { return $flight->cancelled;});
Además de los métodos proporcionados por la clase de colección base de Laravel, la clase de colección Eloquent proporciona algunos métodos adicionales que están específicamente diseñados para interactuar con colecciones de modelos Eloquent. Dado que todas las colecciones de Laravel implementan las interfaces iterables de PHP, puedes recorrer las colecciones como si fueran un array:
foreach ($flights as $flight) { echo $flight->name;}
Resultados de Agrupamiento
Tu aplicación puede quedarse sin memoria si intentas cargar decenas de miles de registros Eloquent a través de los métodos all
o get
. En lugar de usar estos métodos, se puede usar el método chunk
para procesar grandes cantidades de modelos de manera más eficiente.
El método chunk
recuperará un subconjunto de modelos Eloquent, pasándolos a una función anónima
para su procesamiento. Dado que solo se recupera el bloque actual de modelos Eloquent a la vez, el método chunk
proporcionará un uso de memoria significativamente reducido al trabajar con una gran cantidad de modelos:
use App\Models\Flight;use Illuminate\Database\Eloquent\Collection; Flight::chunk(200, function (Collection $flights) { foreach ($flights as $flight) { // ... }});
El primer argumento pasado al método chunk
es el número de registros que deseas recibir por "chunk". La función anónima pasada como segundo argumento se invocará para cada chunk que se recupere de la base de datos. Se ejecutará una consulta a la base de datos para recuperar cada chunk de registros pasado a la función anónima.
Si estás filtrando los resultados del método chunk
en función de una columna que también estarás actualizando mientras iteras sobre los resultados, deberías usar el método chunkById
. Usar el método chunk
en estos escenarios podría llevar a resultados inesperados e inconsistentes. Internamente, el método chunkById
siempre recuperará modelos con una columna id
mayor que el último modelo del bloque anterior:
Flight::where('departed', true) ->chunkById(200, function (Collection $flights) { $flights->each->update(['departed' => false]); }, $column = 'id');
Fragmentación utilizando colecciones perezosas
El método lazy
funciona de manera similar al método chunk en el sentido de que, detrás de escena, ejecuta la consulta en fragmentos. Sin embargo, en lugar de pasar cada fragmento directamente a un callback como está, el método lazy
devuelve una LazyCollection
aplanada de modelos Eloquent, lo que te permite interactuar con los resultados como si fuera un solo flujo:
use App\Models\Flight; foreach (Flight::lazy() as $flight) { // ...}
Si estás filtrando los resultados del método lazy
según una columna que también estarás actualizando mientras iteras sobre los resultados, deberías usar el método lazyById
. Internamente, el método lazyById
siempre recuperará modelos con una columna id
mayor que el último modelo en el bloque anterior:
Flight::where('departed', true) ->lazyById(200, $column = 'id') ->each->update(['departed' => false]);
Puedes filtrar los resultados en función del orden descendente del id
utilizando el método lazyByIdDesc
.
Cursores
Similar al método lazy
, el método cursor
se puede utilizar para reducir significativamente el consumo de memoria de tu aplicación al iterar a través de decenas de miles de registros de modelos Eloquent.
El método cursor
solo ejecutará una sola consulta a la base de datos; sin embargo, los modelos Eloquent individuales no se hidratarán hasta que se iteren realmente. Por lo tanto, solo se mantiene un modelo Eloquent en memoria en cualquier momento mientras se itera sobre el cursor.
[!WARNING] Dado que el método
cursor
solo mantiene un solo modelo Eloquent en memoria a la vez, no puede cargar relaciones de manera ansiosa. Si necesitas cargar relaciones de manera ansiosa, considera usar el métodolazy
en su lugar. Internamente, el métodocursor
utiliza generadores de PHP para implementar esta funcionalidad:
use App\Models\Flight; foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) { // ...}
El cursor
devuelve una instancia de Illuminate\Support\LazyCollection
. Las colecciones perezosas te permiten usar muchos de los métodos de colección disponibles en las colecciones típicas de Laravel mientras solo cargas un modelo en memoria a la vez:
use App\Models\User; $users = User::cursor()->filter(function (User $user) { return $user->id > 500;}); foreach ($users as $user) { echo $user->id;}
Aunque el método cursor
utiliza mucha menos memoria que una consulta regular (al mantener solo un solo modelo Eloquent en memoria a la vez), aún eventualmente se quedará sin memoria. Esto se debe a que el driver PDO de PHP almacena en caché internamente todos los resultados de la consulta en su búfer. Si estás manejando un número muy grande de registros Eloquent, considera usar en su lugar el método lazy
.
Subconsultas Avanzadas
Selecciones de Subconsulta
Eloquent también ofrece soporte avanzado para subconsultas, lo que te permite obtener información de tablas relacionadas en una sola consulta. Por ejemplo, imaginemos que tenemos una tabla de destinos
de vuelo y una tabla de vuelos
a los destinos. La tabla vuelos
contiene una columna arrived_at
que indica cuándo llegó el vuelo al destino.
Utilizando la funcionalidad de subconsultas disponible en los métodos select
y addSelect
del generador de consultas, podemos seleccionar todos los destinos
y el nombre del vuelo que llegó más recientemente a ese destino utilizando una sola consulta:
use App\Models\Destination;use App\Models\Flight; return Destination::addSelect(['last_flight' => Flight::select('name') ->whereColumn('destination_id', 'destinations.id') ->orderByDesc('arrived_at') ->limit(1)])->get();
Ordenamiento de Subconsultas
Además, la función orderBy
del generador de consultas admite subconsultas. Continuando con nuestro ejemplo de vuelo, podemos usar esta funcionalidad para ordenar todos los destinos según cuándo llegó el último vuelo a ese destino. Nuevamente, esto se puede hacer mientras se ejecuta una sola consulta a la base de datos:
return Destination::orderByDesc( Flight::select('arrived_at') ->whereColumn('destination_id', 'destinations.id') ->orderByDesc('arrived_at') ->limit(1))->get();
Recuperando Modelos / Agregados Individuales
Además de recuperar todos los registros que coinciden con una consulta dada, también puedes recuperar registros individuales utilizando los métodos find
, first
o firstWhere
. En lugar de devolver una colección de modelos, estos métodos devuelven una sola instancia del modelo:
use App\Models\Flight; // Retrieve a model by its primary key...$flight = Flight::find(1); // Retrieve the first model matching the query constraints...$flight = Flight::where('active', 1)->first(); // Alternative to retrieving the first model matching the query constraints...$flight = Flight::firstWhere('active', 1);
A veces es posible que desees realizar alguna otra acción si no se encuentran resultados. Los métodos findOr
y firstOr
devolverán una sola instancia de modelo o, si no se encuentran resultados, ejecutarán la función anónima
dada. El valor devuelto por la función anónima
se considerará el resultado del método:
$flight = Flight::findOr(1, function () { // ...}); $flight = Flight::where('legs', '>', 3)->firstOr(function () { // ...});
Excepciones No Encontradas
A veces es posible que desees lanzar una excepción si no se encuentra un modelo. Esto es particularmente útil en rutas o controladores. Los métodos findOrFail
y firstOrFail
recuperarán el primer resultado de la consulta; sin embargo, si no se encuentra ningún resultado, se lanzará una Illuminate\Database\Eloquent\ModelNotFoundException
:
$flight = Flight::findOrFail(1); $flight = Flight::where('legs', '>', 3)->firstOrFail();
Si la ModelNotFoundException
no se captura, se envía automáticamente una respuesta HTTP 404 de vuelta al cliente:
use App\Models\Flight; Route::get('/api/flights/{id}', function (string $id) { return Flight::findOrFail($id);});
Recuperando o Creando Modelos
El método firstOrCreate
intentará localizar un registro de base de datos utilizando los pares de columna / valor dados. Si el modelo no puede ser encontrado en la base de datos, se insertará un registro con los atributos resultantes de fusionar el primer argumento de array con el segundo argumento de array opcional:
El método firstOrNew
, al igual que firstOrCreate
, intentará localizar un registro en la base de datos que coincida con los atributos dados. Sin embargo, si no se encuentra un modelo, se devolverá una nueva instancia del modelo. Ten en cuenta que el modelo devuelto por firstOrNew
aún no ha sido persistido en la base de datos. Necesitarás llamar manualmente al método save
para persistirlo:
use App\Models\Flight; // Retrieve flight by name or create it if it doesn't exist...$flight = Flight::firstOrCreate([ 'name' => 'London to Paris']); // Retrieve flight by name or create it with the name, delayed, and arrival_time attributes...$flight = Flight::firstOrCreate( ['name' => 'London to Paris'], ['delayed' => 1, 'arrival_time' => '11:30']); // Retrieve flight by name or instantiate a new Flight instance...$flight = Flight::firstOrNew([ 'name' => 'London to Paris']); // Retrieve flight by name or instantiate with the name, delayed, and arrival_time attributes...$flight = Flight::firstOrNew( ['name' => 'Tokyo to Sydney'], ['delayed' => 1, 'arrival_time' => '11:30']);
Recuperando Agregados
Al interactuar con modelos Eloquent, también puedes usar los métodos de agregación como count
, sum
, max
y otros métodos de agregación proporcionados por el constructor de consultas de Laravel. Como puedes esperar, estos métodos devuelven un valor escalar en lugar de una instancia de modelo Eloquent:
$count = Flight::where('active', 1)->count(); $max = Flight::where('active', 1)->max('price');
Insertando y Actualizando Modelos
Inserciones
Por supuesto, al usar Eloquent, no solo necesitamos recuperar modelos de la base de datos. También necesitamos insertar nuevos registros. Afortunadamente, Eloquent lo hace simple. Para insertar un nuevo registro en la base de datos, debes instanciar una nueva instancia del modelo y establecer atributos en el modelo. Luego, llama al método save
en la instancia del modelo:
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller;use App\Models\Flight;use Illuminate\Http\RedirectResponse;use Illuminate\Http\Request; class FlightController extends Controller{ /** * Store a new flight in the database. */ public function store(Request $request): RedirectResponse { // Validate the request... $flight = new Flight; $flight->name = $request->name; $flight->save(); return redirect('/flights'); }}
En este ejemplo, asignamos el campo name
de la solicitud HTTP entrante al atributo name
de la instancia del modelo App\Models\Flight
. Cuando llamamos al método save
, se insertará un registro en la base de datos. Las marcas de tiempo created_at
y updated_at
del modelo se establecerán automáticamente cuando se llame al método save
, por lo que no es necesario configurarlas manualmente.
Alternativamente, puedes usar el método create
para "guardar" un nuevo modelo utilizando una sola declaración PHP. La instancia del modelo insertado te será devuelta por el método create
:
Sin embargo, antes de usar el método create
, necesitarás especificar una propiedad fillable
o guarded
en tu clase de modelo. Estas propiedades son necesarias porque todos los modelos de Eloquent están protegidos contra vulnerabilidades de asignación masiva de forma predeterminada. Para aprender más sobre la asignación masiva, consulta la documentación sobre asignación masiva.
Actualizaciones
El método save
también se puede utilizar para actualizar modelos que ya existen en la base de datos. Para actualizar un modelo, debes recuperarlo y configurar cualquier atributo que desees actualizar. Luego, debes llamar al método save
del modelo. Nuevamente, la marca de tiempo updated_at
se actualizará automáticamente, por lo que no es necesario establecer su valor manualmente:
use App\Models\Flight; $flight = Flight::find(1); $flight->name = 'Paris to London'; $flight->save();
Ocasionalmente, es posible que necesites actualizar un modelo existente o crear un nuevo modelo si no existe un modelo que coincida. Al igual que el método firstOrCreate
, el método updateOrCreate
persiste el modelo, por lo que no es necesario llamar manualmente al método save
.
En el ejemplo a continuación, si existe un vuelo con una ubicación de salida
de Oakland
y una ubicación de destino
de San Diego
, se actualizarán sus columnas precio
y descuento
. Si no existe tal vuelo, se creará un nuevo vuelo que tenga los atributos resultantes de fusionar el array del primer argumento con el array del segundo argumento:
$flight = Flight::updateOrCreate( ['departure' => 'Oakland', 'destination' => 'San Diego'], ['price' => 99, 'discounted' => 1]);
Actualizaciones Masivas
Las actualizaciones también se pueden realizar en modelos que coinciden con una consulta dada. En este ejemplo, todos los vuelos que están activos
y tienen un destino
de San Diego
se marcarán como retrasados:
Flight::where('active', 1) ->where('destination', 'San Diego') ->update(['delayed' => 1]);
El método update
espera un array de pares de columna y valor que representan las columnas que deben ser actualizadas. El método update
devuelve el número de filas afectadas.
[!WARNING] Al realizar una actualización masiva a través de Eloquent, los eventos del modelo
saving
,saved
,updating
yupdated
no se activarán para los modelos actualizados. Esto se debe a que los modelos nunca son realmente recuperados al realizar una actualización masiva.
Examinando Cambios en Atributos
Eloquent proporciona los métodos isDirty
, isClean
y wasChanged
para examinar el estado interno de tu modelo y determinar cómo han cambiado sus atributos desde que se recuperó originalmente el modelo.
El método isDirty
determina si alguno de los atributos del modelo ha sido cambiado desde que se recuperó el modelo. Puedes pasar un nombre de atributo específico o un array de atributos al método isDirty
para determinar si alguno de los atributos está "sucio". El método isClean
determinará si un atributo ha permanecido sin cambios desde que se recuperó el modelo. Este método también acepta un argumento de atributo opcional:
use App\Models\User; $user = User::create([ 'first_name' => 'Taylor', 'last_name' => 'Otwell', 'title' => 'Developer',]); $user->title = 'Painter'; $user->isDirty(); // true$user->isDirty('title'); // true$user->isDirty('first_name'); // false$user->isDirty(['first_name', 'title']); // true $user->isClean(); // false$user->isClean('title'); // false$user->isClean('first_name'); // true$user->isClean(['first_name', 'title']); // false $user->save(); $user->isDirty(); // false$user->isClean(); // true
El método wasChanged
determina si se cambiaron atributos cuando el modelo se guardó por última vez dentro del ciclo de solicitud actual. Si es necesario, puedes pasar un nombre de atributo para ver si se cambió un atributo en particular:
$user = User::create([ 'first_name' => 'Taylor', 'last_name' => 'Otwell', 'title' => 'Developer',]); $user->title = 'Painter'; $user->save(); $user->wasChanged(); // true$user->wasChanged('title'); // true$user->wasChanged(['title', 'slug']); // true$user->wasChanged('first_name'); // false$user->wasChanged(['first_name', 'title']); // true
El método getOriginal
devuelve un array que contiene los atributos originales del modelo sin importar cualquier cambio en el modelo desde que fue recuperado. Si es necesario, puedes pasar un nombre de atributo específico para obtener el valor original de un atributo particular:
$user = User::find(1); $user->name; // John$user->email; // john@example.com $user->name = "Jack";$user->name; // Jack $user->getOriginal('name'); // John$user->getOriginal(); // Array of original attributes...
Asignación Masiva
Puedes usar el método create
para "guardar" un nuevo modelo utilizando una sola declaración PHP. La instancia del modelo insertada te será devuelta por el método:
use App\Models\Flight; $flight = Flight::create([ 'name' => 'London to Paris',]);
Sin embargo, antes de usar el método create
, necesitarás especificar una propiedad fillable
o guarded
en tu clase de modelo. Estas propiedades son necesarias porque todos los modelos Eloquent están protegidos contra vulnerabilidades de asignación masiva por defecto.
Una vulnerabilidad de asignación masiva ocurre cuando un usuario envía un campo de solicitud HTTP inesperado y ese campo cambia una columna en tu base de datos que no esperabas. Por ejemplo, un usuario malintencionado podría enviar un parámetro is_admin
a través de una solicitud HTTP, que luego se pasa al método create
de tu modelo, permitiendo al usuario elevarse a administrador.
Entonces, para comenzar, deberías definir qué atributos del modelo deseas hacer asignables en masa. Puedes hacer esto utilizando la propiedad $fillable
en el modelo. Por ejemplo, hagamos que el atributo name
de nuestro modelo Flight
sea asignable en masa:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * The attributes that are mass assignable. * * @var array */ protected $fillable = ['name'];}
Una vez que hayas especificado qué atributos son asignables en masa, puedes usar el método create
para insertar un nuevo registro en la base de datos. El método create
devuelve la instancia del modelo que se ha creado recientemente:
$flight = Flight::create(['name' => 'London to Paris']);
Si ya tienes una instancia del modelo, puedes usar el método fill
para poblarla con un array de atributos:
$flight->fill(['name' => 'Amsterdam to Frankfurt']);
Asignación Masiva y Columnas JSON
Al asignar columnas JSON, la clave asignable en masa de cada columna debe especificarse en el array $fillable
de tu modelo. Por razones de seguridad, Laravel no admite la actualización de atributos JSON anidados al usar la propiedad guarded
:
/** * The attributes that are mass assignable. * * @var array */protected $fillable = [ 'options->enabled',];
Permitiendo la Asignación Masiva
Si deseas que todos tus atributos sean mass assignable, puedes definir la propiedad $guarded
de tu modelo como un array vacío. Si decides desproteger tu modelo, debes tener cuidado de siempre elaborar manualmente los arrays pasados a los métodos fill
, create
y update
de Eloquent:
/** * The attributes that aren't mass assignable. * * @var array */protected $guarded = [];
Excepciones de Asignación Masiva
Por defecto, los atributos que no están incluidos en el array $fillable
se descartan silenciosamente al realizar operaciones de asignación masiva. En producción, este es un comportamiento esperado; sin embargo, durante el desarrollo local puede llevar a confusiones sobre por qué los cambios en el modelo no tienen efecto.
Si lo deseas, puedes instruir a Laravel para que lance una excepción cuando intente llenar un atributo no rellenable invocando el método preventSilentlyDiscardingAttributes
. Típicamente, este método debe invocarse en el método boot
de la clase AppServiceProvider
de tu aplicación:
use Illuminate\Database\Eloquent\Model; /** * Bootstrap any application services. */public function boot(): void{ Model::preventSilentlyDiscardingAttributes($this->app->isLocal());}
Upserts
El método upsert
de Eloquent se puede utilizar para actualizar o crear registros en una sola operación atómica. El primer argumento del método consiste en los valores a insertar o actualizar, mientras que el segundo argumento enumera las columnas que identifican de manera única los registros dentro de la tabla asociada. El tercer y último argumento del método es un array de las columnas que deben actualizarse si ya existe un registro coincidente en la base de datos. El método upsert
establecerá automáticamente las marcas de tiempo created_at
y updated_at
si las marcas de tiempo están habilitadas en el modelo:
Flight::upsert([ ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]], uniqueBy: ['departure', 'destination'], update: ['price']);
[!WARNING] Todas las bases de datos excepto SQL Server requieren que las columnas en el segundo argumento del método
upsert
tengan un índice "primario" o "único". Además, los controladores de bases de datos MariaDB y MySQL ignoran el segundo argumento del métodoupsert
y siempre utilizan los índices "primarios" y "únicos" de la tabla para detectar registros existentes.
Eliminar Modelos
Para eliminar un modelo, puedes llamar al método delete
en la instancia del modelo:
use App\Models\Flight; $flight = Flight::find(1); $flight->delete();
Puedes llamar al método truncate
para eliminar todos los registros de base de datos asociados al modelo. La operación truncate
también restablecerá cualquier ID de autoincremento en la tabla asociada del modelo:
Flight::truncate();
Eliminando un Modelo Existente por su Clave Primaria
En el ejemplo anterior, estamos recuperando el modelo de la base de datos antes de llamar al método delete
. Sin embargo, si conoces la clave primaria del modelo, puedes eliminar el modelo sin recuperarlo explícitamente llamando al método destroy
. Además de aceptar la única clave primaria, el método destroy
aceptará múltiples claves primarias, un array de claves primarias o una colección de claves primarias:
Flight::destroy(1); Flight::destroy(1, 2, 3); Flight::destroy([1, 2, 3]); Flight::destroy(collect([1, 2, 3]));
Si estás utilizando eliminación suave de modelos, puedes eliminar modelos de manera permanente a través del método forceDestroy
:
Flight::forceDestroy(1);
[!WARNING] El método
destroy
carga cada modelo individualmente y llama al métododelete
para que los eventosdeleting
ydeleted
se despachen correctamente para cada modelo.
Eliminando Modelos Usando Consultas
Por supuesto, puedes construir una consulta Eloquent para eliminar todos los modelos que coincidan con los criterios de tu consulta. En este ejemplo, eliminaremos todos los vuelos que están marcados como inactivos. Al igual que las actualizaciones masivas, las eliminaciones masivas no despacharán eventos del modelo para los modelos que son eliminados:
$deleted = Flight::where('active', 0)->delete();
[!WARNING] Al ejecutar una declaración de eliminación masiva a través de Eloquent, los eventos de modelo
deleting
ydeleted
no se despacharán para los modelos eliminados. Esto se debe a que los modelos nunca son realmente recuperados al ejecutar la declaración de eliminación.
Eliminación Suave
Además de eliminar registros de tu base de datos, Eloquent también puede "eliminar suavemente" modelos. Cuando los modelos son eliminados suavemente, en realidad no son eliminados de tu base de datos. En su lugar, se establece un atributo deleted_at
en el modelo indicando la fecha y hora en la que el modelo fue "eliminado". Para habilitar eliminaciones suaves para un modelo, añade el trait Illuminate\Database\Eloquent\SoftDeletes
al modelo:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\SoftDeletes; class Flight extends Model{ use SoftDeletes;}
[!NOTA] El trait
SoftDeletes
convertirá automáticamente el atributodeleted_at
a una instancia deDateTime
/Carbon
para ti. También deberías añadir la columnadeleted_at
a tu tabla de base de datos. El schema builder de Laravel contiene un método auxiliar para crear esta columna:
use Illuminate\Database\Schema\Blueprint;use Illuminate\Support\Facades\Schema; Schema::table('flights', function (Blueprint $table) { $table->softDeletes();}); Schema::table('flights', function (Blueprint $table) { $table->dropSoftDeletes();});
Ahora, cuando llames al método delete
en el modelo, la columna deleted_at
se establecerá en la fecha y hora actuales. Sin embargo, el registro de la base de datos del modelo se dejará en la tabla. Al consultar un modelo que utiliza eliminaciones suaves, los modelos eliminados suavemente se excluirán automáticamente de todos los resultados de consulta.
Para determinar si una instancia de modelo dada ha sido eliminada suavemente, puedes usar el método trashed
:
if ($flight->trashed()) { // ...}
Restaurando Modelos Suavemente Eliminados
A veces es posible que desees "cancelar la eliminación" de un modelo eliminado de forma suave. Para restaurar un modelo eliminado de forma suave, puedes llamar al método restore
en una instancia del modelo. El método restore
establecerá la columna deleted_at
del modelo en null
:
$flight->restore();
También puedes usar el método restore
en una consulta para restaurar múltiples modelos. Al igual que otras operaciones "masivas", esto no despachará ningún evento de modelo para los modelos que son restaurados:
Flight::withTrashed() ->where('airline_id', 1) ->restore();
El método restore
también puede utilizarse al construir consultas de relación:
$flight->history()->restore();
Eliminando Modelos Permanentemente
A veces es posible que necesites eliminar realmente un modelo de tu base de datos. Puedes usar el método forceDelete
para eliminar permanentemente un modelo eliminado suavemente de la tabla de la base de datos:
$flight->forceDelete();
También puedes usar el método forceDelete
al construir consultas de relaciones Eloquent:
$flight->history()->forceDelete();
Consultando Modelos Suavemente Eliminados
Incluyendo Modelos Soft Deleted
Como se señaló anteriormente, los modelos eliminados suavemente se excluirán automáticamente de los resultados de la consulta. Sin embargo, puedes forzar la inclusión de modelos eliminados suavemente en los resultados de una consulta llamando al método withTrashed
en la consulta:
use App\Models\Flight; $flights = Flight::withTrashed() ->where('account_id', 1) ->get();
El método withTrashed
también se puede llamar al construir una consulta de relación:
$flight->history()->withTrashed()->get();
Recuperando Solo Modelos Suaves Eliminados
El método onlyTrashed
recuperará solo los modelos eliminados suavemente:
$flights = Flight::onlyTrashed() ->where('airline_id', 1) ->get();
Poda de Modelos
A veces es posible que desees eliminar periódicamente modelos que ya no son necesarios. Para lograr esto, puedes agregar el trait Illuminate\Database\Eloquent\Prunable
o Illuminate\Database\Eloquent\MassPrunable
a los modelos que te gustaría depurar periódicamente. Después de agregar uno de los traits al modelo, implementa un método prunable
que devuelva un constructor de consultas Eloquent que resuelva los modelos que ya no son necesarios:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Prunable; class Flight extends Model{ use Prunable; /** * Get the prunable model query. */ public function prunable(): Builder { return static::where('created_at', '<=', now()->subMonth()); }}
Al marcar modelos como Prunable
, también puedes definir un método pruning
en el modelo. Este método se llamará antes de que se elimine el modelo. Este método puede ser útil para eliminar cualquier recurso adicional asociado con el modelo, como archivos almacenados, antes de que el modelo sea eliminado permanentemente de la base de datos:
/** * Prepare the model for pruning. */protected function pruning(): void{ // ...}
Después de configurar tu modelo desechable, debes programar el comando Artisan model:prune
en el archivo routes/console.php
de tu aplicación. Tienes la libertad de elegir el intervalo apropiado en el que se debe ejecutar este comando:
use Illuminate\Support\Facades\Schedule; Schedule::command('model:prune')->daily();
Detrás de escena, el comando model:prune
detectará automáticamente los modelos "Ppodables" dentro del directorio app/Models
de tu aplicación. Si tus modelos están en una ubicación diferente, puedes usar la opción --model
para especificar los nombres de las clases de modelo:
Schedule::command('model:prune', [ '--model' => [Address::class, Flight::class],])->daily();
Si deseas excluir ciertos modelos de ser eliminados mientras se eliminan todos los demás modelos detectados, puedes usar la opción --except
:
Schedule::command('model:prune', [ '--except' => [Address::class, Flight::class],])->daily();
Puedes probar tu consulta prunable
ejecutando el comando model:prune
con la opción --pretend
. Al simular, el comando model:prune
simplemente informará cuántos registros se eliminarían si el comando se ejecutara realmente:
php artisan model:prune --pretend
[!WARNING] Los modelos eliminados suavemente serán eliminados permanentemente (
forceDelete
) si coinciden con la consulta prunable.
Poda en Masa
Cuando los modelos están marcados con el trait Illuminate\Database\Eloquent\MassPrunable
, los modelos se eliminan de la base de datos utilizando consultas de eliminación masiva. Por lo tanto, el método pruning
no se invocará, ni se despacharán los eventos del modelo deleting
y deleted
. Esto se debe a que los modelos nunca se recuperan realmente antes de la eliminación, lo que hace que el proceso de poda sea mucho más eficiente:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\MassPrunable; class Flight extends Model{ use MassPrunable; /** * Get the prunable model query. */ public function prunable(): Builder { return static::where('created_at', '<=', now()->subMonth()); }}
Replicando Modelos
Puedes crear una copia no guardada de una instancia de modelo existente utilizando el método replicate
. Este método es particularmente útil cuando tienes instancias de modelo que comparten muchos de los mismos atributos:
use App\Models\Address; $shipping = Address::create([ 'type' => 'shipping', 'line_1' => '123 Example Street', 'city' => 'Victorville', 'state' => 'CA', 'postcode' => '90001',]); $billing = $shipping->replicate()->fill([ 'type' => 'billing']); $billing->save();
Para excluir uno o más atributos de ser replicados al nuevo modelo, puedes pasar un array al método replicate
:
$flight = Flight::create([ 'destination' => 'LAX', 'origin' => 'LHR', 'last_flown' => '2020-03-04 11:00:00', 'last_pilot_id' => 747,]); $flight = $flight->replicate([ 'last_flown', 'last_pilot_id']);
Ámbitos de Consulta
Alcances Globales
Los alcances globales te permiten añadir restricciones a todas las consultas para un modelo dado. La propia funcionalidad de eliminación suave de Laravel utiliza alcances globales para solo recuperar modelos "no eliminados" de la base de datos. Escribir tus propios alcances globales puede proporcionar una manera conveniente y fácil de asegurarte de que cada consulta para un modelo dado reciba ciertas restricciones.
Generando Scopes
Para generar un nuevo scope global, puedes invocar el comando Artisan make:scope
, que colocará el scope generado en el directorio app/Models/Scopes
de tu aplicación:
php artisan make:scope AncientScope
Escribiendo Alcances Globales
Escribir un alcance global es simple. Primero, utiliza el comando make:scope
para generar una clase que implemente la interfaz Illuminate\Database\Eloquent\Scope
. La interfaz Scope
requiere que implementes un método: apply
. El método apply
puede añadir restricciones where
u otros tipos de cláusulas a la consulta según sea necesario:
<?php namespace App\Models\Scopes; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Scope; class AncientScope implements Scope{ /** * Apply the scope to a given Eloquent query builder. */ public function apply(Builder $builder, Model $model): void { $builder->where('created_at', '<', now()->subYears(2000)); }}
[!NOTA] Si tu alcance global está añadiendo columnas a la cláusula select de la consulta, debes usar el método
addSelect
en lugar deselect
. Esto evitará el reemplazo no intencionado de la cláusula select existente de la consulta.
Aplicando Alcances Globales
Para asignar un alcance global a un modelo, simplemente puedes colocar el atributo ScopedBy
en el modelo:
<?php namespace App\Models; use App\Models\Scopes\AncientScope;use Illuminate\Database\Eloquent\Attributes\ScopedBy; #[ScopedBy([AncientScope::class])]class User extends Model{ //}
O bien, puedes registrar el alcance global manualmente sobreescribiendo el método booted
del modelo e invocando el método addGlobalScope
del modelo. El método addGlobalScope
acepta una instancia de tu alcance como su único argumento:
<?php namespace App\Models; use App\Models\Scopes\AncientScope;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * The "booted" method of the model. */ protected static function booted(): void { static::addGlobalScope(new AncientScope); }}
Después de añadir el alcance en el ejemplo anterior al modelo App\Models\User
, una llamada al método User::all()
ejecutará la siguiente consulta SQL:
select * from `users` where `created_at` < 0021-02-18 00:00:00
Alcances Globales Anónimos
Eloquent también te permite definir alcances globales utilizando funciones anónimas, lo que es particularmente útil para alcances simples que no justifican una clase separada. Al definir un alcance global utilizando una función anónima, debes proporcionar un nombre de alcance de tu elección como el primer argumento al método addGlobalScope
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * The "booted" method of the model. */ protected static function booted(): void { static::addGlobalScope('ancient', function (Builder $builder) { $builder->where('created_at', '<', now()->subYears(2000)); }); }}
Eliminando Alcances Globales
Si deseas eliminar un alcance global para una consulta dada, puedes usar el método withoutGlobalScope
. Este método acepta el nombre de la clase del alcance global como su único argumento:
User::withoutGlobalScope(AncientScope::class)->get();
O, si definiste el alcance global utilizando una función anónima, deberías pasar el nombre de cadena que asignaste al alcance global:
User::withoutGlobalScope('ancient')->get();
Si deseas eliminar varios o incluso todos los scopes globales de la consulta, puedes usar el método withoutGlobalScopes
:
// Remove all of the global scopes...User::withoutGlobalScopes()->get(); // Remove some of the global scopes...User::withoutGlobalScopes([ FirstScope::class, SecondScope::class])->get();
Alcances Locales
Los ámbitos locales te permiten definir conjuntos comunes de restricciones de consulta que puedes reutilizar fácilmente en toda tu aplicación. Por ejemplo, es posible que necesites recuperar con frecuencia todos los usuarios que se consideran "populares". Para definir un ámbito, prefija un método del modelo Eloquent con scope
.
Los scopes siempre deben devolver la misma instancia del constructor de consultas o void
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * Scope a query to only include popular users. */ public function scopePopular(Builder $query): void { $query->where('votes', '>', 100); } /** * Scope a query to only include active users. */ public function scopeActive(Builder $query): void { $query->where('active', 1); }}
Utilizando un Alcance Local
Una vez que se ha definido el alcance, puedes llamar a los métodos del alcance al consultar el modelo. Sin embargo, no debes incluir el prefijo scope
al llamar al método. Incluso puedes encadenar llamadas a varios alcances:
use App\Models\User; $users = User::popular()->active()->orderBy('created_at')->get();
Combinar múltiples alcances de modelos Eloquent mediante un operador de consulta or
puede requerir el uso de funciones anónimas
para lograr el agrupamiento lógico correcto:
$users = User::popular()->orWhere(function (Builder $query) { $query->active();})->get();
Sin embargo, como esto puede ser engorroso, Laravel ofrece un método orWhere
de "orden superior" que te permite encadenar fluida y fácilmente los scopes sin el uso de funciones anónimas:
$users = User::popular()->orWhere->active()->get();
Alcances Dinámicos
A veces es posible que desees definir un alcance que acepte parámetros. Para empezar, solo añade tus parámetros adicionales a la firma del método de alcance. Los parámetros del alcance deben definirse después del parámetro $query
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * Scope a query to only include users of a given type. */ public function scopeOfType(Builder $query, string $type): void { $query->where('type', $type); }}
Una vez que los argumentos esperados se hayan añadido a la firma del método scope, puedes pasar los argumentos al llamar al scope:
$users = User::ofType('admin')->get();
Comparando Modelos
A veces es posible que necesites determinar si dos modelos son "iguales" o no. Los métodos is
y isNot
se pueden utilizar para verificar rápidamente si dos modelos tienen la misma clave primaria, tabla y conexión a la base de datos o no:
if ($post->is($anotherPost)) { // ...} if ($post->isNot($anotherPost)) { // ...}
Los métodos is
e isNot
también están disponibles al usar las relaciones belongsTo
, hasOne
, morphTo
y morphOne
relaciones. Este método es particularmente útil cuando deseas comparar un modelo relacionado sin emitir una consulta para recuperar ese modelo:
if ($post->author()->is($user)) { // ...}
Eventos
[!NOTA] ¿Quieres transmitir tus eventos de Eloquent directamente a tu aplicación del lado del cliente? Consulta la difusión de eventos de modelo de Laravel. Los modelos Eloquent despachan varios eventos, lo que te permite engancharte en los siguientes momentos del ciclo de vida de un modelo:
retrieved
,creating
,created
,updating
,updated
,saving
,saved
,deleting
,deleted
,trashed
,forceDeleting
,forceDeleted
,restoring
,restored
yreplicating
. El eventoretrieved
se despachará cuando se recupere un modelo existente de la base de datos. Cuando se guarda un nuevo modelo por primera vez, se despacharán los eventoscreating
ycreated
. Los eventosupdating
/updated
se despacharán cuando se modifique un modelo existente y se llame al métodosave
. Los eventossaving
/saved
se despacharán cuando se cree o se actualice un modelo, incluso si los atributos del modelo no han cambiado. Los nombres de los eventos que terminan en-ing
se despachan antes de que se persistan los cambios en el modelo, mientras que los eventos que terminan en-ed
se despachan después de que se persistan los cambios en el modelo. Para comenzar a escuchar los eventos del modelo, define una propiedad$dispatchesEvents
en tu modelo Eloquent. Esta propiedad mapea varios puntos del ciclo de vida del modelo Eloquent a tus propias clases de evento. Cada clase de evento del modelo debe esperar recibir una instancia del modelo afectado a través de su constructor:
<?php namespace App\Models; use App\Events\UserDeleted;use App\Events\UserSaved;use Illuminate\Foundation\Auth\User as Authenticatable;use Illuminate\Notifications\Notifiable; class User extends Authenticatable{ use Notifiable; /** * The event map for the model. * * @var array<string, string> */ protected $dispatchesEvents = [ 'saved' => UserSaved::class, 'deleted' => UserDeleted::class, ];}
Después de definir y mapear tus eventos de Eloquent, puedes usar escuchas de eventos para manejar los eventos.
[!WARNING] Al emitir una consulta de actualización o eliminación masiva a través de Eloquent, los eventos del modelo
saved
,updated
,deleting
ydeleted
no se dispararán para los modelos afectados. Esto se debe a que los modelos nunca se recuperan realmente al realizar actualizaciones o eliminaciones masivas.
Usando Funciones Anónimas
En lugar de utilizar clases de eventos personalizadas, puedes registrar funciones anónimas
que se ejecuten cuando se despachan varios eventos de modelo. Típicamente, debes registrar estas funciones anónimas
en el método booted
de tu modelo:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * The "booted" method of the model. */ protected static function booted(): void { static::created(function (User $user) { // ... }); }}
Si es necesario, puedes utilizar escuchadores de eventos anónimos en cola al registrar eventos del modelo. Esto indicará a Laravel que ejecute el escuchador de eventos del modelo en segundo plano utilizando la cola de tu aplicación:
use function Illuminate\Events\queueable; static::created(queueable(function (User $user) { // ...}));
Observadores
Definiendo Observadores
Si estás escuchando muchos eventos en un modelo dado, puedes usar observadores para agrupar todos tus oyentes en una sola clase. Las clases de observadores tienen nombres de métodos que reflejan los eventos de Eloquent a los que deseas escuchar. Cada uno de estos métodos recibe el modelo afectado como su único argumento. El comando Artisan make:observer
es la forma más fácil de crear una nueva clase de observador:
php artisan make:observer UserObserver --model=User
Este comando colocará el nuevo observador en tu directorio app/Observers
. Si este directorio no existe, Artisan lo creará por ti. Tu nuevo observador se verá como el siguiente:
<?php namespace App\Observers; use App\Models\User; class UserObserver{ /** * Handle the User "created" event. */ public function created(User $user): void { // ... } /** * Handle the User "updated" event. */ public function updated(User $user): void { // ... } /** * Handle the User "deleted" event. */ public function deleted(User $user): void { // ... } /** * Handle the User "restored" event. */ public function restored(User $user): void { // ... } /** * Handle the User "forceDeleted" event. */ public function forceDeleted(User $user): void { // ... }}
Para registrar un observador, puedes colocar el atributo ObservedBy
en el modelo correspondiente:
use App\Observers\UserObserver;use Illuminate\Database\Eloquent\Attributes\ObservedBy; #[ObservedBy([UserObserver::class])]class User extends Authenticatable{ //}
O bien, puedes registrar un observador manualmente invocando el método observe
en el modelo que deseas observar. Puedes registrar observadores en el método boot
de la clase AppServiceProvider
de tu aplicación:
use App\Models\User;use App\Observers\UserObserver; /** * Bootstrap any application services. */public function boot(): void{ User::observe(UserObserver::class);}
[!NOTA] Hay eventos adicionales a los que un observador puede escuchar, como
saving
yretrieved
. Estos eventos se describen en la documentación de events.
Observadores y Transacciones de Base de Datos
Cuando se están creando modelos dentro de una transacción de base de datos, es posible que desees instruir a un observador para que ejecute sus controladores de eventos solo después de que se confirme la transacción de base de datos. Puedes lograr esto implementando la interfaz ShouldHandleEventsAfterCommit
en tu observador. Si no hay una transacción de base de datos en progreso, los controladores de eventos se ejecutarán de inmediato:
<?php namespace App\Observers; use App\Models\User;use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit; class UserObserver implements ShouldHandleEventsAfterCommit{ /** * Handle the User "created" event. */ public function created(User $user): void { // ... }}
Silenciar Eventos
Puede que ocasionalmente necesites "silenciar" temporalmente todos los eventos disparados por un modelo. Puedes lograr esto utilizando el método withoutEvents
. El método withoutEvents
acepta una función anónima como su único argumento. Cualquier código ejecutado dentro de esta función anónima no despachará eventos del modelo, y cualquier valor retornado por la función anónima será devuelto por el método withoutEvents
:
use App\Models\User; $user = User::withoutEvents(function () { User::findOrFail(1)->delete(); return User::find(2);});
Guardando un Solo Modelo Sin Eventos
A veces es posible que desees "guardar" un modelo dado sin despachar ningún evento. Puedes lograr esto utilizando el método saveQuietly
:
$user = User::findOrFail(1); $user->name = 'Victoria Faith'; $user->saveQuietly();
También puedes "actualizar", "eliminar", "eliminar de forma suave", "restaurar" y "replicar" un modelo dado sin despachar ningún evento:
$user->deleteQuietly();$user->forceDeleteQuietly();$user->restoreQuietly();