Eloquent Getting Started

Eloquent: Getting Started

Laravel incluye Eloquent, un mapeador objeto-relacional (ORM) que hace que interactuar con tu base de datos sea una experiencia placentera. 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 de Eloquent también permiten insertar, actualizar y eliminar registros de la tabla.

Generating Model Classes

Para empezar, vamos a crear un modelo de Eloquent. Los modelos normalmente se encuentran en el directorio app\Models y extienden la clase Illuminate\Database\Eloquent\Model. Puedes utilizar el comando make:model de Artisan para generar un nuevo modelo:

php artisan make:model Flight

Si quieres generar también la migración con el modelo, puedes usar --migration or -m

php artisan make:model Flight --migration

Puedes generar varios otros tipos de clases al generar un modelo, como factories, seeders, policies, controladores y solicitudes de formularios. Además, estas opciones se pueden combinar para crear múltiples clases al mismo tiempo:

# Generate a model and a FlightFactory class...
php artisan make:model Flight --factory
php artisan make:model Flight -f
 
# Generate a model and a FlightSeeder class...
php artisan make:model Flight --seed
php artisan make:model Flight -s
 
# Generate a model and a FlightController class...
php artisan make:model Flight --controller
php artisan make:model Flight -c
 
# Generate a model, FlightController resource class, and form request classes...
php artisan make:model Flight --controller --resource --requests
php 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
php artisan make:model Flight -a
 
# Generate a pivot model...
php artisan make:model Member --pivot
php artisan make:model Member -p

Inspecting Models

A veces puede ser difícil determinar todos los atributos y relaciones disponibles de un modelo solo al revisar su código. En su lugar, puedes usar el comando model:show de Artisan, que proporciona una vista general conveniente de todos los atributos y relaciones del modelo:

php artisan model:show Flight

Eloquent Model Conventions

Los modelos generados por el comando make:model se colocarán en el directorio app/Models. Vamos a examinar una clase de modelo básica y discutir algunas de las convenciones clave de Eloquent:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    // ...
}

Table Names

Después de echar un vistazo al ejemplo anterior, habrás notado que no indicamos a Eloquent qué tabla de base de datos corresponde a nuestro modelo Flight. Por convención, se utilizará el nombre en “snake case” y en plural de la clase como nombre de la tabla, a menos que se especifique explícitamente otro nombre. 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 la 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';
}

Primary Keys

Eloquent también asumirá que la tabla de la base de datos correspondiente a cada modelo tiene una columna de clave primaria llamada id. Si es necesario, puedes definir una propiedad protegida $primaryKey en tu modelo para especificar una columna diferente que sirva como 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 autoincremental, lo que significa que Eloquent convertirá automáticamente la clave primaria a un entero. Si deseas usar una clave primaria que no sea autoincremental o que no sea numérica, debes definir una propiedad pública $incrementing en tu modelo y establecerla 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 protegida $keyType en tu modelo. Esta propiedad debe tener el valor string:

<?php
 
class Flight extends Model
{
    /**
     * The data type of the primary key ID.
     *
     * @var string
     */
    protected $keyType = 'string';
}

“Composite” Primary Keys

Eloquent requiere que cada modelo tenga al menos un “ID” de identificación único que pueda servir como su clave primaria. Los modelos de Eloquent no soportan claves primarias “compuestas”. Sin embargo, eres libre de agregar índices únicos de múltiples columnas adicionales a tus tablas de base de datos, además de la clave primaria de identificación única de la tabla.

UUID and ULID Keys

En lugar de usar enteros autoincrementales como claves primarias en tu modelo de Eloquent, puedes optar por usar UUIDs. Los UUIDs son identificadores alfanuméricos universalmente únicos que tienen 36 caracteres de longitud.

Si deseas que un modelo use una clave UUID en lugar de una clave de entero autoincremental, 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 trait HasUuids generará UUIDs “ordenados” para tus modelos. Estos UUIDs son más eficientes para el almacenamiento indexado en bases de datos porque pueden ser ordenados lexicográficamente.

Puedes sobrescribir el proceso de generación de UUID para un modelo específico 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 se pueden ordenar lexicográficamente para un indexado eficiente en la base de datos. Para utilizar ULIDs, debes 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"

Timestamps

Por defecto, Eloquent espera que existan columnas created_at y updated_at 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 los modelos. Si no deseas que estas columnas sean gestionadas automáticamente por Eloquent, debes 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 los timestamp 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 convierte en un array o en 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 las 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 la marca de tiempo updated_at del modelo, puedes operar sobre el modelo dentro de un closure proporcionado al método withoutTimestamps:

Model::withoutTimestamps(fn () => $post->increment('reads'));

Database Connections

Por defecto, todos los modelos de Eloquent utilizarán la conexión de base de datos predeterminada que está configurada para tu aplicación. Si deseas especificar una conexión diferente que se debe utilizar 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';
}

Default Attribute Values

Por defecto, una nueva instancia de un modelo no contendrá ningún 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 “crudo” o “almacenable”, 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,
    ];
}

Configuring Eloquent Strictness

Laravel ofrece varios métodos que te permiten configurar el comportamiento y la “rigidez” de Eloquent en diversas situaciones.

Primero, el método preventLazyLoading acepta un argumento booleano opcional que indica si se debe prevenir la carga diferida (lazy loading). Por ejemplo, podrías desear deshabilitar la carga diferida solo en entornos no productivos, de modo que tu entorno de producción continúe funcionando normalmente incluso si una relación cargada diferidamente está presente por accidente en el código de producción. Normalmente, este método debe ser invocado 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 cuando se intente dar valor a un atributo que no es “fillable” al invocar el método preventSilentlyDiscardingAttributes. Esto puede ayudar a prevenir errores inesperados durante el desarrollo local cuando se intenta establecer un atributo que no ha sido agregado al array fillable del modelo:

Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());

(Poner en el método boot de un serviceProvider)

Retrieving Models

Retrieving Models

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 considerar cada modelo de Eloquent como un potente generador de consultas que te permite consultar de manera fluida la tabla de la 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;
}

Building Queries

Dado que cada modelo de Eloquent actúa como un query builder, 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();

Dado que los modelos de Eloquent son generadores de consultas, debes revisar todos los métodos proporcionados por el query builder de Laravel. Puedes utilizar cualquiera de estos métodos al escribir tus consultas Eloquent.

Refreshing Models

Si ya tienes una instancia de un modelo Eloquent que fue recuperada de la base de datos, puedes “actualizar” 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 de modelo existente no se verá afectada:

$flight = Flight::where('number', 'FR 900')->first();
 
$freshFlight = $flight->fresh();

El método refresh volverá a hidratar(re-hydrate) el modelo existente utilizando datos frescos de la base de datos. Además, todas sus relaciones cargadas también se actualizarán:

$flight = Flight::where('number', 'FR 900')->first();
 
$flight->number = 'FR 456';
 
$flight->refresh();
 
$flight->number; // "FR 900"

Collections

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 de PHP. En su lugar, se devuelve 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 colecciones de datos. Por ejemplo, el método reject se puede usar para eliminar modelos de una colección en función de los resultados de un closure invocado:

$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 base de colecciones de Laravel, la clase de colecciones de Eloquent proporciona algunos métodos adicionales que están específicamente destinados a interactuar con colecciones de modelos Eloquent.

Dado que todas las colecciones de Laravel implementan la interfaz iterable de PHP, puedes recorrer las colecciones como si fueran un array.

foreach ($flights as $flight) {
    echo $flight->name;
}

Chunking Results

Tu aplicación podría quedarse sin memoria si intentas cargar decenas de miles de registros Eloquent utilizando los métodos all o get. En lugar de usar estos métodos, puedes utilizar 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 de cierre para su procesamiento. Dado que solo se recupera el fragmento actual de modelos Eloquent en cada momento, el método chunk proporcionará un uso de memoria significativamente reducido al trabajar con un gran número de modelos.

use App\Models\Flight;
use Illuminate\Database\Eloquent\Collection;
 
Flight::chunk(200, function (Collection $flights) {
    foreach ($flights as $flight) {
        // ...
    }
});

El primer argumento que se pasa al método chunk es el número de registros que deseas recibir por cada “fragmento” (chunk). La función de cierre (closure) pasada como segundo argumento se invocará para cada fragmento que se recupere de la base de datos. Se ejecutará una consulta a la base de datos para recuperar cada fragmento que se pasa a la closure.

Si estás filtrando los resultados del método chunk y 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 de id mayor que el último modelo en el fragmento anterior.

Flight::where('departed', true)
    ->chunkById(200, function (Collection $flights) {
        $flights->each->update(['departed' => false]);
    }, $column = 'id');

Chunking Using Lazy Collections

El método lazy funciona de manera similar al método chunk en el sentido de que, en el fondo, ejecuta la consulta en fragmentos. Sin embargo, en lugar de pasar cada fragmento directamente a una función de devolución de llamada (callback) tal como está, el método lazy devuelve una LazyCollection de modelos Eloquent aplanada, lo que te permite interactuar con los resultados en un solo flujo (stream).

use App\Models\Flight;
 
foreach (Flight::lazy() as $flight) {
    // ...
}

Si estás filtrando los resultados del método lazy y actualizando mientras iteras sobre los resultados, deberías usar el método lazyById. Usar el método lazy en estos escenarios podría llevar a resultados inesperados e inconsistentes. Internamente, el método lazyById siempre recuperará modelos con una columna de id mayor que el último modelo en el fragmento anterior.

Puedes filtrar los resultados basándote en el orden descendente del id utilizando el método lazyByIdDesc.

Cursors

De manera similar al método lazy, el método cursor puede utilizarse 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 única consulta a la base de datos; sin embargo, los modelos Eloquent individuales no serán hidratados hasta que realmente se iteran. Por lo tanto, solo se mantiene un modelo Eloquent en memoria en cualquier momento mientras se itera sobre el cursor.

⚠️ Dado que el método cursor solo mantiene un modelo Eloquent en memoria en un momento dado, no puede cargar relaciones de manera anticipada (eager load). Si necesitas cargar relaciones de manera anticipada, considera usar el método lazy en su lugar.

Internamente, el método cursor utiliza php generators para implementar esta funcionalidad:

use App\Models\Flight;
 
foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) {
    // ...
}

El método cursor devuelve una instancia de Illuminate\Support\LazyCollection. Las colecciones perezosas (LazyCollection) te permite utilizar 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 modelo Eloquent en memoria a la vez), eventualmente también se quedará sin memoria. Esto se debe a que el controlador PDO de PHP internamente almacena en caché todos los resultados de la consulta cruda en su buffer. Si estás tratando con un número muy grande de registros Eloquent, considera usar el método lazy en su lugar.

El método lazy es más adecuado para manejar conjuntos de datos extremadamente grandes porque no requiere que todos los resultados sean almacenados en el buffer de la base de datos, sino que procesa los datos en fragmentos de manera más eficiente.

Advanced Subqueries

Eloquent también ofrece soporte avanzado para subconsultas, lo que permite obtener información de tablas relacionadas en una sola consulta. Por ejemplo, imaginemos que tenemos una tabla de “destination” y una tabla de “flights” a destinos. La tabla de “flights” contiene una columna arrived_at que indica cuándo llegó el vuelo al destino.

Usando la funcionalidad de subconsulta disponible en los métodos select y addSelect del query builder, 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();

Subquery Ordering

Además, la función orderBy del query builder soporta subconsultas. Continuando con nuestro ejemplo de vuelos, podemos usar esta funcionalidad para ordenar todos los destinos según el momento en que el último vuelo llegó a ese destino. De nuevo, esto se puede hacer ejecutando 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();

Retrieving Single Models / Aggregates

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 ú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, puede que desees realizar alguna otra acción si no se encuentran resultados. Los métodos findOr y firstOr devolverán una única instancia de modelo o, si no se encuentran resultados, ejecutarán el closure proporcionado. El valor devuelto por la closure será considerado como el resultado del método.

$flight = Flight::findOr(1, function () {
    // ...
});
 
$flight = Flight::where('legs', '>', 3)->firstOr(function () {
    // ...
});

Not Found Exceptions

A veces, puede que desees lanzar una excepción si no se encuentra un modelo. Esto es especialmente ú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 la excepción ModelNotFoundException no es capturada, se enviará 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);
});

Retrieving or Creating Models

El método firstOrCreate intentará localizar un registro en la base de datos usando los pares de columnas/valores proporcionados. Si el modelo no se encuentra en la base de datos, se insertará un registro con los atributos resultantes de fusionar el primer array con el segundo 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 el modelo, se devolverá una nueva instancia del modelo. Ten en cuenta que el modelo devuelto por firstOrNew aún no ha sido guardado en la base de datos. Deberá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']
);

Retrieving Aggregates

Al interactuar con los modelos Eloquent, también puedes usar los métodos de agregación proporcionados por el query builder de Laravel, como count, sum, max y otros. Como es de 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');

Inserting and Updating Models

Inserts

Para insertar un nuevo registro en la base de datos, debes crear una nueva instancia del modelo y establecer los atributos en el modelo. Luego, llamar 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 establecerlas manualmente.

Alternativamente, puedes usar el método create para “guardar” un nuevo modelo usando una sola declaración PHP. La instancia del modelo insertado 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ás especificar una de las propiedades 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. Para aprender más sobre la asignación masiva, consulta la documentación sobre asignación masiva.

Updates

El método save también puede usarse para actualizar modelos que ya existen en la base de datos. Para actualizar un modelo, debes recuperarlo y establecer los atributos que deseas actualizar. Luego, debes 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();

Ocasionalmente, puede que necesites actualizar un modelo existente o crear un nuevo modelo si no existe ningún 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 siguiente ejemplo, si existe un vuelo con una ubicación de salida de Oakland y una ubicación de destino de San Diego, se actualizarán las columnas price y discounted. Si no existe tal vuelo, se creará un nuevo vuelo con los atributos resultantes de fusionar el primer array de argumentos con el segundo array de argumentos:

$flight = Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99, 'discounted' => 1]
);

Mass Updates

Las actualizaciones también se pueden realizar en modelos que coincidan con una consulta dada. En este ejemplo, todos los vuelos que están activos y tienen como destino San Diego serán marcados como retrasados:

Flight::where('active', 1)
      ->where('destination', 'San Diego')
      ->update(['delayed' => 1]);

El método update espera un array de pares columna-valor que representan las columnas que deben ser actualizadas. El método update devuelve el número de filas afectadas.

⚠️ Al realizar una actualización masiva a través de Eloquent, los eventos saving, saved, updating y updated del modelo no se dispararán para los modelos actualizados. Esto se debe a que los modelos nunca se recuperan realmente al realizar una actualización masiva.

Examining Attribute Changes

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 el modelo fue recuperado originalmente.

  • isDirty: Determina si alguno de los atributos del modelo ha sido modificado desde que el modelo fue recuperado. Puedes pasar un nombre de atributo específico o un array de atributos al método isDirty para verificar si alguno de los atributos está “sucio” (modificado).
  • isClean: Determina si un atributo ha permanecido sin cambios desde que el modelo fue recuperado. Este método también acepta un argumento opcional de atributo.
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 alguno de los atributos fue modificado cuando el modelo fue guardado por última vez dentro del ciclo de solicitud actual. Si es necesario, puedes pasar un nombre de atributo para verificar si un atributo específico fue cambiado:

$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 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...

Mass Assignment

Puedes usar el método create para ‘guardar’ un nuevo modelo con una sola instrucción PHP. La instancia del modelo insertado 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 esten protegidos contra vulnerabilidades de asignación masiva por defecto.

Una vulnerabilidad de asignación masiva ocurre cuando un usuario pasa un campo inesperado en una solicitud HTTP 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 escalar sus privilegios a administrador.

Por lo tanto, para comenzar, 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 usar el método create para insertar un nuevo registro en la base de datos. El método create devuelve la instancia del modelo recién creado.

$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']);

Mass Assignment and JSON Columns

Al asignar columnas JSON, cada clave de la columna debe especificarse en el array $fillable de tu modelo. Por razones de seguridad, Laravel no admite 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',
];

Allowing Mass Assignment

Si deseas hacer que todos tus atributos sean asignables en masa, puedes definir la propiedad $guarded de tu modelo como un array vacío. Si decides desproteger tu modelo, debes tener cuidado para siempre crear tu mismo los arrays que se pasan a los métodos fill, create y update de Eloquent.

/**
 * The attributes that aren't mass assignable.
 *
 * @var array
 */
protected $guarded = [];

Mass Assignment Exceptions

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 comportamiento es el esperado; sin embargo, durante el desarrollo local, puede llevar a confusión sobre por qué los cambios en el modelo no están surtiendo efecto.

Si lo deseas, puedes instruir a Laravel para que lance una excepción al intentar rellenar un atributo no asignable en masa invocando el método preventSilentlyDiscardingAttributes. Normalmente, este método debe ser invocado 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 usar 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 es un array de las columnas que deben ser actualizadas si ya existe un registro coincidente en la base de datos. El método upsert automáticamente establecerá 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']);

⚠️ Todas las bases de datos, excepto SQL Server, requieren que las columnas en el segundo argumento del método upsert tengan un índice “primary” o “unique”. Además, los controladores de bases de datos MariaDB y MySQL ignoran el segundo argumento del método upsert y siempre utilizan los índices “primary” y “unique” de la tabla para detectar registros existentes.

Deleting Models

Deleting Models

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 con el modelo. La operación truncate también reiniciará cualquier ID auto-incremental en la tabla asociada al modelo.

Flight::truncate();

Deleting an Existing Model by its Primary Key

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 necesidad de recuperarlo explícitamente llamando al método destroy. Además de aceptar una sola clave primaria, el método destroy acepta 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]));

⚠️ El método destroy carga cada modelo individualmente y llama al método delete para que los eventos de “deleting” y “deleted” se lancen correctamente para cada modelo.

Deleting Models Using Queries

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.

$deleted = Flight::where('active', 0)->delete();

⚠️ Al igual que con las actualizaciones masivas, las eliminaciones masivas no dispararán eventos del modelo para los modelos que se eliminan.

Soft Deleting

Además de eliminar realmente los registros de tu base de datos, Eloquent también puede “eliminar suavemente” los modelos. Cuando los modelos se eliminan suavemente, en realidad no se eliminan de la base de datos. En su lugar, se establece un atributo deleted_at en el modelo que indica la fecha y la hora en la que el modelo fue “eliminado”. Para habilitar las 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;
}

❕El trait SoftDeletes convertirá automáticamente el atributo deleted_at a una instancia de DateTime / Carbon para ti.

También debes agregar la columna deleted_at a tu tabla en la base de datos. El schema builder de Laravel contiene un método helper 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 del modelo en la base de datos permanecerá 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 la consulta.

Para determinar si una instancia de modelo ha sido eliminada suavemente, puedes usar el método trashed:

if ($flight->trashed()) {
    // ...
}

Restoring Soft Deleted Models

A veces, es posible que desees restaurar un modelo que ha sido eliminado. Para restaurar un modelo eliminado suavemente, 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 utilizar el método restore en una consulta para restaurar múltiples modelos.

Flight::withTrashed()
        ->where('airline_id', 1)
        ->restore();

⚠️ Al igual que con otras operaciones “masivas”, esto no disparará eventos del modelo para los modelos que se restauran:

El método restore también se puede usar con consultas con relaciones:

$flight->history()->restore();

Permanently Deleting Models

A veces, es posible que necesites eliminar verdaderamente 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();

El método forceDelete también se puede usar con consultas con relaciones:

$flight->history()->forceDelete();

Querying Soft Deleted Models

Including Soft Deleted Models

Como se mencionó anteriormente, los modelos eliminados suavemente se excluyen automáticamente de los resultados de las consultas. Sin embargo, puedes forzar que los modelos eliminados suavemente se incluyan 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 usar en consultas con relaciones:

$flight->history()->withTrashed()->get();

Retrieving Only Soft Deleted Models

El método onlyTrashed recuperará únicamente los modelos que han sido eliminados suavemente:

$flights = Flight::onlyTrashed()
                ->where('airline_id', 1)
                ->get();

Pruning Models

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 podar 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 el modelo sea eliminado. Puede ser útil para eliminar recursos adicionales asociados con el modelo, como archivos almacenados, antes de que el modelo se elimine permanentemente de la base de datos:

/**
 * Prepare the model for pruning.
 */
protected function pruning(): void
{
    // ...
}

Después de configurar tu modelo Prunable, debes programar el comando model:prune de Artisan en el archivo routes/console.php de tu aplicación. Puedes elegir el intervalo adecuado en el que este comando debe ejecutarse. Aquí tienes un ejemplo de cómo programar este comando:

use Illuminate\Support\Facades\Schedule;
 
Schedule::command('model:prune')->daily();

El comando model:prune detectará automáticamente los modelos “Prunable” 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 los modelos:

Schedule::command('model:prune', [
    '--model' => [Address::class, Flight::class],
])->daily();

Si deseas excluir ciertos modelos de la poda mientras podas 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 de eliminación ejecutando el comando model:prune con la opción --pretend. Al usar esta opción, el comando model:prune simplemente informará cuántos registros serían eliminados si el comando se ejecutará realmente, sin realizar ninguna eliminación:

php artisan model:prune --pretend

⚠️ Los modelos eliminados suavemente serán eliminados permanentemente (forceDelete) si coinciden con la consulta de eliminación.

Mass Pruning

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.

⚠️ El método pruning no se invocará, ni se disparará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 eliminación 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());
    }
}

Replicating Models

Puedes crear una copia no guardada de una instancia de un modelo existente utilizando el método replicate. Este método es especialmente útil cuando tienes instancias de modelos 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 la replicación 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'
]);

Query Scopes

Global Scopes

Los global scopes (alcances globales) te permiten añadir restricciones a todas las consultas para un modelo determinado. La funcionalidad de Soft Delete de Laravel utiliza alcances globales para recuperar únicamente los modelos “no eliminados” de la base de datos. Escribir tus propios alcances globales puede proporcionar una manera conveniente y sencilla de asegurarte de que cada consulta para un modelo específico reciba ciertas restricciones.

Generating Scopes

Para generar un nuevo alcance global, puedes invocar el comando make:scope de Artisan, el cual colocará el scope generado en el directorio app/Models/Scopes de tu aplicación:

php artisan make:scope AncientScope

Writing Global Scopes

Escribir un alcance global es sencillo. Primero, usa 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 agregar 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));
    }
}

⚠️Si tu alcance global está agregando columnas a la cláusula select de la consulta, deberías usar el método addSelect en lugar de select. Esto evitará el reemplazo no intencional de la cláusula select existente en la consulta.

Applying Global Scopes

Para asignar un alcance global a un modelo, simplemente debes 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, puedes registrar manualmente el scope global sobrescribiendo el método booted del modelo e invocando el método addGlobalScope del modelo. El método addGlobalScope acepta una instancia de tu global scope 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.
     */
    protected static function booted(): void
    {
        static::addGlobalScope(new AncientScope);
    }
}

Después de agregar 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

Anonymous Global Scopes

Eloquent también te permite definir scopes globales utilizando closures, lo cual es especialmente útil para scopes simples que no justifican una clase aparte. Al definir un scope global utilizando un closure, debes proporcionar un nombre al scope como 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));
        });
    }
}

Removing Global Scopes

Si deseas realizar una consulta específica sin que se apliquen scopes globales, puedes utilizar 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 global scope usando una closure, incluir el nombre que le pusiste.

User::withoutGlobalScope('ancient')->get();

Si necesitas borrar únicamente un scope o todos, puedes usar el método withoutGlobalScope

// Remove all of the global scopes...
User::withoutGlobalScopes()->get();
 
// Remove some of the global scopes...
User::withoutGlobalScopes([
    FirstScope::class, SecondScope::class
])->get();

Local Scopes

Los local scopes te permiten definir restricciones de consultas 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 scope, hay que poner scope como prefijo al método del modelo Eloquent.

Los scopes siempre deben devolver la misma instancia del constructor de consultas (query builder) 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);
    }
}

Utilizing a Local Scope

Una vez que el scope ha sido definido, puedes llamar a los métodos del scope al consultar el modelo. Sin embargo, no debes incluir el prefijo scope al llamar al método. Incluso puedes encadenar llamadas a varios scopes

use App\Models\User;
 
$users = User::popular()->active()->orderBy('created_at')->get();

Combinar múltiples scopes de modelos Eloquent mediante un operador or en una consulta 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 ser engorroso, Laravel proporciona un método orWhere de “alto nivel” que te permite encadenar scopes de manera fluida sin necesidad de usar closures:

$users = User::popular()->orWhere->active()->get();

Dynamic Scopes

A veces, es posible que desees definir un scope que acepte parámetros. Para comenzar, simplemente añade tus parámetros adicionales a la firma del método del scope. Los parámetros del scope 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 han añadido en la definición del método del scope, puedes pasar los argumentos al llamar al scope

$users = User::ofType('admin')->get();

Comparing Models

A veces, es posible que necesites determinar si dos modelos son “iguales” o no. Los métodos is y isNot pueden ser utilizados 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 e isNot también están disponibles cuando se utilizan las relaciones belongsTo, hasOne, morphTo y morphOne. Este método es particularmente útil cuando deseas comparar un modelo relacionado sin realizar una consulta para recuperar ese modelo:

if ($post->author()->is($user)) {
    // ...
}

Events

Los modelos de Eloquent lanzan varios eventos, permitiéndote ejecutar hooks 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 y replicating.

El evento retrieved se disparará cuando un modelo existente se recupere de la base de datos. Cuando un nuevo modelo se guarda por primera vez, se dispararán los eventos creating y created.

Los eventos updating / updated se dispararán cuando un modelo existente sea modificado y se llame al método save.

Los eventos saving / saved se dispararán cuando un modelo sea creado o actualizado, incluso si los atributos del modelo no han sido cambiados. Los nombres de eventos que terminan con -ing se disparan antes de que cualquier cambio al modelo se haya persistido, mientras que los eventos que terminan con -ed se disparan después de que los cambios al modelo se hayan persistido.

Para empezar a escuchar eventos de modelos, define una propiedad $dispatchesEvents en tu modelo de Eloquent. Esta propiedad mapea varios puntos del ciclo de vida de un modelo de Eloquent a tus propias clases de eventos. Cada clase de evento debería 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 listeners para manejar esos eventos.

⚠️Cuando realizas una actualización o eliminación masiva de registros a través de Eloquent, los eventos saved, updated, deleting y deleted del modelo 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.

Using Closures

En lugar de utilizar clases de eventos personalizadas, puedes registrar closures que se ejecuten cuando se lancen diversos eventos del modelo. Normalmente, deberías registrar estos cierres en el método boot 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 necesitas, puedes utilizar listeners que sean “queueable” (enviables a la cola) al registrar eventos de modelo. Esto le indicará a Laravel que ejecute el escuchador de eventos del modelo en segundo plano utilizando la queue de tu aplicación. Esta técnica es útil para manejar tareas que pueden ser procesadas de manera asíncrona, como el envío de notificaciones o el procesamiento de datos.

use function Illuminate\Events\queueable;
 
static::created(queueable(function (User $user) {
    // ...
}));

Observers

Defining Observers

Si estás escuchando muchos eventos en un modelo dado, puedes usar observadores para agrupar todos tus escuchadores en una sola clase. Las clases de observador 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 make:observer de Artisan es la manera más fácil de crear una nueva clase de observador.

php artisan make:observer UserObserver --model=User

El comando make:observer creará una nueva clase Observer en el directorio app/Observers. Si este directorio no existe, Artisan lo creará automáticamente por ti. El archivo generado tendrá una estructura básica que puedes usar para definir cómo manejar los eventos del modelo.

<?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 en Eloquent, puedes utilizar el atributo ObservedBy en el modelo correspondiente

use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
 
#[ObservedBy([UserObserver::class])]
class User extends Authenticatable
{
    //
}

También puedes registrar manualmente un observador 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);
}

❕Existen eventos adicionales a los que un observador puede escuchar, como saving y retrieved. Estos eventos se describen en la documentación de eventos

Observers and Database Transactions

Cuando los modelos se están creando dentro de una transacción de base de datos, es posible que desees indicar a un observador que solo ejecute sus manejadores de eventos después de que la transacción de base de datos se haya confirmado. Puedes lograr esto implementando la interfaz ShouldHandleEventsAfterCommit en tu observador. Si no hay una transacción de base de datos en curso, los manejadores de eventos se ejecutarán inmediatamente:

<?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
    {
        // ...
    }
}

Muting Events

En ocasiones, puede ser necesario ‘silenciar’ temporalmente todos los eventos generados 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 no lanzará eventos del modelo, y cualquier valor retornado por la función será el valor retornado por el método withoutEvents.

use App\Models\User;
 
$user = User::withoutEvents(function () {
    User::findOrFail(1)->delete();
 
    return User::find(2);
});

Saving a Single Model Without Events

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 suavemente’, ‘restaurar’ y ‘replicar’ un modelo dado sin despachar ningún evento:

$user->deleteQuietly();
$user->forceDeleteQuietly();
$user->restoreQuietly();