Eloquent: Primeros pasos
- Introducción
- Generación de clases modelo
- Convenciones de Eloquent Model
- Recuperación de modelos
- Recuperación de Modelos Individuales / Agregados
- Inserción y actualización de modelos
- Borrado de modelos
- Poda de modelos
- Replicación de modelos
- Ámbitos de consulta
- Comparación de modelos
- Eventos
Introducción
Laravel incluye Eloquent, un mapeador objeto-relacional (ORM) que hace que sea agradable interactuar con tu base de datos. Cuando se utiliza Eloquent, cada tabla de la base de datos tiene su correspondiente "Modelo" que se utiliza para interactuar con esa tabla. Además de recuperar registros de la tabla de la base de datos, los modelos de Eloquent permiten insertar, actualizar y eliminar registros de la tabla.
Nota
Antes de empezar, asegúrate de configurar una conexión a la base de datos en el archivo de configuraciónconfig/database.php
de tu aplicación. Para más información sobre la configuración de la base de datos, consulta la documentación de configuración de la base de datos.
Laravel Bootcamp
Si eres nuevo en Laravel, puedes echarle un ojo al Laravel Bootcamp. El Laravel Bootcamp te guiará a través de la construcción de tu primera aplicación Laravel utilizando Eloquent. Es una buena forma de aprender sobre lo que Laravel y Eloquent puede ofrecerte.
Generación de clases de modelos
Para empezar, vamos a crear un modelo Eloquent. Los modelos normalmente viven en el directorio app\Models
y extienden la clase Illuminate\Database\Eloquent\Model
. Puede utilizar el comando Artisan make:model
para generar un nuevo modelo:
php artisan make:model Flight
Si desea generar una migración de base de datos al generar el modelo, puede utilizar la opción --migration
o -m
:
php artisan make:model Flight --migration
Puedes generar varios otros tipos de clases al generar un modelo, tales como factories, seeders, policies, controllers, y form requests. Además, estas opciones pueden combinarse 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 --all # Generate a pivot model...php artisan make:model Member --pivot
Inspección de modelos
A veces puede ser difícil determinar todos los atributos y relaciones disponibles de un modelo simplemente hojeando su código. En su lugar, pruebe el comando Artisan model:show
, que proporciona una buena visión general de todos los atributos y relaciones del modelo:
php artisan model:show Flight
Convenciones del modelo Eloquent
Los modelos generados por el comando make:model
serán colocados en el directorio app/Models
. Examinemos una clase modelo básica y discutamos algunas de las convenciones clave 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, te habrás dado cuenta de que no le hemos dicho a Eloquent qué tabla de la base de datos corresponde a nuestro modelo Flight
. Por convención, el nombre de la clase en plural y en formato "snake_case" será usado como el nombre de tabla a menos que otro nombre sea especificado expresamente. Así, en este caso, Eloquent asumirá que el modelo Flight
almacena los registros en la tabla flights
, mientras que un modelo AirTrafficController
almacenaría los 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 de 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 llamada id
. Si es necesario, puede definir una propiedad protegida $primaryKey
en su modelo para especificar una columna diferente que sirva como clave primaria de su 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 incremental, lo que significa que Eloquent convertirá automáticamente la clave primaria en un entero. Si desea utilizar una clave primaria no incremental o no numérica, debe definir una propiedad pública $incrementing
en su modelo con el valor 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 su modelo no es un entero, debe definir una propiedad protegida $keyType
en su modelo. Esta propiedad debe tener el valor string
:
<?php class Flight extends Model{ /** * The data type of the auto-incrementing ID. * * @var string */ protected $keyType = 'string';}
Claves primarias compuestas
Eloquent requiere que cada modelo tenga al menos un "ID" de identificación único que pueda servir como clave primaria. Los modelos Eloquent no admiten claves primarias "compuestas". Sin embargo, puede añadir índices únicos adicionales de varias columnas a las tablas de la base de datos, además de la clave principal de identificación única de la tabla.
Claves UUID y ULID
En lugar de utilizar números enteros autoincrementados como claves primarias de su modelo Eloquent, puede optar por utilizar UUIDs en su lugar. Los UUIDs son identificadores alfanuméricos universalmente únicos de 36 caracteres de longitud.
Si desea que un modelo utilice una clave UUID en lugar de una clave entera autoincrementada, puede utilizar el trait Illuminate\Database\Eloquent\Concerns\HasUuids
en el modelo. Por supuesto, debe asegurarse de que el modelo tiene 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 trait HasUuids
generará UUIDs "ordenados" para sus modelos. Estos UUIDs son más eficientes para el almacenamiento indexado en bases de datos porque pueden ser ordenados lexicográficamente.
Puede sobreescribir el proceso de generación de UUID para un modelo determinado definiendo un método newUniqueId
en el modelo. Además, puede 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. * * @return string */public function newUniqueId(){ return (string) Uuid::uuid4();} /** * Get the columns that should receive a unique identifier. * * @return array */public function uniqueIds(){ return ['id', 'discount_code'];}
Si lo desea, puede optar por utilizar "ULIDs" en lugar de UUIDs. Los ULIDs son similares a los UUIDs; sin embargo, sólo tienen 26 caracteres de longitud. Al igual que los UUID ordenados, los ULID pueden ordenarse lexicográficamente para una indexación eficiente de la base de datos. Para utilizar ULIDs, debe utilizar el trait Illuminate\Database\Eloquent\Concerns\HasUlids
en su modelo. También debe asegurarse de que el modelo tiene 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"
Marcas de tiempo
Por defecto, Eloquent espera que las columnas created_at
y updated_at
existan en la tabla correspondiente de la base de datos del modelo. Eloquent establecerá automáticamente los valores de estas columnas cuando se creen o actualicen los modelos. Si no quieres que estas columnas sean gestionadas automáticamente por Eloquent, debes definir una propiedad $timestamps
en tu modelo con el valor 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 necesita personalizar el formato de las marcas de tiempo de su modelo, defina la propiedad $dateFormat
en su 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 necesita personalizar los nombres de las columnas utilizadas para almacenar las marcas de tiempo, puede definir las constantes CREATED_AT
y UPDATED_AT
en su modelo:
<?php class Flight extends Model{ const CREATED_AT = 'creation_date'; const UPDATED_AT = 'updated_date';}
Si quieres realizar operaciones sobre el modelo sin que se modifique su timestamp updated_at
, puedes operar sobre el modelo dentro de un closure dado al método withoutTimestamps
:
Model::withoutTimestamps(fn () => $post->increment(['reads']));
Conexiones de bases de datos
Por defecto, todos los modelos de Eloquent utilizarán la conexión de base de datos por defecto configurada para tu aplicación. Si desea especificar una conexión diferente para un modelo en particular, debe 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 = 'sqlite';}
Valores por defecto de los atributos
Por defecto, una instancia de modelo recién instanciada no contendrá ningún valor de atributo. Si desea definir los valores por defecto para algunos de los atributos de su modelo, puede definir una propiedad $attributes
en su modelo:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * The model's default values for attributes. * * @var array */ protected $attributes = [ 'delayed' => false, ];}
Configuración de Eloquent Strictness
Laravel ofrece varios métodos que le permiten configurar el comportamiento de Eloquent y la "rigurosidad" en una variedad de situaciones.
En primer lugar, el método preventLazyLoading
acepta un argumento booleano opcional que indica si debe evitarse la carga lenta. Por ejemplo, es posible que sólo desee desactivar la carga lenta en entornos que no sean de producción para que su entorno de producción siga funcionando normalmente incluso si una relación cargada de forma lenta está accidentalmente presente en el código de producción. Típicamente, este método debería ser invocado en el método boot
del AppServiceProvider
de su aplicación:
use Illuminate\Database\Eloquent\Model; /** * Bootstrap any application services. * * @return void */public function boot(){ Model::preventLazyLoading(! $this->app->isProduction());}
Además, puede indicar a Laravel que lance una excepción cuando intente rellenar un atributo no rellenable invocando el método preventSilentlyDiscardingAttributes
. Esto puede ayudar a prevenir errores inesperados durante el desarrollo local cuando se intenta establecer un atributo que no se ha añadido a la array fillable
del modelo:
Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
Por último, puede indicar a Eloquent que lance una excepción si intenta acceder a un atributo de un modelo cuando ese atributo no se ha recuperado realmente de la base de datos o cuando el atributo no existe. Por ejemplo, esto puede ocurrir cuando se olvida añadir un atributo a la cláusula select
de una consulta de Eloquent:
Model::preventAccessingMissingAttributes(! $this->app->isProduction());
Activar el "modo estricto" de Eloquent
Para mayor comodidad, puede activar los tres métodos anteriores simplemente invocando el método shouldBeStrict
:
Model::shouldBeStrict(! $this->app->isProduction());
Recuperación de modelos
Una vez que has creado un modelo y su tabla de base de datos asociada, estás listo para empezar a recuperar datos de tu base de datos. Puedes pensar en cada modelo Eloquent como un potente constructor de consultas que te permite consultar con fluidez la tabla de base de datos asociada al 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;}
Creación de consultas
El método all
de Eloquent devolverá todos los resultados de la tabla del modelo. Sin embargo, dado que cada modelo Eloquent sirve como 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 Eloquent son constructores de consultas, deberías revisar todos los métodos proporcionados por el constructor de consultas de Laravel. Puedes utilizar cualquiera de estos métodos cuando escribas tus consultas Eloquent.
Actualización de modelos
Si ya tienes una instancia de un modelo Eloquent que fue recuperada de la base de datos, puedes "refrescar" el modelo usando los métodos fresh
y refresh
. El método fresh
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, los 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 simple array PHP. En su lugar, devuelven una instancia de Illuminate\Database\Eloquent\Collection
.
La clase Eloquent Collection
extiende la clase base Illuminate\Support\Collection
de Laravel, que proporciona una variedad de métodos útiles para interactuar con las colecciones de datos. Por ejemplo, el método reject
puede utilizarse para eliminar modelos de una colección basándose en los resultados de un closure invocado:
$flights = Flight::where('destination', 'Paris')->get(); $flights = $flights->reject(function ($flight) { return $flight->cancelled;});
Además de los métodos proporcionados por la clase base collection de Laravel, la clase collection de Eloquent proporciona algunos métodos extra que están específicamente pensados para interactuar con colecciones de modelos Eloquent.
Dado que todas las colecciones de Laravel implementan las interfaces iterables de PHP, puedes hacer bucles sobre las colecciones como si fueran un array:
foreach ($flights as $flight) { echo $flight->name;}
Dividir resultados en partes
Tu aplicación puede quedarse sin memoria si intentas cargar decenas de miles de registros Eloquent mediante los métodos all
o get
. En lugar de utilizar estos métodos, se puede utilizar el método chunk
para procesar un gran número de modelos de manera más eficiente.
El método chunk
recuperará un subconjunto de modelos Eloquent, pasándolos a un closure para su procesamiento. Dado que sólo se recupera el trozo actual de modelos Eloquent a la vez, el método de chunk
reducirá significativamente el uso de memoria cuando se trabaje con un gran número de modelos:
use App\Models\Flight; Flight::chunk(200, function ($flights) { foreach ($flights as $flight) { // }});
El primer argumento que se pasa al método chunk
es el número de registros que se desea recibir por "chunk". El closure pasado 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 trozo de registros pasado al closure.
Si va a filtrar los resultados del método chunk
basándose en una columna que también va a actualizar mientras itera sobre los resultados, debería utilizar el método chunkById
. Utilizar el método chunk
en estos casos podría dar lugar a resultados inesperados e incoherentes. Internamente, el método chunkById
siempre recuperará modelos con una columna id
mayor que el último modelo del chunk anterior:
Flight::where('departed', true) ->chunkById(200, function ($flights) { $flights->each->update(['departed' => false]); }, $column = 'id');
Dividir resultados en partes mediante Lazy Collections
El método lazy
funciona de forma similar al método chunk
en el sentido de que, entre bastidores, ejecuta la consulta por trozos. Sin embargo, en lugar de pasar cada trozo directamente a una llamada de retorno, el método lazy
devuelve una LazyCollection
de modelos Eloquent, lo que permite interactuar con los resultados como un único flujo:
use App\Models\Flight; foreach (Flight::lazy() as $flight) { //}
Si va a filtrar los resultados del método lazy
basándose en una columna que también va a actualizar mientras itera sobre los resultados, debería utilizar el método lazyById
. Internamente, el método lazyById
siempre recuperará modelos con una columna id
mayor que el último modelo del chunk anterior:
Flight::where('departed', true) ->lazyById(200, $column = 'id') ->each->update(['departed' => false]);
Puede filtrar los resultados basándose en el orden descendente del id
utilizando el método lazyByIdDesc
.
Cursores
Al igual que el método lazy
, el método cursor
se puede utilizar para reducir significativamente el consumo de memoria de la aplicación cuando se itera a través de decenas de miles de registros de modelos de Eloquent.
El método cursor
sólo ejecutará una única consulta a la base de datos; sin embargo, los modelos Eloquent individuales no se hidratarán hasta que realmente se itere sobre ellos. Por lo tanto, sólo un modelo Eloquent se mantiene en memoria en un momento dado mientras se itera sobre el cursor.
Advertencia
Dado que el métodocursor
sólo mantiene un único modelo Eloquent en memoria cada vez, no puede precargar sus relaciones de forma anticipada. Si necesitas cargar relaciones, considera usar el métodolazy
en su lugar.
Internamente, el método cursor
utiliza generadores PHP para implementar esta funcionalidad:
use App\Models\Flight; foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) { //}
El cursor
devuelve una instancia Illuminate\Support\LazyCollection
. Las colecciones lazy le permiten utilizar muchos de los métodos de colección disponibles en las colecciones típicas de Laravel, mientras que sólo se carga un único modelo en la memoria a la vez:
use App\Models\User; $users = User::cursor()->filter(function ($user) { return $user->id > 500;}); foreach ($users as $user) { echo $user->id;}
Aunque el método cursor
utiliza mucha menos memoria que una consulta normal (al mantener un único modelo de Eloquent en memoria cada vez), con el tiempo se quedará sin memoria. Esto se debe a que el controlador PDO de PHP almacena internamente en caché todos los resultados sin procesar de la consulta en su búfer. Si está tratando con un gran número de registros Eloquent, considere usar el método lazy
en su lugar.
Subconsultas avanzadas
Selección de subconsultas
Eloquent también ofrece soporte avanzado para subconsultas, lo que permite extraer información de tablas relacionadas en una única consulta. Por ejemplo, imaginemos que tenemos una tabla destinos
(destinations) de vuelos y una tabla de vuelos
(flights) a destinos. La tabla de vuelos
contiene una columna arrived_at
que indica cuándo llegó el vuelo al destino.
Utilizando la funcionalidad de subconsulta disponible para los métodos select
y addSelect
del constructor de consultas, podemos seleccionar todos los destinos
y el nombre del vuelo que llegó más recientemente a ese destino utilizando una única 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();
Ordenación de subconsultas
Además, la función orderBy
del generador de consultas admite subconsultas. Siguiendo con nuestro ejemplo del vuelo, podemos utilizar esta función para ordenar todos los destinos en función de cuándo llegó el último vuelo a ese destino. De nuevo, esto puede hacerse mientras se ejecuta una única consulta a la base de datos:
return Destination::orderByDesc( Flight::select('arrived_at') ->whereColumn('destination_id', 'destinations.id') ->orderByDesc('arrived_at') ->limit(1))->get();
Recuperación de modelos individuales / agregados
Además de recuperar todos los registros que coincidan con una consulta determinada, también puede 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 única 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 desee realizar alguna otra acción si no se encuentra ningún resultado. Los métodos findOr
y firstOr
devolverán una única instancia del modelo o, si no se encuentran resultados, ejecutarán el closure dado. El valor devuelto por el closure se considerará el resultado del método:
$flight = Flight::findOr(1, function () { // ...}); $flight = Flight::where('legs', '>', 3)->firstOr(function () { // ...});
Excepciones de no encontrado
A veces es posible que desee 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 excepción Illuminate\Database\Eloquent\ModelNotFoundException
:
$flight = Flight::findOrFail(1); $flight = Flight::where('legs', '>', 3)->firstOrFail();
Si no se detecta la excepción ModelNotFoundException
, se envía automáticamente una respuesta HTTP 404 al cliente:
use App\Models\Flight; Route::get('/api/flights/{id}', function ($id) { return Flight::findOrFail($id);});
Recuperación o creación de modelos
El método firstOrCreate
intentará localizar un registro de la base de datos utilizando los pares columna / valor dados. Si el modelo no se encuentra en la base de datos, se insertará un registro con los atributos resultantes de combinar el primer argumento de array con el segundo argumento opcional array:
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. Tenga en cuenta que el modelo devuelto por firstOrNew
aún no se ha guardado en la base de datos. Deberá llamar manualmente al método save
para guardarlo:
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']);
Recuperación de agregados
Al interactuar con modelos Eloquent, también puede utilizar el count
, sum
, max
y otros métodos agregados proporcionados por el constructor de consultas de Laravel. Como es de esperar, estos métodos devuelven un valor escalar en lugar de una instancia del modelo Eloquent:
$count = Flight::where('active', 1)->count(); $max = Flight::where('active', 1)->max('price');
Inserción y actualización de modelos
Inserciones
Por supuesto, cuando usamos Eloquent, no sólo necesitamos recuperar modelos de la base de datos. También necesitamos insertar nuevos registros. Afortunadamente, Eloquent lo hace sencillo. Para insertar un nuevo registro en la base de datos, debes instanciar una nueva instancia de modelo y establecer atributos en el modelo. A continuación, llame 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\Request; class FlightController extends Controller{ /** * Store a new flight in the database. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { // Validate the request... $flight = new Flight; $flight->name = $request->name; $flight->save(); }}
En este ejemplo, asignamos el campo name
de la petición HTTP entrante al atributo name
de la instancia del modelo App\Models\Flight
. Cuando llamemos 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 establecerlas manualmente.
De manera alternativa, puede usar el método create
para "guardar" un nuevo modelo usando una sola sentencia PHP. La instancia del modelo insertado le será devuelta por el método create
:
use App\Models\Flight; $flight = Flight::create([ 'name' => 'London to Paris',]);
Sin embargo, antes de usar el método create
, necesitará especificar una propiedad fillable
o guarded
en su clase modelo. Estas propiedades son necesarias porque todos los modelos Eloquent están protegidos contra vulnerabilidades de asignación masiva por defecto. Para obtener más información sobre la asignación masiva, consulte la documentación de asignación masiva.
Actualizaciones
El método save
también puede utilizarse para actualizar modelos que ya existen en la base de datos. Para actualizar un modelo, debe recuperarlo y establecer los atributos que desee actualizar. A continuación, debe llamar al método save
del modelo. De nuevo, 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();
Actualizaciones masivas
También se pueden realizar actualizaciones contra modelos que coincidan con una consulta determinada. En este ejemplo, todos los vuelos que estén active
y tengan como destination
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 columnas y valores que representan las columnas que deben actualizarse. El método update
devuelve el número de filas afectadas.
Advertencia
Al realizar una actualización masiva a través de Eloquent, los eventossaving
,saved
,updating
yupdated
no se lanzarán para los modelos actualizados. Esto se debe a que los modelos nunca se recuperan cuando se emite una actualización masiva.
Examinar cambios de atributos
Eloquent proporciona los métodos isDirty
, isClean
y wasChanged
para examinar el estado interno de su modelo y determinar cómo han cambiado sus atributos desde que se recuperó el modelo originalmente.
El método isDirty
determina si alguno de los atributos del modelo ha cambiado desde que se recuperó el modelo. Puede 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 no ha cambiado 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 ha modificado algún atributo la última vez que se guardó el modelo dentro del ciclo de solicitud actual. Si es necesario, puede pasar un nombre de atributo para ver si se ha modificado un atributo concreto:
$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, independientemente de cualquier cambio que se haya producido en el modelo desde que se recuperó. Si es necesario, puedes pasar un nombre de atributo específico para obtener el valor original de un atributo en 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
Puede utilizar el método create
para "guardar" un nuevo modelo utilizando una única sentencia PHP. La instancia del modelo insertado le 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á especificar una propiedad fillable
o guarded
en su clase 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 pasa un campo de solicitud HTTP inesperado y ese campo cambia una columna en su base de datos que usted no esperaba. Por ejemplo, un usuario malicioso podría enviar un parámetro is_admin
a través de una petición HTTP, que luego se pasa al método create
de tu modelo, permitiendo al usuario escalarse a sí mismo a administrador.
Así que, para empezar, deberías definir qué atributos del modelo quieres hacer asignables en masa. Puedes hacer esto usando 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 utilizar el método create
para insertar un nuevo registro en la base de datos. El método create
devuelve la instancia de modelo recién creada:
$flight = Flight::create(['name' => 'London to Paris']);
Si ya tienes una instancia del modelo, puedes usar el método fill
para rellenarla 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 seguridad, Laravel no soporta la actualización de atributos JSON anidados cuando se utiliza la propiedad guarded
:
/** * The attributes that are mass assignable. * * @var array */protected $fillable = [ 'options->enabled',];
Permitir la asignación masiva
Si quieres que todos tus atributos sean asignables en masa, puedes definir la propiedad $guarded
de tu modelo como un array vacío. Si opta por desproteger su modelo, debe tener especial cuidado de crear siempre a mano las matrices pasadas a los métodos de 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 la array $fillable
se descartan silenciosamente al realizar operaciones de asignación masiva. En producción, este es el comportamiento esperado; sin embargo, durante el desarrollo local puede llevar a confusión en cuanto a por qué los cambios del modelo no tienen efecto.
Si lo deseas, puedes indicar a Laravel que lance una excepción cuando intente rellenar un atributo no rellenable invocando el método preventSilentlyDiscardingAttributes
. Normalmente, este método debería invocarse dentro del método boot
de uno de los proveedores de servicios de tu aplicación:
use Illuminate\Database\Eloquent\Model; /** * Bootstrap any application services. * * @return void */public function boot(){ Model::preventSilentlyDiscardingAttributes($this->app->isLocal());}
Upserts
Ocasionalmente, puedes necesitar 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 hay necesidad de llamar manualmente al método save
.
En el ejemplo siguiente, si existe un vuelo con salida
(departure) en Oakland
y destino
(destination) en San Diego
, se actualizarán las columnas de price
y discounted
. Si no existe tal vuelo, se creará un nuevo vuelo que tendrá los atributos resultantes de fusionar el primera array con el segundo:
$flight = Flight::updateOrCreate( ['departure' => 'Oakland', 'destination' => 'San Diego'], ['price' => 99, 'discounted' => 1]);
Si desea realizar múltiples "upserts" en una sola consulta, entonces debe utilizar el método upsert
en su lugar. El primer argumento del método consiste en los valores a insertar o actualizar, mientras que el segundo argumento enumera la(s) columna(s) que identifican de forma ú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]], ['departure', 'destination'], ['price']);
Advertencia
Todas las bases de datos excepto SQL Server requieren que las columnas del segundo argumento del métodoupsert
tengan un índice "primario" o "único". Además, el controlador de base de datos MySQL ignora el segundo argumento del métodoupsert
y utiliza siempre los índices "primario" y "único" de la tabla para detectar los registros existentes.
Borrado de modelos
Para borrar un modelo, puede llamar al método delete
en la instancia del modelo:
use App\Models\Flight; $flight = Flight::find(1); $flight->delete();
Puede llamar al método truncate
para borrar todos los registros de la base de datos asociados al modelo. La operación de truncate
también restablecerá cualquier ID autoincrementado en la tabla asociada al modelo:
Flight::truncate();
Borrar 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 una ú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]));
Advertencia
El métododestroy
carga cada modelo individualmente y llama al métododelete
para que los eventos dedeleting
ydeleted
sean enviados correctamente para cada modelo.
Borrado de Modelos mediante Consultas
Por supuesto, puede crear una consulta de Eloquent para eliminar todos los modelos que coincidan con los criterios de la consulta. En este ejemplo, borraremos todos los vuelos marcados como inactivos. Al igual que las actualizaciones masivas, los borrados masivos no enviarán eventos de modelo para los modelos borrados:
$deleted = Flight::where('active', 0)->delete();
Advertencia
Al ejecutar una sentencia de borrado masivo a través de Eloquent, los eventos dedeleting
y de modelodeleted
no se enviarán para los modelos borrados. Esto se debe a que los modelos nunca se recuperan cuando se ejecuta la sentencia de borrado.
Borrado suave
Además de eliminar registros de la base de datos, Eloquent también puede "eliminar suavemente" modelos. Cuando los modelos se borran suavemente, no se eliminan realmente de la base de datos. En su lugar, se establece un atributo deleted_at
en el modelo que indica la fecha y hora en la que se "eliminó" el modelo. Para habilitar el borrado suave de un modelo, añada 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 traitSoftDeletes
convertirá automáticamente el atributodeleted_at
en una instanciaDateTime
/Carbon
.
También debe añadir la columna deleted_at
a su tabla de base de datos. El constructor de esquemas de Laravel contiene un método de ayuda 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 llame al método delete
en el modelo, la columna deleted_at
se establecerá en la fecha y hora actuales. Sin embargo, el registro de base de datos del modelo se mantendrá en la tabla. Cuando se consulta un modelo que utiliza borrados suaves, los modelos borrados suaves se excluirán automáticamente de todos los resultados de la consulta.
Para determinar si una instancia de un modelo ha sido borrada de forma suave, puede utilizar el método de trashed
:
if ($flight->trashed()) { //}
Restauración de modelos borrados "de forma suave"
A veces es posible que desee "recuperar" un modelo borrado suavemente. Para ello, 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 puede utilizar el método de restore
en una consulta para restaurar varios modelos. De nuevo, al igual que otras operaciones "masivas", no se enviará ningún evento de modelo para los modelos restaurados:
Flight::withTrashed() ->where('airline_id', 1) ->restore();
El método restore
también puede utilizarse para crear consultas de relación:
$flight->history()->restore();
Borrado permanente de modelos
A veces es necesario eliminar un modelo de la base de datos. Puede utilizar el método forceDelete
para eliminar permanentemente un modelo borrado "de forma suave" de la tabla de la base de datos:
$flight->forceDelete();
También puede utilizar el método forceDelete
al crear consultas de relación Eloquent:
$flight->history()->forceDelete();
Consulta de modelos borrados "de forma suave"
Inclusión de modelos eliminados "de forma suave"
Como se ha indicado anteriormente, los modelos eliminados suavemente se excluirán automáticamente de los resultados de las consultas. Sin embargo, puede forzar la inclusión de los modelos borrados "de forma suave" 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 puede ser llamado cuando se construye una consulta de relación:
$flight->history()->withTrashed()->get();
Recuperar sólo modelos borrados "de forma suave"
El método onlyTrashed
recuperará sólo los modelos borrados "de forma suave":
$flights = Flight::onlyTrashed() ->where('airline_id', 1) ->get();
Eliminar modelos innecesarios (Pruning Models)
A veces es posible que desee eliminar periódicamente los modelos que ya no son necesarios. Para ello, puede añadir el trait Illuminate\Database\Eloquent\Prunable
o Illuminate\Database\Eloquent\MassPrunable
a los modelos que desee eliminar periódicamente. Después de añadir uno de los traits al modelo, implemente 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\Model;use Illuminate\Database\Eloquent\Prunable; class Flight extends Model{ use Prunable; /** * Get the prunable model query. * * @return \Illuminate\Database\Eloquent\Builder */ public function prunable() { return static::where('created_at', '<=', now()->subMonth()); }}
Cuando se marcan modelos como Prunable
, también se puede definir un método de pruning
en el modelo. Este método será llamado antes de que el modelo sea borrado. Este método puede ser útil para borrar cualquier recurso adicional asociado al modelo, como archivos almacenados, antes de que el modelo sea eliminado permanentemente de la base de datos:
/** * Prepare the model for pruning. * * @return void */protected function pruning(){ //}
Después de configurar tu modelo como "prunable", debes programar el comando model:prune
Artisan en la clase App\Console\Kernel
de tu aplicación. Eres libre de elegir el intervalo apropiado en el que este comando debe ser ejecutado:
/** * Define the application's command schedule. * * @param \Illuminate\Console\Scheduling\Schedule $schedule * @return void */protected function schedule(Schedule $schedule){ $schedule->command('model:prune')->daily();}
El comando model:prune
detectará automáticamente los modelos "prunable" dentro del directorio app/Models
de su aplicación. Si sus modelos se encuentran en una ubicación diferente, puede utilizar la opción --model
para especificar los nombres de las clases de los modelos:
$schedule->command('model:prune', [ '--model' => [Address::class, Flight::class],])->daily();
Si desea excluir ciertos modelos de ser eliminados mientras se borran todos los demás modelos detectados, puede utilizar la opción --except
:
$schedule->command('model:prune', [ '--except' => [Address::class, Flight::class],])->daily();
Puede probar su consulta prunable
ejecutando el comando model:prune
con la opción --pretend
. Al fingir, el comando model:prune
simplemente informará de cuántos registros se borrarían si el comando se ejecutara realmente:
php artisan model:prune --pretend
Advertencia
Los modelos borrados "de forma suave" se borrarán permanentemente (forceDelete
) si coinciden con la consulta prunable.
Eliminar modelos masivamente
Cuando los modelos se marcan con el trait Illuminate\Database\Eloquent\MassPrunable
, los modelos se eliminan de la base de datos mediante consultas de eliminación masiva. Por lo tanto, no se invocará el método de pruning
, ni se enviarán los eventos de modelo deleting
y deleted
. Esto se debe a que los modelos nunca se recuperan antes de ser eliminados, lo que hace que el proceso de eliminación sea mucho más eficiente:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\MassPrunable; class Flight extends Model{ use MassPrunable; /** * Get the prunable model query. * * @return \Illuminate\Database\Eloquent\Builder */ public function prunable() { return static::where('created_at', '<=', now()->subMonth()); }}
Replicación de modelos
Puede crear una copia no guardada de una instancia de modelo existente utilizando el método replicate
. Este método es especialmente útil cuando se tienen 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, puede 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 (Query Scopes)
Scopes globales
Los scopes globales permiten añadir restricciones a todas las consultas de un modelo determinado. La propia funcionalidad de borrado suave de Laravel utiliza scopes globales para recuperar únicamente los modelos "no borrados" de la base de datos. Escribir sus propios scopes globales puede proporcionar una manera fácil de asegurarse de que cada consulta para un modelo dado recibe ciertas restricciones.
Escritura de scopes globales
Escribir un scope global es sencillo. En primer lugar, defina una clase que implemente la interfaz Illuminate\Database\Eloquent\Scope
. Laravel no tiene una ubicación convencional donde se deben colocar las clases scope, por lo que eres libre de colocar esta clase en cualquier directorio que desees.
La interfaz Scope
requiere que se implemente 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. * * @param \Illuminate\Database\Eloquent\Builder $builder * @param \Illuminate\Database\Eloquent\Model $model * @return void */ public function apply(Builder $builder, Model $model) { $builder->where('created_at', '<', now()->subYears(2000)); }}
Nota
Si su scope global añade columnas a la cláusula select de la consulta, debe utilizar el métodoaddSelect
en lugar deselect
. Esto evitará la sustitución involuntaria de la cláusula select existente en la consulta.
Aplicación de scopes globales
Para asignar un scope global a un modelo, debe sobreescribir el método booted
del modelo e invocar el método addGlobalScope
del modelo. El método addGlobalScope
acepta una instancia de su ámbito como ú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. * * @return void */ protected static function booted() { static::addGlobalScope(new AncientScope); }}
Tras añadir el scope del 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
Scopes globales anónimos
Eloquent también permite definir scopes globales mediante closures, lo que resulta especialmente útil para scopes sencillos que no requieren una clase propia independiente. Cuando defina un ámbito global utilizando un closure, debe proporcionar un nombre de ámbito de su elección como primer argumento del 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. * * @return void */ protected static function booted() { static::addGlobalScope('ancient', function (Builder $builder) { $builder->where('created_at', '<', now()->subYears(2000)); }); }}
Eliminación de scopes globales
Si desea eliminar un scope global para una consulta determinada, puede utilizar el método withoutGlobalScope
. Este método acepta como único argumento el nombre de la clase del scope global:
User::withoutGlobalScope(AncientScope::class)->get();
O, si definió el scope global utilizando un closure, deberá pasar el nombre de la cadena que asignó al scope global:
User::withoutGlobalScope('ancient')->get();
Si desea eliminar varios o incluso todos los scopes globales de la consulta, puede utilizar 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();
Scopes locales
Los scopes locales le permiten definir conjuntos comunes de restricciones de consulta que puede reutilizar fácilmente en toda su aplicación. Por ejemplo, puede que necesite recuperar con frecuencia todos los usuarios considerados "populares". Para definir un scope, anteponga el prefijo scope
a un método del modelo Eloquent.
Los scopes deben devolver siempre la misma instancia del constructor de consultas o void
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * Scope a query to only include popular users. * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ public function scopePopular($query) { return $query->where('votes', '>', 100); } /** * Scope a query to only include active users. * * @param \Illuminate\Database\Eloquent\Builder $query * @return void */ public function scopeActive($query) { $query->where('active', 1); }}
Utilizando un Scope Local
Una vez definido el scope, puede llamar a los métodos del scope cuando consulte el modelo. Sin embargo, no debe incluir el prefijo scope
al llamar al método. Incluso se pueden encadenar llamadas a varios scopes:
use App\Models\User; $users = User::popular()->active()->orderBy('created_at')->get();
La combinación de varios scopes del modelo Eloquent mediante un operador de consulta or
puede requerir el uso de closures para lograr la agrupación lógica correcta:
$users = User::popular()->orWhere(function (Builder $query) { $query->active();})->get();
Sin embargo, dado que esto puede resultar engorroso, Laravel proporciona un método orWhere
de "orden superior" que permite encadenar scopes de forma fluida sin necesidad de utilizar closures:
$users = App\Models\User::popular()->orWhere->active()->get();
Scopes dinámicos
A veces es posible que desee definir un scope que acepte parámetros. Para empezar, basta con añadir los parámetros adicionales a la firma del método de scope. Los parámetros deben definirse después del parámetro $query
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * Scope a query to only include users of a given type. * * @param \Illuminate\Database\Eloquent\Builder $query * @param mixed $type * @return \Illuminate\Database\Eloquent\Builder */ public function scopeOfType($query, $type) { return $query->where('type', $type); }}
Una vez que los argumentos esperados han sido añadidos a la firma de tu método scope, puedes pasar los argumentos cuando este sea llamado:
$users = User::ofType('admin')->get();
Comparación de modelos
A veces puede ser necesario determinar si dos modelos son "iguales" o no. Los métodos is
e isNot
se pueden utilizar para verificar rápidamente si dos modelos tienen la misma clave primaria, tabla y conexión de base de datos o no:
if ($post->is($anotherPost)) { //} if ($post->isNot($anotherPost)) { //}
Los métodos is
y isNot
también están disponibles cuando se utilizan las relaciones belongsTo
, hasOne
, morphTo
y morphOne
. Este método resulta especialmente útil cuando se desea comparar un modelo relacionado sin emitir una consulta para recuperar dicho modelo:
if ($post->author()->is($user)) { //}
Eventos
Nota
¿Quieres transmitir tus eventos Eloquent directamente a tu aplicación cliente? Echa un vistazo a la difusión de eventos de modelo de Laravel.
Los modelos Eloquent despachan varios eventos, permitiéndole conectarse a los siguientes momentos en el ciclo de vida de un modelo: retrieved
, creating
, created
, updating
, updated
, saving
, saved
, deleting
, deleted
, trashed
, forceDeleted
, restoring
, restored
, y replicating
.
El evento retrieved
se enviará cuando se recupere un modelo existente de la base de datos. Cuando un nuevo modelo es guardado por primera vez, los eventos creating
y created
serán enviados. Los eventos updating
/ updated
se enviarán cuando se modifique un modelo existente y se llame al método save
. Los eventos saving
/ saved
se enviarán cuando un modelo sea creado o actualizado - incluso si los atributos del modelo no han sido cambiados. Los nombres de eventos que terminan en -ing
se envían antes de que se guarden los cambios en el modelo, mientras que los eventos que terminan en -ed
se envían después de que se guarden los cambios en el modelo.
Para empezar a escuchar eventos de modelo, define una propiedad $dispatchesEvents
en tu modelo Eloquent. Esta propiedad asigna varios puntos del ciclo de vida del modelo Eloquent a tus propias clases de eventos. 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 */ protected $dispatchesEvents = [ 'saved' => UserSaved::class, 'deleted' => UserDeleted::class, ];}
Después de definir y mapear tus eventos Eloquent, puedes utilizar listeners de eventos para manejar los eventos.
Advertencia
Al realizar una actualización o eliminación masiva a través de Eloquent, los eventos de modelosaved
,updated
,deleting
ydeleted
no se enviarán para los modelos afectados. Esto se debe a que los modelos nunca se recuperan cuando se realizan actualizaciones o borrados masivos.
Uso de closures
En lugar de utilizar clases de eventos personalizadas, puede registrar closures que se ejecuten cuando se envíen varios eventos de modelo. Por lo general, deberías registrar estos closures 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. * * @return void */ protected static function booted() { static::created(function ($user) { // }); }}
Si es necesario, puedes utilizarlisteners de eventos anónimos en cola cuando registres eventos de modelo. Esto le 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) { //}));
Observadores
Definición de observadores
Si estás escuchando muchos eventos en un modelo dado, puedes utilizar observadores para agrupar todos tus listeners en una única clase. Las clases de observadores tienen nombres de métodos que reflejan los eventos de Eloquent que desea escuchar. Cada uno de estos métodos recibe como único argumento el modelo afectado. El comando make:observer
de Artisan es la forma más sencilla 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 usted. Su nuevo observador tendrá la siguiente apariencia:
<?php namespace App\Observers; use App\Models\User; class UserObserver{ /** * Handle the User "created" event. * * @param \App\Models\User $user * @return void */ public function created(User $user) { // } /** * Handle the User "updated" event. * * @param \App\Models\User $user * @return void */ public function updated(User $user) { // } /** * Handle the User "deleted" event. * * @param \App\Models\User $user * @return void */ public function deleted(User $user) { // } /** * Handle the User "restored" event. * * @param \App\Models\User $user * @return void */ public function restored(User $user) { // } /** * Handle the User "forceDeleted" event. * * @param \App\Models\User $user * @return void */ public function forceDeleted(User $user) { // }}
Para registrar un observador, necesitas llamar al método observe
en el modelo que deseas observar. Puedes registrar observadores en el método boot
del proveedor de servicios App\Providers\EventServiceProvider
de tu aplicación:
use App\Models\User;use App\Observers\UserObserver; /** * Register any events for your application. * * @return void */public function boot(){ User::observe(UserObserver::class);}
De manera alternativa, puede listar sus observadores dentro de una propiedad $observers
de la clase App\Providers\EventServiceProvider
de su aplicación:
use App\Models\User;use App\Observers\UserObserver; /** * The model observers for your application. * * @var array */protected $observers = [ User::class => [UserObserver::class],];
Nota
Hay eventos adicionales que un observador puede escuchar, comosaving
yretrieved
. Estos eventos se describen en la documentación de eventos.
Observadores y Transacciones de Base de Datos
Cuando los modelos están siendo creados dentro de una transacción de base de datos, usted puede querer instruir a un observador para que solo ejecute sus manejadores de eventos después de que la transacción de base de datos sea confirmada. Esto se consigue definiendo una propiedad $afterCommit
en el observador. Si una transacción de base de datos no está en progreso, los manejadores de eventos se ejecutarán inmediatamente:
<?php namespace App\Observers; use App\Models\User; class UserObserver{ /** * Handle events after all transactions are committed. * * @var bool */ public $afterCommit = true; /** * Handle the User "created" event. * * @param \App\Models\User $user * @return void */ public function created(User $user) { // }}
Silenciando Eventos
Ocasionalmente puedes necesitar "silenciar" temporalmente todos los eventos disparados por un modelo. Puede conseguirlo utilizando el método withoutEvents
. El método withoutEvents
acepta un closure como único argumento. Cualquier código ejecutado dentro de este closure no enviará eventos del modelo, y cualquier valor devuelto por el closure será devuelto por el método withoutEvents
:
use App\Models\User; $user = User::withoutEvents(function () { User::findOrFail(1)->delete(); return User::find(2);});
Guardar un único modelo sin eventos
A veces es posible que desee "guardar" un modelo determinado sin enviar ningún evento. Para ello, utilice el método saveQuietly
:
$user = User::findOrFail(1); $user->name = 'Victoria Faith'; $user->saveQuietly();
También puedes "update", "delete", "soft delete", "restore" y "replicate" un modelo dado sin enviar ningún evento:
$user->deleteQuietly(); $user->restoreQuietly();