Saltar contenido

Eloquent: Relaciones

Introducción

Las tablas de una base de datos suelen estar relacionadas entre sí. Por ejemplo, una entrada de blog puede tener muchos comentarios o un pedido puede estar relacionado con el usuario que lo realizó. Eloquent facilita la gestión y el trabajo con estas relaciones, y soporta una gran variedad de relaciones comunes:

Definición de relaciones

Las relaciones Eloquent se definen como métodos en sus clases modelo Eloquent. Dado que las relaciones también sirven como constructores de consultas, definir relaciones como métodos proporciona una gran capacidad de encadenamiento de métodos y consultas. Por ejemplo, podemos encadenar restricciones de consulta adicionales en esta relación posts:

$user->posts()->where('active', 1)->get();

Pero, antes de sumergirnos demasiado en el uso de las relaciones, aprendamos a definir cada tipo de relación soportada por Eloquent.

Uno a uno - One To One

Una relación uno a uno es un tipo muy básico de relación de base de datos. Por ejemplo, un modelo de User puede estar asociado a un modelo de Phone. Para definir esta relación, colocaremos un método phone en el modelo User. El método phone debe llamar al método hasOne y devolver su resultado. El método hasOne está disponible para su modelo a través de la clase base Illuminate\Database\Eloquent\Model del modelo:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
/**
* Get the phone associated with the user.
*/
public function phone()
{
return $this->hasOne(Phone::class);
}
}

El primer argumento que se pasa al método hasOne es el nombre de la clase modelo relacionada. Una vez definida la relación, podemos recuperar el registro relacionado utilizando las propiedades dinámicas de Eloquent. Las propiedades dinámicas permiten acceder a los métodos de relación como si fueran propiedades definidas en el modelo:

$phone = User::find(1)->phone;

Eloquent determina la clave externa de la relación basándose en el nombre del modelo padre. En este caso, se asume automáticamente que el modelo Phone tiene una clave externa user_id. Si desea sobreescribir esta convención, puede pasar un segundo argumento al método hasOne:

return $this->hasOne(Phone::class, 'foreign_key');

Además, Eloquent asume que la clave externa debe tener un valor que coincida con la columna de clave primaria del padre. En otras palabras, Eloquent buscará el valor de la columna id del usuario en la columna user_id del registro Phone. Si desea que la relación utilice un valor de clave primaria distinto de id o de la propiedad $primaryKey de su modelo, puede pasar un tercer argumento al método hasOne:

return $this->hasOne(Phone::class, 'foreign_key', 'local_key');

Definición de la relación inversa

Así, podemos acceder al modelo Phone desde nuestro modelo User. A continuación, vamos a definir una relación en el modelo Phone que nos permitirá acceder al usuario propietario del teléfono. Podemos definir la inversa de una relación hasOne utilizando el método belongsTo:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo(User::class);
}
}

Al invocar el método user, Eloquent intentará encontrar un modelo User que tenga un id que coincida con la columna user_id del modelo Phone.

Eloquent determina el nombre de la clave externa examinando el nombre del método de relación y añadiendo _id como sufijo al nombre del método. Así, en este caso, Eloquent asume que el modelo Phone tiene una columna user_id. Sin embargo, si la clave externa del modelo Phone no es user_id, puede pasar un nombre de clave personalizado como segundo argumento del método belongsTo:

/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo(User::class, 'foreign_key');
}

Si el modelo padre no utiliza id como clave principal, o desea encontrar el modelo asociado utilizando una columna diferente, puede pasar un tercer argumento al método belongsTo especificando la clave personalizada de la tabla padre:

/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
}

Uno a muchos - One To Many

Una relación uno a muchos se utiliza para definir relaciones en las que un único modelo es el padre de uno o más modelos hijos. Por ejemplo, una entrada de blog puede tener un número infinito de comentarios. Al igual que el resto de relaciones de Eloquent, las relaciones uno a muchos se definen definiendo un método en el modelo de Eloquent:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Post extends Model
{
/**
* Get the comments for the blog post.
*/
public function comments()
{
return $this->hasMany(Comment::class);
}
}

Recuerde que Eloquent determinará automáticamente la columna de clave externa adecuada para el modelo Comment. Por convención, Eloquent tomará el nombre "snake case" del modelo padre y le añadirá el sufijo _id. Así, en este ejemplo, Eloquent asumirá que la columna de clave externa del modelo Comment es post_id.

Una vez definido el método de relación, podemos acceder a la colección de comentarios relacionados accediendo a la propiedad comments. Recuerda que, como Eloquent proporciona "propiedades de relación dinámicas", podemos acceder a los métodos de relación como si estuvieran definidos como propiedades en el modelo:

use App\Models\Post;
 
$comments = Post::find(1)->comments;
 
foreach ($comments as $comment) {
//
}

Puesto que todas las relaciones sirven también como constructores de consultas, puede añadir más restricciones a la consulta de la relación llamando al método comments y continuando la cadena de condiciones en la consulta:

$comment = Post::find(1)->comments()
->where('title', 'foo')
->first();

Al igual que con el método hasOne, también puedes sobreescribir las claves externas y locales pasando argumentos adicionales al método hasMany:

return $this->hasMany(Comment::class, 'foreign_key');
 
return $this->hasMany(Comment::class, 'foreign_key', 'local_key');

Uno a muchos (inverso) / Pertenece a

Ahora que podemos acceder a todos los comentarios de una entrada, definamos una relación que permita a un comentario acceder a su entrada padre. Para definir la inversa de una relación hasMany, define un método de relación en el modelo hijo que llame al método belongsTo:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Comment extends Model
{
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo(Post::class);
}
}

Una vez definida la relación, podemos recuperar el post padre de un comentario accediendo a la "propiedad de relación dinámica" post:

use App\Models\Comment;
 
$comment = Comment::find(1);
 
return $comment->post->title;

En el ejemplo anterior, Eloquent intentará encontrar un modelo Post que tenga un id que coincida con la columna post_id del modelo Comment.

Eloquent determina el nombre de la clave externa por defecto examinando el nombre del método de relación y añadiendo al nombre del método el sufijo _ seguido del nombre de la columna de clave primaria del modelo padre. Así, en este ejemplo, Eloquent asumirá que la clave externa del modelo Post en la tabla de comments es post_id.

Sin embargo, si la clave externa de su relación no sigue estas convenciones, puede pasar un nombre de clave externa personalizado como segundo argumento del método belongsTo:

/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo(Post::class, 'foreign_key');
}

Si su modelo padre no utiliza id como clave principal, o desea encontrar el modelo asociado utilizando una columna diferente, puede pasar un tercer argumento al método belongsTo especificando la clave personalizada de su tabla padre:

/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');
}

Modelos por defecto

Las relaciones belongsTo, hasOne, hasOneThrough y morphOne permiten definir un modelo predeterminado que se devolverá si la relación dada es null. Este patrón se conoce a menudo como patrón de objeto null y puede ayudar a eliminar comprobaciones condicionales en el código. En el siguiente ejemplo, la relación user devolverá un modelo App\Models\User vacío si no hay ningún usuario adjunto al modelo Post:

/**
* Get the author of the post.
*/
public function user()
{
return $this->belongsTo(User::class)->withDefault();
}

Para rellenar el modelo por defecto con atributos, puede pasar un array o closure al método withDefault:

/**
* Get the author of the post.
*/
public function user()
{
return $this->belongsTo(User::class)->withDefault([
'name' => 'Guest Author',
]);
}
 
/**
* Get the author of the post.
*/
public function user()
{
return $this->belongsTo(User::class)->withDefault(function ($user, $post) {
$user->name = 'Guest Author';
});
}

Consulta de relaciones belongsTo

Cuando consultes por los hijos de una relación "belongs to", puedes construir manualmente la cláusula where para recuperar los modelos Eloquent correspondientes:

use App\Models\Post;
 
$posts = Post::where('user_id', $user->id)->get();

Sin embargo, puede que le resulte más cómodo utilizar el método whereBelongsTo, que determinará automáticamente la relación adecuada y la clave externa para el modelo dado:

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

También puedes proporcionar una colección al método whereBelongsTo. Al hacerlo, Laravel recuperará los modelos que pertenezcan a cualquiera de los modelos padre dentro de la colección:

$users = User::where('vip', true)->get();
 
$posts = Post::whereBelongsTo($users)->get();

Por defecto, Laravel determinará la relación asociada con el modelo dado basándose en el nombre de la clase del modelo; sin embargo, puedes especificar el nombre de la relación manualmente proporcionándolo como segundo argumento al método whereBelongsTo:

$posts = Post::whereBelongsTo($user, 'author')->get();

Tiene Uno De Muchos

A veces un modelo puede tener muchos modelos relacionados, pero se desea recuperar fácilmente el modelo "más reciente" o "más antiguo" de la relación. Por ejemplo, un modelo de User puede estar relacionado con muchos modelos de Order, pero usted quiere definir una manera conveniente de interactuar con el pedido más reciente que el usuario ha realizado. Para ello puede utilizar el tipo de relación hasOne combinado con los métodos ofMany:

/**
* Get the user's most recent order.
*/
public function latestOrder()
{
return $this->hasOne(Order::class)->latestOfMany();
}

Del mismo modo, puede definir un método para recuperar el modelo "más antiguo", o el primer modelo relacionado de una relación:

/**
* Get the user's oldest order.
*/
public function oldestOrder()
{
return $this->hasOne(Order::class)->oldestOfMany();
}

Por defecto, los métodos latestOfMany y oldestOfMany recuperarán el modelo relacionado más reciente o más antiguo basándose en la clave primaria del modelo, que debe ser ordenable. Sin embargo, a veces es posible que desee recuperar un único modelo de una relación más amplia utilizando un criterio de ordenación diferente.

Por ejemplo, utilizando el método ofMany, puede recuperar el pedido más caro del usuario. El método ofMany acepta la columna ordenable como primer argumento y qué función agregada (min o max) aplicar al consultar el modelo relacionado:

/**
* Get the user's largest order.
*/
public function largestOrder()
{
return $this->hasOne(Order::class)->ofMany('price', 'max');
}

Advertencia
Debido a que PostgreSQL no soporta la ejecución de la función MAX contra columnas UUID, actualmente no es posible utilizar relaciones uno-de-muchos en combinación con columnas UUID de PostgreSQL.

Relaciones "Tiene Una De Muchas" Avanzadas

Es posible construir relaciones "tiene uno de muchos" más avanzadas. Por ejemplo, un modelo de Product puede tener muchos modelos de Price asociados que se conservan en el sistema incluso después de que se publiquen nuevos precios. Además, los nuevos datos de precios para el producto pueden publicarse por adelantado para que entren en vigor en una fecha futura a través de una columna published_at.

Así que, en resumen, necesitamos recuperar el último precio publicado cuando la fecha de publicación no esté en el futuro. Además, si dos precios tienen la misma fecha de publicación, preferiremos el precio con el mayor ID. Para ello, debemos pasar un array al método ofMany que contenga las columnas ordenables que determinan el último precio. Además, se proporcionará un closure como segundo argumento al método ofMany. Este closure se encargará de añadir restricciones adicionales de fecha de publicación a la consulta de relación:

/**
* Get the current pricing for the product.
*/
public function currentPricing()
{
return $this->hasOne(Price::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function ($query) {
$query->where('published_at', '<', now());
});
}

Tiene uno a través de - Has One Through

La relación "has-one-through" define una relación de uno a uno con otro modelo. Sin embargo, esta relación indica que el modelo declarante puede emparejarse con una instancia de otro modelo pasando por un tercer modelo.

Por ejemplo, en una aplicación de taller de reparación de vehículos, cada modelo de Mechanic puede estar asociado a un modelo de Car, y cada modelo de Car puede estar asociado a un modelo de Owner. Aunque el mecánico y el propietario no tienen una relación directa dentro de la base de datos, el mecánico puede acceder al propietario a través del modelo Car. Veamos las tablas necesarias para definir esta relación:

mechanics
id - integer
name - string
 
cars
id - integer
model - string
mechanic_id - integer
 
owners
id - integer
name - string
car_id - integer

Ahora que hemos examinado la estructura de tablas para la relación, definamos la relación en el modelo Mechanic:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Mechanic extends Model
{
/**
* Get the car's owner.
*/
public function carOwner()
{
return $this->hasOneThrough(Owner::class, Car::class);
}
}

El primer argumento pasado al método hasOneThrough es el nombre del modelo final al que deseamos acceder, mientras que el segundo argumento es el nombre del modelo intermedio.

Convenciones clave

Al realizar las consultas de la relación se utilizarán las convenciones típicas de Eloquent para claves externas. Si desea personalizar las claves de la relación, puede pasarlas como tercer y cuarto argumento al método hasOneThrough. El tercer argumento es el nombre de la clave ajena en el modelo intermedio. El cuarto argumento es el nombre de la clave externa en el modelo final. El quinto argumento es la clave local, mientras que el sexto argumento es la clave local del modelo intermedio:

class Mechanic extends Model
{
/**
* Get the car's owner.
*/
public function carOwner()
{
return $this->hasOneThrough(
Owner::class,
Car::class,
'mechanic_id', // Foreign key on the cars table...
'car_id', // Foreign key on the owners table...
'id', // Local key on the mechanics table...
'id' // Local key on the cars table...
);
}
}

Tiene muchos a través de - Has Many Through

La relación "has-many-through" proporciona una forma cómoda de acceder a relaciones distantes a través de una relación intermedia. Por ejemplo, supongamos que estamos construyendo una plataforma de despliegue como Laravel Vapor. Un modelo de Project podría acceder a muchos modelos de Deployment a través de un modelo intermedio de Environment. Usando este ejemplo, podrías reunir fácilmente todos los despliegues para un proyecto dado. Veamos las tablas necesarias para definir esta relación:

projects
id - integer
name - string
 
environments
id - integer
project_id - integer
name - string
 
deployments
id - integer
environment_id - integer
commit_hash - string

Ahora que hemos examinado la estructura de la tabla para la relación, definamos la relación en el modelo Project:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Project extends Model
{
/**
* Get all of the deployments for the project.
*/
public function deployments()
{
return $this->hasManyThrough(Deployment::class, Environment::class);
}
}

El primer argumento pasado al método hasManyThrough es el nombre del modelo final al que deseamos acceder, mientras que el segundo argumento es el nombre del modelo intermedio.

Aunque la tabla del modelo Deployment no contiene una columna project_id, la relación hasManyThrough proporciona acceso a los despliegues de un proyecto a través de $project->deployments. Para recuperar estos modelos, Eloquent inspecciona la columna project_id en la tabla intermedia del modelo Environment. Después de encontrar los IDs de entorno relevantes, se utilizan para consultar la tabla del modelo Deployment.

Convenciones sobre claves

Se utilizarán las convenciones típicas de claves externas de Eloquent al realizar las consultas de la relación. Si desea personalizar las claves de la relación, puede pasarlas como tercer y cuarto argumento al método hasManyThrough. El tercer argumento es el nombre de la clave ajena en el modelo intermedio. El cuarto argumento es el nombre de la clave externa en el modelo final. El quinto argumento es la clave local, mientras que el sexto argumento es la clave local del modelo intermedio:

class Project extends Model
{
public function deployments()
{
return $this->hasManyThrough(
Deployment::class,
Environment::class,
'project_id', // Foreign key on the environments table...
'environment_id', // Foreign key on the deployments table...
'id', // Local key on the projects table...
'id' // Local key on the environments table...
);
}
}

Relaciones de Muchos a Muchos - Many To Many

Las relaciones many-to-many son ligeramente más complicadas que las relaciones hasOne y hasMany. Un ejemplo de una relación muchos-a-muchos es un usuario que tiene muchos roles y esos roles también son compartidos por otros usuarios en la aplicación. Por ejemplo, a un usuario se le puede asignar el rol de "Autor" y "Editor"; sin embargo, esos roles también pueden ser asignados a otros usuarios. Así, un usuario tiene muchos roles y un rol tiene muchos usuarios.

Estructura de la tabla

Para definir esta relación, se necesitan tres tablas de base de datos: users, roles y role_user. La tabla role_user se deriva del orden alfabético de los nombres de los modelos relacionados y contiene las columnas user_id y role_id. Esta tabla se utiliza como tabla intermedia que vincula los usuarios y los roles.

Recuerde que, dado que un rol puede pertenecer a muchos usuarios, no podemos simplemente colocar una columna user_id en la tabla roles. Esto significaría que un rol sólo podría pertenecer a un único usuario. Para dar soporte a roles asignados a múltiples usuarios, se necesita la tabla role_user. Podemos resumir la estructura de tablas de la relación de la siguiente manera:

users
id - integer
name - string
 
roles
id - integer
name - string
 
role_user
user_id - integer
role_id - integer

Estructura del modelo

Las relaciones muchos-a-muchos se definen escribiendo un método que devuelva el resultado del método belongsToMany. El método belongsToMany es proporcionado por la clase base Illuminate\Database\Eloquent\Model que es utilizada por todos los modelos Eloquent de su aplicación. Por ejemplo, definamos un método roles en nuestro modelo User. El primer argumento pasado a este método es el nombre de la clase modelo relacionada:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
/**
* The roles that belong to the user.
*/
public function roles()
{
return $this->belongsToMany(Role::class);
}
}

Una vez definida la relación, puede acceder a los roles del usuario utilizando la propiedad de relación dinámica roles:

use App\Models\User;
 
$user = User::find(1);
 
foreach ($user->roles as $role) {
//
}

Dado que todas las relaciones también sirven como constructores de consultas, puede añadir más restricciones a la consulta de la relación llamando al método roles y continuando encadenando condiciones a la consulta:

$roles = User::find(1)->roles()->orderBy('name')->get();

Para determinar el nombre de tabla de la tabla intermedia de la relación, Eloquent unirá los dos nombres de modelos relacionados en orden alfabético. Sin embargo, puede sobreescribir esta convención. Puede hacerlo pasando un segundo argumento al método belongsToMany:

return $this->belongsToMany(Role::class, 'role_user');

Además de personalizar el nombre de la tabla intermedia, también puede personalizar los nombres de las columnas de las claves de la tabla pasando argumentos adicionales al método belongsToMany. El tercer argumento es el nombre de la clave externa del modelo en el que se está definiendo la relación, mientras que el cuarto argumento es el nombre de la clave externa del modelo al que se está uniendo:

return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');

Definición de la relación inversa

Para definir la "inversa" de una relación muchos-a-muchos, debes definir un método en el modelo relacionado que también devuelva el resultado del método belongsToMany. Para completar nuestro ejemplo usuario / rol, definamos el método users en el modelo Role:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Role extends Model
{
/**
* The users that belong to the role.
*/
public function users()
{
return $this->belongsToMany(User::class);
}
}

Como se puede ver, la relación se define exactamente igual que su contraparte en el modelo User con la excepción de hacer referencia al modelo App\Models\User. Dado que estamos reutilizando el método belongsToMany, todas las opciones habituales de personalización de tablas y claves están disponibles al definir la "inversa" de las relaciones muchos-a-muchos.

Recuperación de Columnas de Tablas Intermedias

Como ya ha aprendido, trabajar con relaciones muchos-a-muchos requiere la presencia de una tabla intermedia. Eloquent proporciona algunas formas muy útiles de interactuar con esta tabla. Por ejemplo, supongamos que nuestro modelo User tiene muchos modelos Role con los que está relacionado. Después de acceder a esta relación, podemos acceder a la tabla intermedia utilizando el atributo pivot de los modelos:

use App\Models\User;
 
$user = User::find(1);
 
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}

Observe que a cada modelo de Role que recuperamos se le asigna automáticamente un atributo pivot. Este atributo contiene un modelo que representa la tabla intermedia.

Por defecto, sólo las claves del modelo estarán presentes en el modelo pivot. Si su tabla intermedia contiene atributos adicionales, deberá especificarlos al definir la relación:

return $this->belongsToMany(Role::class)->withPivot('active', 'created_by');

Si desea que su tabla intermedia tenga fechas de creación (created_at) y actualización (updated_at) mantenidas automáticamente por Eloquent, llame al método withTimestamps cuando defina la relación:

return $this->belongsToMany(Role::class)->withTimestamps();

Advertencia
Las tablas intermedias que utilizan las marcas de tiempo mantenidas automáticamente por Eloquent deben tener columnas de marca de tiempo created_at y updated_at.

Personalización del nombre del atributo pivot

Como se ha indicado anteriormente, se puede acceder a los atributos de la tabla intermedia en los modelos a través del atributo pivot. Sin embargo, puedes personalizar el nombre de este atributo para que refleje mejor su propósito dentro de tu aplicación.

Por ejemplo, si su aplicación contiene usuarios que pueden suscribirse a podcasts, es probable que tenga una relación de muchos a muchos entre usuarios y podcasts. Si este es el caso, es posible que desee cambiar el nombre de su atributo de tabla intermedia a subscription en lugar de pivot. Para ello, utilice el método as al definir la relación:

return $this->belongsToMany(Podcast::class)
->as('subscription')
->withTimestamps();

Una vez especificado el atributo personalizado de la tabla intermedia, puede acceder a los datos de la tabla intermedia utilizando el nombre personalizado:

$users = User::with('podcasts')->get();
 
foreach ($users->flatMap->podcasts as $podcast) {
echo $podcast->subscription->created_at;
}

Filtrado de consultas mediante columnas de tablas intermedias

También puede filtrar los resultados devueltos por las consultas de relación belongsToMany utilizando los métodos wherePivot, wherePivotIn, wherePivotNotIn, wherePivotBetween, wherePivotNotBetween, wherePivotNull y wherePivotNotNull al definir la relación:

return $this->belongsToMany(Role::class)
->wherePivot('approved', 1);
 
return $this->belongsToMany(Role::class)
->wherePivotIn('priority', [1, 2]);
 
return $this->belongsToMany(Role::class)
->wherePivotNotIn('priority', [1, 2]);
 
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);
 
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);
 
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNull('expired_at');
 
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNotNull('expired_at');

Ordenación de Consultas mediante Columnas de Tablas Intermedias

Puede ordenar los resultados devueltos por las consultas de relación belongsToMany utilizando el método orderByPivot. En el siguiente ejemplo, recuperaremos todas las últimas insignias (Badges) del usuario:

return $this->belongsToMany(Badge::class)
->where('rank', 'gold')
->orderByPivot('created_at', 'desc');

Definición de Modelos Personalizados de Tablas Intermedias

Si desea definir un modelo personalizado para representar la tabla intermedia de su relación muchos-a-muchos, puede llamar al método using cuando defina la relación. Los modelos pivotantes personalizados te dan la oportunidad de definir comportamientos adicionales en el modelo pivotante, como métodos y lanzamientos.

Los modelos pivotantes personalizados de muchos a muchos deben extender la clase Illuminate\Database\Eloquent\Relations\Pivot, mientras que los modelos pivotantes polimórficos personalizados de muchos a muchos deben extender la clase Illuminate\Database\Eloquent\Relations\MorphPivot. Por ejemplo, podemos definir un modelo Role que utilice un modelo pivot personalizado RoleUser:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Role extends Model
{
/**
* The users that belong to the role.
*/
public function users()
{
return $this->belongsToMany(User::class)->using(RoleUser::class);
}
}

Al definir el modelo RoleUser, debe extender la clase Illuminate\Database\Eloquent\Relations\Pivot:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Relations\Pivot;
 
class RoleUser extends Pivot
{
//
}

Advertencia
Los modelos pivotantes no pueden utilizar el trait SoftDeletes. Si necesita borrar suavemente registros pivotantes, considere convertir su modelo pivotante en un modelo real de Eloquent.

Modelos dinámicos personalizados e incremento de ID

Si ha definido una relación de muchos a muchos que utiliza un modelo pivotante personalizado, y ese modelo pivotante tiene una clave primaria autoincrementable, debe asegurarse de que su clase de modelo pivotante personalizado define una propiedad incrementing que se establece en true.

/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = true;

Relaciones polimórficas

Una relación polimórfica permite que el modelo hijo pertenezca a más de un tipo de modelo utilizando una única asociación. Por ejemplo, imagine que está creando una aplicación que permite a los usuarios compartir entradas de blog y vídeos. En una aplicación de este tipo, un modelo Comment podría pertenecer a los modelos Post y Video.

Uno a uno (polimórfico) - One To One (Polymorphic)

Estructura de la tabla

Una relación polimórfica uno a uno es similar a una típica relación uno a uno; sin embargo, el modelo hijo puede pertenecer a más de un tipo de modelo utilizando una única asociación. Por ejemplo, una entrada de blog y un usuario pueden compartir una relación polimórfica con un modelo de imagen. El uso de una relación polimórfica uno a uno le permite tener una única tabla de imágenes únicas que pueden estar asociadas con entradas y usuarios. Primero, examinemos la estructura de la tabla:

posts
id - integer
name - string
 
users
id - integer
name - string
 
images
id - integer
url - string
imageable_id - integer
imageable_type - string

Observe las columnas imageable_id e imageable_type en la tabla images. La columna imageable_id contendrá el valor ID de la entrada o del usuario, mientras que la columna imageable_type contendrá el nombre de la clase del modelo padre. Eloquent utiliza la columna imageable_type para determinar qué "tipo" de modelo padre debe devolverse al acceder a la relación imageable. En este caso, la columna contendría App\Models\Post o App\Models\User.

Estructura del modelo

A continuación, examinemos las definiciones de modelo necesarias para construir esta relación:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Image extends Model
{
/**
* Get the parent imageable model (user or post).
*/
public function imageable()
{
return $this->morphTo();
}
}
 
class Post extends Model
{
/**
* Get the post's image.
*/
public function image()
{
return $this->morphOne(Image::class, 'imageable');
}
}
 
class User extends Model
{
/**
* Get the user's image.
*/
public function image()
{
return $this->morphOne(Image::class, 'imageable');
}
}

Recuperación de la relación

Una vez definidos la tabla y los modelos de la base de datos, puede acceder a las relaciones a través de los modelos. Por ejemplo, para recuperar la imagen de una entrada, podemos acceder a la propiedad de relación dinámica imagen:

use App\Models\Post;
 
$post = Post::find(1);
 
$image = $post->image;

Puede recuperar el padre del modelo polimórfico accediendo al nombre del método que realiza la llamada a morphTo. En este caso, es el método imageable del modelo Image. Por lo tanto, accederemos a ese método como una propiedad de relación dinámica:

use App\Models\Image;
 
$image = Image::find(1);
 
$imageable = $image->imageable;

La relación imageable en el modelo Image devolverá una instancia Post o User, dependiendo del tipo de modelo al que pertenezca la imagen.

Convenciones sobre claves

Si es necesario, puede especificar el nombre de las columnas "id" y "type" utilizadas por su modelo hijo polimórfico. Si lo hace, asegúrese de pasar siempre el nombre de la relación como primer argumento al método morphTo. Normalmente, este valor debe coincidir con el nombre del método, por lo que puede utilizar la constante __FUNCTION__ de PHP:

/**
* Get the model that the image belongs to.
*/
public function imageable()
{
return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id');
}

De uno a muchos (polimórfico) - One To Many (Polymorphic)

Estructura de la tabla

Una relación polimórfica uno-a-muchos es similar a una típica relación uno-a-muchos; sin embargo, el modelo hijo puede pertenecer a más de un tipo de modelo utilizando una única asociación. Por ejemplo, imagine que los usuarios de su aplicación pueden "comentar" entradas y vídeos. Utilizando relaciones polimórficas, puede utilizar una única tabla de comments para contener los comentarios tanto de las entradas como de los vídeos. En primer lugar, examinemos la estructura de tablas necesaria para construir esta relación:

posts
id - integer
title - string
body - text
 
videos
id - integer
title - string
url - string
 
comments
id - integer
body - text
commentable_id - integer
commentable_type - string

Estructura del modelo

A continuación, vamos a examinar las definiciones del modelo necesarias para construir esta relación:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Comment extends Model
{
/**
* Get the parent commentable model (post or video).
*/
public function commentable()
{
return $this->morphTo();
}
}
 
class Post extends Model
{
/**
* Get all of the post's comments.
*/
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
 
class Video extends Model
{
/**
* Get all of the video's comments.
*/
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}

Recuperación de la relación

Una vez definidos la tabla de base de datos y los modelos, puede acceder a las relaciones a través de las propiedades de relación dinámica del modelo. Por ejemplo, para acceder a todos los comentarios de una entrada, podemos utilizar la propiedad dinámica comments:

use App\Models\Post;
 
$post = Post::find(1);
 
foreach ($post->comments as $comment) {
//
}

También puede recuperar el padre de un modelo hijo polimórfico accediendo al nombre del método que realiza la llamada a morphTo. En este caso, es el método commentable del modelo Comment. Por lo tanto, accederemos a ese método como una propiedad de relación dinámica para acceder al modelo padre del comentario:

use App\Models\Comment;
 
$comment = Comment::find(1);
 
$commentable = $comment->commentable;

La relación commentable en el modelo Comment devolverá una instancia Post o Video, dependiendo de qué tipo de modelo sea el padre del comentario.

Uno de muchos (polimórfico) - One Of Many (Polymorphic)

A veces, un modelo puede tener muchos modelos relacionados y, sin embargo, se desea recuperar fácilmente el modelo relacionado "más reciente" o "más antiguo" de la relación. Por ejemplo, un modelo de User puede estar relacionado con muchos modelos de Imagen, pero usted quiere interactuar con la imagen más reciente que el usuario haya subido. Para ello puede utilizar el tipo de relación morphOne combinado con los métodos ofMany:

/**
* Get the user's most recent image.
*/
public function latestImage()
{
return $this->morphOne(Image::class, 'imageable')->latestOfMany();
}

Del mismo modo, puede definir un método para recuperar el modelo "más antiguo", o el primer modelo relacionado de una relación:

/**
* Get the user's oldest image.
*/
public function oldestImage()
{
return $this->morphOne(Image::class, 'imageable')->oldestOfMany();
}

Por defecto, los métodos latestOfMany y oldestOfMany recuperarán el modelo relacionado más reciente o más antiguo basándose en la clave primaria del modelo, que debe ser ordenable. Sin embargo, a veces es posible que desee recuperar un único modelo de una relación más amplia utilizando un criterio de ordenación diferente.

Por ejemplo, utilizando el método ofMany, puede recuperar la imagen que más le ha gustado al usuario. El método ofMany acepta la columna ordenable como primer argumento y la función agregada (min o max) que se debe aplicar al consultar el modelo relacionado:

/**
* Get the user's most popular image.
*/
public function bestImage()
{
return $this->morphOne(Image::class, 'imageable')->ofMany('likes', 'max');
}

Nota
Es posible construir relaciones "uno de muchos" más avanzadas. Para más información, consulte la documentación has one of many.

Muchos a muchos (polimórfico) - Many To Many (Polymorphic)

Estructura de la tabla

Las relaciones polimórficas de muchos a muchos son ligeramente más complicadas que las relaciones "morph one" y "morph many". Por ejemplo, un modelo Post y un modelo Video podrían compartir una relación polimórfica con un modelo Tag. El uso de una relación polimórfica de muchos a muchos en esta situación permitiría a su aplicación tener una única tabla de etiquetas únicas que pueden estar asociadas con publicaciones o vídeos. En primer lugar, examinemos la estructura de tabla necesaria para crear esta relación:

posts
id - integer
name - string
 
videos
id - integer
name - string
 
tags
id - integer
name - string
 
taggables
tag_id - integer
taggable_id - integer
taggable_type - string

Nota
Antes de sumergirse en las relaciones polimórficas de muchos a muchos, puede que le resulte útil leer la documentación sobre las relaciones normales de muchos a muchos.

Estructura del modelo

A continuación, estamos listos para definir las relaciones en los modelos. Los modelos Post y Video contendrán ambos un método tags que llama al método morphToMany proporcionado por la clase modelo base de Eloquent.

El método morphToMany acepta el nombre del modelo relacionado así como el "nombre de la relación". Basándonos en el nombre que asignamos al nombre de nuestra tabla intermedia y a las claves que contiene, nos referiremos a la relación como "taggable":

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Post extends Model
{
/**
* Get all of the tags for the post.
*/
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}

Definición de la relación inversa

A continuación, en el modelo Tag, deberás definir un método para cada uno de sus posibles modelos padre. Así, en este ejemplo, definiremos un método posts y un método videos. Ambos métodos deben devolver el resultado del método morphedByMany.

El método morphedByMany acepta el nombre del modelo relacionado así como el "nombre de la relación". En base al nombre que asignamos a nuestra tabla intermedia y a las claves que contiene, nos referiremos a la relación como "taggable":

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Tag extends Model
{
/**
* Get all of the posts that are assigned this tag.
*/
public function posts()
{
return $this->morphedByMany(Post::class, 'taggable');
}
 
/**
* Get all of the videos that are assigned this tag.
*/
public function videos()
{
return $this->morphedByMany(Video::class, 'taggable');
}
}

Recuperación de la relación

Una vez que la tabla de base de datos y los modelos están definidos, puede acceder a las relaciones a través de sus modelos. Por ejemplo, para acceder a todas las etiquetas de una entrada, puede utilizar la propiedad de relación dinámica tags:

use App\Models\Post;
 
$post = Post::find(1);
 
foreach ($post->tags as $tag) {
//
}

Puede recuperar el padre de una relación polimórfica desde el modelo polimórfico hijo accediendo al nombre del método que realiza la llamada a morphedByMany. En este caso, se trata de los métodos posts o videos del modelo Tag:

use App\Models\Tag;
 
$tag = Tag::find(1);
 
foreach ($tag->posts as $post) {
//
}
 
foreach ($tag->videos as $video) {
//
}

Tipos Polimórficos Personalizados

Por defecto, Laravel utilizará el nombre completo de la clase para almacenar el "tipo" del modelo relacionado. Por ejemplo, dado el ejemplo de relación uno-a-muchos anterior, donde un modelo de Comment puede pertenecer a un modelo de Post o de Video, el commentable_type por defecto sería App\Models\Post o App\Models\Video, respectivamente. Sin embargo, puede que desee desacoplar estos valores de la estructura interna de su aplicación.

Por ejemplo, en lugar de utilizar los nombres de los modelos como el "tipo", podemos utilizar cadenas simples como post y video. De este modo, los valores polimórficos de la columna "tipo" de nuestra base de datos seguirán siendo válidos aunque se cambie el nombre de los modelos:

use Illuminate\Database\Eloquent\Relations\Relation;
 
Relation::enforceMorphMap([
'post' => 'App\Models\Post',
'video' => 'App\Models\Video',
]);

Puede llamar al método enforceMorphMap en el método boot de su clase App\Providers\AppServiceProvider o crear un proveedor de servicios independiente si lo desea.

Puede determinar el alias morph de un modelo dado en tiempo de ejecución utilizando el método getMorphClass del modelo. A la inversa, puedes determinar el nombre de clase completo asociado a un alias morph utilizando el método Relation::getMorphedModel:

use Illuminate\Database\Eloquent\Relations\Relation;
 
$alias = $post->getMorphClass();
 
$class = Relation::getMorphedModel($alias);

Advertencia
Cuando añada un "morph map" a su aplicación existente, cada valor de columna morphable *_type en su base de datos que todavía contenga una clase completamente cualificada necesitará ser convertida a su nombre "map".

Relaciones Dinámicas

Puede utilizar el método resolveRelationUsing para definir relaciones entre modelos Eloquent en tiempo de ejecución. Aunque no se suele recomendar para el desarrollo normal de aplicaciones, esto puede ser útil ocasionalmente al desarrollar paquetes Laravel.

El método resolveRelationUsing acepta el nombre de la relación deseada como primer argumento. El segundo argumento que se pasa al método debe ser un closure que acepte la instancia del modelo y devuelva una definición de relación válida de Eloquent. Normalmente, las relaciones dinámicas se configuran en el método boot de un proveedor de servicios:

use App\Models\Order;
use App\Models\Customer;
 
Order::resolveRelationUsing('customer', function ($orderModel) {
return $orderModel->belongsTo(Customer::class, 'customer_id');
});

Advertencia
Cuando definas relaciones dinámicas, proporciona siempre argumentos explícitos de nombre de clave a los métodos de relación de Eloquent.

Consulta de Relaciones

Dado que todas las relaciones de Eloquent se definen mediante métodos, puede llamar a esos métodos para obtener una instancia de la relación sin ejecutar realmente una consulta para cargar los modelos relacionados. Además, todos los tipos de relaciones Eloquent también sirven como constructores de consultas, lo que le permite seguir encadenando restricciones a la consulta de relación antes de ejecutar finalmente la consulta SQL contra su base de datos.

Por ejemplo, imagina una aplicación de blog en la que un modelo User tiene muchos modelos Post asociados:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
/**
* Get all of the posts for the user.
*/
public function posts()
{
return $this->hasMany(Post::class);
}
}

Puede consultar la relación posts y añadir restricciones adicionales a la relación de esta forma:

use App\Models\User;
 
$user = User::find(1);
 
$user->posts()->where('active', 1)->get();

Puedes utilizar cualquiera de los métodos del constructor del consultas de Laravel en la relación, así que asegúrate de explorar la documentación del constructor de consultas para conocer todos los métodos disponibles.

Encadenamiento de cláusulas orWhere tras relaciones

Como se muestra en el ejemplo anterior, puede añadir restricciones adicionales a las relaciones cuando las consulte. Sin embargo, tenga cuidado al encadenar cláusulas orWhere en una relación, ya que las cláusulas orWhere se agruparán lógicamente en el mismo nivel que la restricción de relación:

$user->posts()
->where('active', 1)
->orWhere('votes', '>=', 100)
->get();

El ejemplo anterior generará el siguiente SQL. Como puede ver, la cláusula or indica a la consulta que devuelva cualquier usuario con más de 100 votos. La consulta ya no está restringida a un usuario específico:

select *
from posts
where user_id = ? and active = 1 or votes >= 100

En la mayoría de las situaciones, debería utilizar grupos lógicos para agrupar las comprobaciones condicionales entre paréntesis:

use Illuminate\Database\Eloquent\Builder;
 
$user->posts()
->where(function (Builder $query) {
return $query->where('active', 1)
->orWhere('votes', '>=', 100);
})
->get();

El ejemplo anterior generará el siguiente SQL. Observe que la agrupación lógica ha agrupado correctamente las restricciones y la consulta sigue estando restringida a un usuario específico:

select *
from posts
where user_id = ? and (active = 1 or votes >= 100)

Métodos de Relación vs. Propiedades Dinámicas

Si no necesita añadir restricciones adicionales a una consulta de relación de Eloquent, puede acceder a la relación como si fuera una propiedad. Por ejemplo, si seguimos utilizando nuestros modelos de ejemplo User y Post, podemos acceder a todas las publicaciones de un usuario de la siguiente manera:

use App\Models\User;
 
$user = User::find(1);
 
foreach ($user->posts as $post) {
//
}

Las propiedades de relación dinámicas realizan una "carga perezosa", lo que significa que sólo cargarán sus datos de relación cuando realmente acceda a ellos. Por este motivo, los desarrolladores suelen utilizar la precarga de las relaciones a las que saben que se accederá después de cargar el modelo. La carga anticipada reduce significativamente las consultas SQL que deben ejecutarse para cargar las relaciones de un modelo.

Consulta de la Existencia de Relaciones

Al recuperar registros del modelo, puede que desee limitar los resultados en función de la existencia de una relación. Por ejemplo, imagine que desea recuperar todas las entradas del blog que tengan al menos un comentario. Para ello, puede pasar el nombre de la relación a los métodos has y orHas:

use App\Models\Post;
 
// Retrieve all posts that have at least one comment...
$posts = Post::has('comments')->get();

También puede especificar un operador y un valor de recuento para personalizar aún más la consulta:

// Retrieve all posts that have three or more comments...
$posts = Post::has('comments', '>=', 3)->get();

Las sentencias has anidadas pueden construirse utilizando la notación "punto". Por ejemplo, puede recuperar todas las entradas que tengan al menos un comentario con al menos una imagen:

// Retrieve posts that have at least one comment with images...
$posts = Post::has('comments.images')->get();

Si necesitas aún más potencia, puedes utilizar los métodos whereHas y orWhereHas para definir restricciones de consulta adicionales en tus consultas has, como inspeccionar el contenido de un comentario:

use Illuminate\Database\Eloquent\Builder;
 
// Retrieve posts with at least one comment containing words like code%...
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();
 
// Retrieve posts with at least ten comments containing words like code%...
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
}, '>=', 10)->get();

Advertencia
Eloquent no admite actualmente consultas sobre la existencia de relaciones entre bases de datos. Las relaciones deben existir dentro de la misma base de datos.

Consultas de existencia de relaciones en línea

Si desea consultar la existencia de una relación con una única y sencilla condición where adjunta a la consulta de relación, puede que le resulte más cómodo utilizar los métodos whereRelation, orWhereRelation, whereMorphRelation y orWhereMorphRelation. Por ejemplo, podemos consultar todas las entradas que tienen comentarios no aprobados:

use App\Models\Post;
 
$posts = Post::whereRelation('comments', 'is_approved', false)->get();

Por supuesto, al igual que las llamadas al método where del constructor de consultas, también puede especificar un operador:

$posts = Post::whereRelation(
'comments', 'created_at', '>=', now()->subHour()
)->get();

Consulta de Ausencia de Relación

Al recuperar registros modelo, puede que desee limitar sus resultados basándose en la ausencia de una relación. Por ejemplo, imagine que desea recuperar todas las entradas de blog que no tienen comentarios. Para ello, puede pasar el nombre de la relación a los métodos doesntHave y orDoesntHave:

use App\Models\Post;
 
$posts = Post::doesntHave('comments')->get();

Si necesita aún más potencia, puede utilizar los métodos whereDoesntHave y orWhereDoesntHave para añadir restricciones de consulta adicionales a sus consultas doesntHave, como inspeccionar el contenido de un comentario:

use Illuminate\Database\Eloquent\Builder;
 
$posts = Post::whereDoesntHave('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();

Puede utilizar la notación "punto" para ejecutar una consulta sobre una relación anidada. Por ejemplo, la siguiente consulta recuperará todas las entradas que no tengan comentarios; sin embargo, las entradas que tengan comentarios de autores que no estén vetados se incluirán en los resultados:

use Illuminate\Database\Eloquent\Builder;
 
$posts = Post::whereDoesntHave('comments.author', function (Builder $query) {
$query->where('banned', 0);
})->get();

Consulta de Relaciones Morph To

Para consultar la existencia de relaciones "morph to", puede utilizar los métodos whereHasMorph y whereDoesntHaveMorph. Estos métodos aceptan el nombre de la relación como primer argumento. A continuación, los métodos aceptan los nombres de los modelos relacionados que desea incluir en la consulta. Por último, puede proporcionar un closure que personalice la consulta de la relación:

use App\Models\Comment;
use App\Models\Post;
use App\Models\Video;
use Illuminate\Database\Eloquent\Builder;
 
// Retrieve comments associated to posts or videos with a title like code%...
$comments = Comment::whereHasMorph(
'commentable',
[Post::class, Video::class],
function (Builder $query) {
$query->where('title', 'like', 'code%');
}
)->get();
 
// Retrieve comments associated to posts with a title not like code%...
$comments = Comment::whereDoesntHaveMorph(
'commentable',
Post::class,
function (Builder $query) {
$query->where('title', 'like', 'code%');
}
)->get();

En ocasiones puede ser necesario añadir restricciones de consulta basadas en el "tipo" del modelo polimórfico relacionado. El closure pasado al método whereHasMorph puede recibir un valor $type como segundo argumento. Este argumento permite inspeccionar el "tipo" de la consulta que se está construyendo:

use Illuminate\Database\Eloquent\Builder;
 
$comments = Comment::whereHasMorph(
'commentable',
[Post::class, Video::class],
function (Builder $query, $type) {
$column = $type === Post::class ? 'content' : 'title';
 
$query->where($column, 'like', 'code%');
}
)->get();

Consulta de todos los modelos relacionados

En lugar de pasar un array de posibles modelos polimórficos, puede proporcionar * como valor comodín. Esto le indicará a Laravel que recupere todos los tipos polimórficos posibles de la base de datos. Laravel ejecutará una consulta adicional para realizar esta operación:

use Illuminate\Database\Eloquent\Builder;
 
$comments = Comment::whereHasMorph('commentable', '*', function (Builder $query) {
$query->where('title', 'like', 'foo%');
})->get();

Agregación de modelos relacionados

Recuento de modelos relacionados

A veces es posible que desee contar el número de modelos relacionados para una relación dada sin cargar realmente los modelos. Para ello, puede utilizar el método withCount. El método withCount colocará un atributo {relation}_count en los modelos resultantes:

use App\Models\Post;
 
$posts = Post::withCount('comments')->get();
 
foreach ($posts as $post) {
echo $post->comments_count;
}

Al pasar un array al método withCount, puede añadir los "recuentos" de múltiples relaciones, así como añadir restricciones adicionales a las consultas:

use Illuminate\Database\Eloquent\Builder;
 
$posts = Post::withCount(['votes', 'comments' => function (Builder $query) {
$query->where('content', 'like', 'code%');
}])->get();
 
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;

También puede poner un alias al resultado del recuento de la relación, permitiendo múltiples recuentos en la misma relación:

use Illuminate\Database\Eloquent\Builder;
 
$posts = Post::withCount([
'comments',
'comments as pending_comments_count' => function (Builder $query) {
$query->where('approved', false);
},
])->get();
 
echo $posts[0]->comments_count;
echo $posts[0]->pending_comments_count;

Carga de recuento diferida

Utilizando el método loadCount, puede cargar un recuento de relaciones después de que el modelo padre haya sido recuperado:

$book = Book::first();
 
$book->loadCount('genres');

Si necesita establecer restricciones adicionales en la consulta de recuento, puede pasar un array con las claves de las relaciones que desea contar. Los valores array deben ser closures que reciban la instancia del constructor de consultas:

$book->loadCount(['reviews' => function ($query) {
$query->where('rating', 5);
}])

Recuento de relaciones y sentencias select personalizadas

Si combina withCount con una sentencia select, asegúrese de llamar a withCount después del método select:

$posts = Post::select(['title', 'body'])
->withCount('comments')
->get();

Otras funciones de agregación

Además del método withCount, Eloquent proporciona los métodos withMin, withMax, withAvg, withSum y withExists. Estos métodos colocarán un atributo {relation}_{function}_{column} en los modelos resultantes:

use App\Models\Post;
 
$posts = Post::withSum('comments', 'votes')->get();
 
foreach ($posts as $post) {
echo $post->comments_sum_votes;
}

Si desea acceder al resultado de la función agregada utilizando otro nombre, puede especificar su propio alias:

$posts = Post::withSum('comments as total_comments', 'votes')->get();
 
foreach ($posts as $post) {
echo $post->total_comments;
}

Al igual que el método loadCount, también existen versiones diferidas de estos métodos. Estas operaciones agregadas adicionales pueden realizarse sobre modelos Eloquent que ya han sido recuperados:

$post = Post::first();
 
$post->loadSum('comments', 'votes');

Si combina estos métodos agregados con una sentencia select, asegúrese de llamar a los métodos agregados después del método select:

$posts = Post::select(['title', 'body'])
->withExists('comments')
->get();

Recuento de modelos relacionados en relaciones Morph To

Si desea pre-cargar una relación "morph to", así como los recuentos de modelos relacionados para las diversas entidades que pueden ser devueltas por esa relación, puede utilizar el método with en combinación con el método morphWithCount de la relación morphTo.

En este ejemplo, vamos a suponer que los modelos Photo y Post pueden crear modelos ActivityFeed. Asumiremos que el modelo ActivityFeed define una relación "morph to" llamada parentable que nos permite recuperar el modelo padre Photo o Post para una instancia ActivityFeed dada. Adicionalmente, asumiremos que los modelos Photo "tienen muchos" modelos Tag y los modelos Post "tienen muchos" modelos Comment.

Ahora, imaginemos que queremos recuperar instancias de ActivityFeed y precargar los modelos parentable de cada instancia de ActivityFeed. Además, queremos recuperar el número de etiquetas que están asociadas con cada foto padre y el número de comentarios que están asociados con cada post padre:

use Illuminate\Database\Eloquent\Relations\MorphTo;
 
$activities = ActivityFeed::with([
'parentable' => function (MorphTo $morphTo) {
$morphTo->morphWithCount([
Photo::class => ['tags'],
Post::class => ['comments'],
]);
}])->get();

Carga de recuentos diferidos

Supongamos que ya hemos recuperado un conjunto de modelos ActivityFeed y ahora queremos cargar los recuentos de relaciones anidadas para los distintos modelos parentables asociados a los activity feeds. Puede utilizar el método loadMorphCount para lograr esto:

$activities = ActivityFeed::with('parentable')->get();
 
$activities->loadMorphCount('parentable', [
Photo::class => ['tags'],
Post::class => ['comments'],
]);

Pre Carga (Eager Loading)

Cuando se accede a las relaciones de Eloquent como propiedades, los modelos relacionados son "cargados perezosamente". Esto significa que los datos de la relación no se cargan hasta que se accede por primera vez a la propiedad. Sin embargo, Eloquent puede precargar las relaciones en el momento en que se consulta el modelo padre. La carga rápida alivia el problema de las consultas "N + 1". Para ilustrar el problema de consulta N + 1, considere un modelo Book que "pertenece a" un modelo Autor:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Book extends Model
{
/**
* Get the author that wrote the book.
*/
public function author()
{
return $this->belongsTo(Author::class);
}
}

Ahora, recuperemos todos los libros y sus autores:

use App\Models\Book;
 
$books = Book::all();
 
foreach ($books as $book) {
echo $book->author->name;
}

Este bucle ejecutará una consulta para recuperar todos los libros dentro de la tabla de la base de datos, luego otra consulta para cada libro con el fin de recuperar el autor del libro. Así, si tenemos 25 libros, el código anterior ejecutaría 26 consultas: una para el libro original, y 25 consultas adicionales para recuperar el autor de cada libro.

Afortunadamente, podemos utilizar la precarga para reducir esta operación a sólo dos consultas. Al crear una consulta, puede especificar qué relaciones deben cargarse mediante el método with:

$books = Book::with('author')->get();
 
foreach ($books as $book) {
echo $book->author->name;
}

Para esta operación, sólo se ejecutarán dos consultas: una para obtener todos los libros y otra para obtener todos los autores de todos los libros:

select * from books
 
select * from authors where id in (1, 2, 3, 4, 5, ...)

Carga rápida de múltiples relaciones

A veces puede ser necesario cargar varias relaciones. Para ello, basta con pasar un array de relaciones al método with:

$books = Book::with(['author', 'publisher'])->get();

Precarga anidada

Para precargar las relaciones de una relación, puede utilizar la sintaxis "punto". Por ejemplo, precarguemos todos los autores del libro y todos los contactos personales del autor:

$books = Book::with('author.contacts')->get();

De manera alternativa, puedes especificar relaciones anidadas de carga rápida proporcionando un array anidado al método with, lo que puede ser conveniente cuando se cargan múltiples relaciones anidadas:

$books = Book::with([
'author' => [
'contacts',
'publisher',
],
])->get();

Precarga anidada de relaciones morphTo

Si desea precargar una relación morphTo, así como relaciones anidadas en varias entidades que pueden ser devueltas por esa relación, puede utilizar el método with en combinación con el método morphWith de la relación morphTo. Para ayudar a ilustrar este método, consideremos el siguiente modelo:

<?php
 
use Illuminate\Database\Eloquent\Model;
 
class ActivityFeed extends Model
{
/**
* Get the parent of the activity feed record.
*/
public function parentable()
{
return $this->morphTo();
}
}

En este ejemplo, asumamos que los modelos Event, Photo y Post pueden crear modelos ActivityFeed. Además, supongamos que los modelos de Event pertenecen a un modelo de Calendar, los modelos de Foto están asociados a modelos de Tag y los modelos de Post pertenecen a un modelo de Author.

Usando estas definiciones de modelo y relaciones, podemos recuperar instancias del modelo ActivityFeed y cargar todos los modelos parentables y sus respectivas relaciones anidadas:

use Illuminate\Database\Eloquent\Relations\MorphTo;
 
$activities = ActivityFeed::query()
->with(['parentable' => function (MorphTo $morphTo) {
$morphTo->morphWith([
Event::class => ['calendar'],
Photo::class => ['tags'],
Post::class => ['author'],
]);
}])->get();

Carga de Columnas Específicas

Puede que no siempre necesite todas las columnas de las relaciones que está recuperando. Por este motivo, Eloquent le permite especificar qué columnas de la relación desea recuperar:

$books = Book::with('author:id,name,book_id')->get();

Advertencia
Al utilizar esta función, debe incluir siempre la columna id y cualquier columna de clave externa relevante en la lista de columnas que desea recuperar.

Precarga por defecto

A veces es posible que desee cargar siempre algunas relaciones al recuperar un modelo. Para ello, puede definir una propiedad $with en el modelo:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Book extends Model
{
/**
* The relationships that should always be loaded.
*
* @var array
*/
protected $with = ['author'];
 
/**
* Get the author that wrote the book.
*/
public function author()
{
return $this->belongsTo(Author::class);
}
 
/**
* Get the genre of the book.
*/
public function genre()
{
return $this->belongsTo(Genre::class);
}
}

Si desea eliminar un elemento de la propiedad $with para una sola consulta, puede utilizar el método without:

$books = Book::without('author')->get();

Si desea sobreescribir todos los elementos de la propiedad $with para una sola consulta, puede utilizar el método withOnly:

$books = Book::withOnly('genre')->get();

Restricciones de la precarga

A veces puede que desee precargar una relación pero también especificar condiciones adicionales para la consulta. Puede conseguirlo pasando un array de relaciones al método with donde la clave array es el nombre de la relación y el valor del array es un closure que añade restricciones adicionales a la consulta de carga rápida:

use App\Models\User;
 
$users = User::with(['posts' => function ($query) {
$query->where('title', 'like', '%code%');
}])->get();

En este ejemplo, Eloquent sólo cargará las entradas cuyo title contenga la palabra code. Puede llamar a otros métodos del generador de consultas para personalizar aún más la operación de carga rápida:

$users = User::with(['posts' => function ($query) {
$query->orderBy('created_at', 'desc');
}])->get();

Advertencia
Los métodos de construcción de consultas limit y take no pueden utilizarse cuando se restringen cargas ansiosas.

Restricción de la carga dinámica de relaciones morphTo

Si está realizando una carga rápida de una relación morphTo, Eloquent ejecutará varias consultas para obtener cada tipo de modelo relacionado. Puede añadir restricciones adicionales a cada una de estas consultas utilizando el método constrain de la relación MorphTo:

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\MorphTo;
 
$comments = Comment::with(['commentable' => function (MorphTo $morphTo) {
$morphTo->constrain([
Post::class => function (Builder $query) {
$query->whereNull('hidden_at');
},
Video::class => function (Builder $query) {
$query->where('type', 'educational');
},
]);
}])->get();

En este ejemplo, Eloquent sólo precargará las entradas que no se hayan ocultado y los vídeos que tengan un valor de type "educativo".

Restricción de cargas ansiosas con existencia de relación

Es posible que a veces necesite comprobar la existencia de una relación y, al mismo tiempo, cargar la relación basándose en las mismas condiciones. Por ejemplo, es posible que desee recuperar sólo los modelos de User que tienen modelos de Post secundarios que coincidan con una condición de consulta determinada y, al mismo tiempo, cargar las entradas que coincidan. Para ello puede utilizar el método withWhereHas:

use App\Models\User;
 
$users = User::withWhereHas('posts', function ($query) {
$query->where('featured', true);
})->get();

Carga Perezosa (Lazy Eager Loading)

A veces puede ser necesario cargar una relación después de que el modelo padre haya sido recuperado. Por ejemplo, esto puede ser útil si necesita decidir dinámicamente si cargar modelos relacionados:

use App\Models\Book;
 
$books = Book::all();
 
if ($someCondition) {
$books->load('author', 'publisher');
}

Si necesita establecer restricciones adicionales en la consulta de carga rápida, puede pasar un array con las claves de las relaciones que desea cargar. Los valores array deben ser instancias de closure que reciban la instancia de consulta:

$author->load(['books' => function ($query) {
$query->orderBy('published_date', 'asc');
}]);

Para cargar una relación sólo cuando aún no se ha cargado, utilice el método loadMissing:

$book->loadMissing('author');

Carga Perezosa Anidada (Nested Lazy Eager Loading) & morphTo

Si desea cargar una relación morphTo, así como relaciones anidadas en las distintas entidades que pueden ser devueltas por esa relación, puede utilizar el método loadMorph.

Este método acepta el nombre de la relación morphTo como primer argumento, y un array de pares modelo / relación como segundo argumento. Para ayudar a ilustrar este método, consideremos el siguiente modelo:

<?php
 
use Illuminate\Database\Eloquent\Model;
 
class ActivityFeed extends Model
{
/**
* Get the parent of the activity feed record.
*/
public function parentable()
{
return $this->morphTo();
}
}

En este ejemplo, supongamos que los modelos de Event, Photo y Post pueden crear modelos ActivityFeed. Además, supongamos que los modelos Event pertenecen a un modelo Calendar, los modelos Foto están asociados a modelos Tag y los modelos Post pertenecen a un modelo Author.

Usando estas definiciones de modelo y relaciones, podemos recuperar instancias del modelo ActivityFeed y cargar todos los modelos parentables y sus respectivas relaciones anidadas:

$activities = ActivityFeed::with('parentable')
->get()
->loadMorph('parentable', [
Event::class => ['calendar'],
Photo::class => ['tags'],
Post::class => ['author'],
]);

Prevención de la carga lenta

Como se ha comentado anteriormente, la precarga de relaciones a menudo proporciona importantes beneficios de rendimiento a su aplicación. Por lo tanto, si lo desea, puede instruir a Laravel para evitar siempre la carga perezosa de las relaciones. Para ello, puede invocar el método preventLazyLoading que ofrece la clase modelo base de Eloquent. Por lo general, deberías llamar a este método dentro del método boot de la clase AppServiceProvider de tu aplicación.

El método preventLazyLoading acepta un argumento booleano opcional que indica si se debe evitar la carga perezosa. Por ejemplo, usted puede desear deshabilitar la carga perezosa solamente en entornos de no-producción para que su entorno de producción continúe funcionando incluso si una relación cargada perezosa está accidentalmente presente en el código de producción:

use Illuminate\Database\Eloquent\Model;
 
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Model::preventLazyLoading(! $this->app->isProduction());
}

Después de evitar la carga perezosa, Eloquent lanzará una excepción Illuminate\Database\LazyLoadingViolationException cuando su aplicación intente realizar una carga perezosa de cualquier relación de Eloquent.

Puedes personalizar el comportamiento de las violaciones de carga perezosa utilizando el método handleLazyLoadingViolationsUsing. Por ejemplo, utilizando este método, puedes indicar a las violaciones de carga perezosa que sólo se registren en lugar de interrumpir la ejecución de la aplicación con excepciones:

Model::handleLazyLoadingViolationUsing(function ($model, $relation) {
$class = get_class($model);
 
info("Attempted to lazy load [{$relation}] on model [{$class}].");
});

Inserción y actualización de modelos relacionados

El método save

Eloquent proporciona métodos convenientes para añadir nuevos modelos a las relaciones. Por ejemplo, quizás necesites añadir un nuevo comentario a un post. En lugar de establecer manualmente el atributo post_id en el modelo Comment puedes insertar el comentario utilizando el método save de la relación:

use App\Models\Comment;
use App\Models\Post;
 
$comment = new Comment(['message' => 'A new comment.']);
 
$post = Post::find(1);
 
$post->comments()->save($comment);

Observe que no hemos accedido a la relación comments como una propiedad dinámica. En su lugar, hemos llamado al método comments para obtener una instancia de la relación. El método save añadirá automáticamente el valor post_id apropiado al nuevo modelo Comment.

Si necesita guardar múltiples modelos relacionados, puede utilizar el método saveMany:

$post = Post::find(1);
 
$post->comments()->saveMany([
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another new comment.']),
]);

Los métodos save y saveMany persistirán las instancias de modelo dadas, pero no añadirán los nuevos modelos persistidos a ninguna relación en memoria que ya esté cargada en el modelo padre. Si planea acceder a la relación después de utilizar los métodos save o saveMany, puede utilizar el método refresh para recargar el modelo y sus relaciones:

$post->comments()->save($comment);
 
$post->refresh();
 
// All comments, including the newly saved comment...
$post->comments;

Guardar recursivamente modelos y relaciones

Si desea guardar su modelo y todas sus relaciones asociadas, puede utilizar el método push. En este ejemplo, el modelo Post se guardará así como sus comentarios y los autores de los comentarios:

$post = Post::find(1);
 
$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';
 
$post->push();

Método de creación

Además de los métodos save y saveMany, también puedes utilizar el método create, que acepta un array de atributos, crea un modelo y lo inserta en la base de datos. La diferencia entre save y create es que save acepta una instancia completa del modelo Eloquent mientras que create acepta un array PHP plano. El modelo recién creado será devuelto por el método create:

use App\Models\Post;
 
$post = Post::find(1);
 
$comment = $post->comments()->create([
'message' => 'A new comment.',
]);

Puede utilizar el método createMany para crear múltiples modelos relacionados:

$post = Post::find(1);
 
$post->comments()->createMany([
['message' => 'A new comment.'],
['message' => 'Another new comment.'],
]);

También puede utilizar los métodos findOrNew, firstOrNew, firstOrCreate y updateOrCreate para crear y actualizar modelos de relaciones.

Nota
Antes de utilizar el método create, asegúrese de revisar la documentación de asignación masiva.

Relaciones de Pertenencia

Si desea asignar un modelo hijo a un nuevo modelo padre, puede utilizar el método associate. En este ejemplo, el modelo User define una relación belongsTo con el modelo Account. Este método associate establecerá la clave externa en el modelo hijo:

use App\Models\Account;
 
$account = Account::find(10);
 
$user->account()->associate($account);
 
$user->save();

Para eliminar un modelo padre de un modelo hijo, puede utilizar el método dissociate. Este método establecerá la clave externa de la relación en null:

$user->account()->dissociate();
 
$user->save();

Relaciones de muchos a muchos

Acoplar / Desacoplar

Eloquent también proporciona métodos para hacer más cómodo el trabajo con relaciones de muchos a muchos. Por ejemplo, imaginemos que un usuario puede tener muchos roles y un rol puede tener muchos usuarios. Puedes utilizar el método attach para adjuntar un rol a un usuario insertando un registro en la tabla intermedia de la relación:

use App\Models\User;
 
$user = User::find(1);
 
$user->roles()->attach($roleId);

Cuando se adjunta una relación a un modelo, también se puede pasar un array de datos adicionales a insertar en la tabla intermedia:

$user->roles()->attach($roleId, ['expires' => $expires]);

A veces puede ser necesario eliminar un rol de un usuario. Para eliminar un registro de una relación muchos a muchos, utilice el método detach. El método detach eliminará el registro correspondiente de la tabla intermedia; sin embargo, ambos modelos permanecerán en la base de datos:

// Detach a single role from the user...
$user->roles()->detach($roleId);
 
// Detach all roles from the user...
$user->roles()->detach();

Para mayor comodidad, attach y detach también aceptan matrices de IDs como entrada:

$user = User::find(1);
 
$user->roles()->detach([1, 2, 3]);
 
$user->roles()->attach([
1 => ['expires' => $expires],
2 => ['expires' => $expires],
]);

Sincronización de asociaciones

También puede utilizar el método sync para construir asociaciones de muchos a muchos. El método sync acepta una array de IDs para colocar en la tabla intermedia. Cualquier ID que no esté en el array dado será eliminado de la tabla intermedia. Por lo tanto, una vez completada esta operación, sólo existirán en la tabla intermedia los IDs de la array dada:

$user->roles()->sync([1, 2, 3]);

También puede pasar valores adicionales de la tabla intermedia con los ID:

$user->roles()->sync([1 => ['expires' => true], 2, 3]);

Si desea insertar los mismos valores de tabla intermedia con cada uno de los IDs de modelo sincronizados, puede utilizar el método syncWithPivotValues:

$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);

Si no desea separar los ID existentes que faltan en la array dada, puede utilizar el método syncWithoutDetaching:

$user->roles()->syncWithoutDetaching([1, 2, 3]);

Conmutación de asociaciones

La relación muchos-a-muchos también proporciona un método toggle que "alterna" el estado de vinculación de los IDs de modelo relacionados dados. Si el ID en cuestión está actualmente vinculado, se desvinculará. Del mismo modo, si está desvinculado, se vinculará:

$user->roles()->toggle([1, 2, 3]);

También puede pasar valores adicionales de la tabla intermedia con los ID:

$user->roles()->toggle([
1 => ['expires' => true],
2 => ['expires' => true],
]);

Actualización de un registro en la tabla intermedia

Si necesita actualizar una fila existente en la tabla intermedia de su relación, puede utilizar el método updateExistingPivot. Este método acepta la clave externa del registro intermedio y una array de atributos para actualizar:

$user = User::find(1);
 
$user->roles()->updateExistingPivot($roleId, [
'active' => false,
]);

Tocar marcas de tiempo de los padres

Cuando un modelo define una relación belongsTo o belongsToMany con otro modelo, como un Comment que pertenece a un Post, a veces es útil actualizar la marca de tiempo del modelo padre cuando se actualiza el modelo hijo.

Por ejemplo, cuando se actualiza un modelo de Comment, es posible que desee "tocar" automáticamente la marca de tiempo updated_at de la Post para que se ajuste a la fecha y hora actuales. Para ello, puede añadir una propiedad touches a su modelo hijo que contenga los nombres de las relaciones cuyas marcas de tiempo updated_at deben actualizarse cuando se actualice el modelo hijo:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Comment extends Model
{
/**
* All of the relationships to be touched.
*
* @var array
*/
protected $touches = ['post'];
 
/**
* Get the post that the comment belongs to.
*/
public function post()
{
return $this->belongsTo(Post::class);
}
}

Advertencia
Las marcas de tiempo del modelo padre sólo se actualizarán si el modelo hijo se actualiza utilizando el método save de Eloquent.