Eloquent: Relaciones
- Introducción
- Definiendo Relaciones
- Relaciones de Muchos a Muchos
- Relaciones Polimórficas
- Relaciones Dinámicas
- Consultando Relaciones
- Agregando Modelos Relacionados
- Carga Eager
- Insertando y Actualizando Modelos Relacionados
- Actualizando Timestamps de Padres
Introducción
Las tablas de la base de datos a menudo están relacionadas entre sí. Por ejemplo, una publicación 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 admite una variedad de relaciones comunes:
Definiendo Relaciones
Las relaciones de Eloquent se definen como métodos en tus clases de modelo Eloquent. Dado que las relaciones también sirven como potentes constructores de consultas, definir las relaciones como métodos proporciona poderosas capacidades de encadenamiento y consulta de métodos. Por ejemplo, podemos encadenar restricciones de consulta adicionales en esta relación posts
:
$user->posts()->where('active', 1)->get();
Pero, antes de sumergirnos en el uso de relaciones, aprendamos cómo definir cada tipo de relación admitida por Eloquent.
Uno a Uno
Una relación uno a uno es un tipo muy básico de relación de base de datos. Por ejemplo, un modelo User
podría estar asociado con un modelo 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 tu modelo a través de la clase base Illuminate\Database\Eloquent\Model
del modelo:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\HasOne; class User extends Model{ /** * Get the phone associated with the user. */ public function phone(): HasOne { return $this->hasOne(Phone::class); }}
El primer argumento pasado al método hasOne
es el nombre de la clase del modelo relacionado. Una vez que se define la relación, podemos recuperar el registro relacionado utilizando las propiedades dinámicas de Eloquent. Las propiedades dinámicas te 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 foránea de la relación en función del nombre del modelo padre. En este caso, se asume automáticamente que el modelo Phone
tiene una clave foránea user_id
. Si deseas anular esta convención, puedes pasar un segundo argumento al método hasOne
:
return $this->hasOne(Phone::class, 'foreign_key');
Además, Eloquent asume que la clave foránea 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 deseas que la relación utilice un valor de clave primaria diferente a id
o a la propiedad $primaryKey
de tu modelo, puedes pasar un tercer argumento al método hasOne
:
return $this->hasOne(Phone::class, 'foreign_key', 'local_key');
Así que podemos acceder al modelo Phone
desde nuestro modelo User
. A continuación, definamos una relación en el modelo Phone
que nos permita acceder al usuario que posee el 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;use Illuminate\Database\Eloquent\Relations\BelongsTo; class Phone extends Model{ /** * Get the user that owns the phone. */ public function user(): BelongsTo { 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
en el modelo Phone
.
Eloquent determina el nombre de la clave foránea examinando el nombre del método de relación y sufijando el nombre del método con _id
. Así que, en este caso, Eloquent asume que el modelo Phone
tiene una columna user_id
. Sin embargo, si la clave foránea en el modelo Phone
no es user_id
, puedes pasar un nombre de clave personalizado como segundo argumento al método belongsTo
:
/** * Get the user that owns the phone. */public function user(): BelongsTo{ return $this->belongsTo(User::class, 'foreign_key');}
Si el modelo padre no utiliza id
como su clave primaria, o deseas encontrar el modelo asociado utilizando una columna diferente, puedes 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(): BelongsTo{ return $this->belongsTo(User::class, 'foreign_key', 'owner_key');}
Uno a Muchos
Una relación uno a muchos se utiliza para definir relaciones donde un solo modelo es el padre de uno o más modelos hijos. Por ejemplo, una publicación de blog puede tener un número infinito de comentarios. Al igual que todas las demás relaciones Eloquent, las relaciones uno a muchos se definen definiendo un método en tu modelo Eloquent:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\HasMany; class Post extends Model{ /** * Get the comments for the blog post. */ public function comments(): HasMany { return $this->hasMany(Comment::class); }}
Recuerda que Eloquent determinará automáticamente la columna de clave foránea adecuada para el modelo Comment
. Por convención, Eloquent tomará el nombre en "snake case" del modelo padre y lo suffixará con _id
. Así que, en este ejemplo, Eloquent asumirá que la columna de clave foránea en el modelo Comment
es post_id
.
Una vez que se ha definido el método de relación, podemos acceder a la colección de comentarios relacionados accediendo a la propiedad comments
. Recuerda, dado que 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) { // ...}
Dado que todas las relaciones también sirven como generadores de consultas, puedes agregar más restricciones a la consulta de la relación llamando al método comments
y continuando a encadenar condiciones en la consulta:
$comment = Post::find(1)->comments() ->where('title', 'foo') ->first();
Al igual que el método hasOne
, también puedes anular las claves foráneas 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 publicación, definamos una relación que permita a un comentario acceder a su publicación 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;use Illuminate\Database\Eloquent\Relations\BelongsTo; class Comment extends Model{ /** * Get the post that owns the comment. */ public function post(): BelongsTo { return $this->belongsTo(Post::class); }}
Una vez que se ha definido la relación, podemos recuperar la publicación 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
en el modelo Comment
.
Eloquent determina el nombre predeterminado de la clave foránea examinando el nombre del método de relación y sufijando el nombre del método con un _
seguido del nombre de la columna de clave primaria del modelo padre. Así que, en este ejemplo, Eloquent asumirá que la clave foránea del modelo Post
en la tabla comments
es post_id
.
Sin embargo, si la clave foránea para tu relación no sigue estas convenciones, puedes pasar un nombre de clave foránea personalizado como segundo argumento al método belongsTo
:
/** * Get the post that owns the comment. */public function post(): BelongsTo{ return $this->belongsTo(Post::class, 'foreign_key');}
Si tu modelo parent no utiliza id
como su clave primaria, o si deseas encontrar el modelo asociado utilizando una columna diferente, puedes pasar un tercer argumento al método belongsTo
especificando la clave personalizada de tu tabla parent:
/** * Get the post that owns the comment. */public function post(): BelongsTo{ return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');}
Modelos Predeterminados
Las relaciones belongsTo
, hasOne
, hasOneThrough
y morphOne
te permiten definir un modelo por defecto que se devolverá si la relación dada es null
. Este patrón a menudo se denomina patrón de Objeto Nulo y puede ayudar a eliminar verificaciones condicionales en tu código. En el siguiente ejemplo, la relación user
devolverá un modelo App\Models\User
vacío si no hay un usuario adjunto al modelo Post
:
/** * Get the author of the post. */public function user(): BelongsTo{ return $this->belongsTo(User::class)->withDefault();}
Para poblar el modelo predeterminado con atributos, puedes pasar un array o una función anónima al método withDefault
:
/** * Get the author of the post. */public function user(): BelongsTo{ return $this->belongsTo(User::class)->withDefault([ 'name' => 'Guest Author', ]);} /** * Get the author of the post. */public function user(): BelongsTo{ return $this->belongsTo(User::class)->withDefault(function (User $user, Post $post) { $user->name = 'Guest Author'; });}
Consultando Relaciones de Pertenencia
Al consultar los hijos de una relación "pertenece a", 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 te resulte más conveniente usar el método whereBelongsTo
, que determinará automáticamente la relación y la clave externa apropiadas para el modelo dado:
$posts = Post::whereBelongsTo($user)->get();
También puedes proporcionar una instancia de colección al método whereBelongsTo
. Al hacerlo, Laravel recuperará modelos que pertenecen a cualquiera de los modelos padres 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 en función del nombre de la clase del modelo; sin embargo, puedes especificar el nombre de la relación manualmente proporcionándolo como el 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 deseas recuperar fácilmente el modelo relacionado "más reciente" u "oldest". Por ejemplo, un modelo User
puede estar relacionado con muchos modelos Order
, pero quieres definir una forma conveniente de interactuar con el pedido más reciente que ha realizado el usuario. Puedes lograr esto utilizando el tipo de relación hasOne
combinado con los métodos ofMany
:
/** * Get the user's most recent order. */public function latestOrder(): HasOne{ return $this->hasOne(Order::class)->latestOfMany();}
/** * Get the user's oldest order. */public function oldestOrder(): HasOne{ return $this->hasOne(Order::class)->oldestOfMany();}
Por ejemplo, utilizando el método ofMany
, puedes recuperar el pedido más caro del usuario. El método ofMany
acepta la columna ordenable como su primer argumento y qué función de agregado (min
o max
) aplicar al consultar el modelo relacionado:
/** * Get the user's largest order. */public function largestOrder(): HasOne{ return $this->hasOne(Order::class)->ofMany('price', 'max');}
[!WARNING] Debido a que PostgreSQL no admite la ejecución de la función
MAX
contra columnas UUID, actualmente no es posible usar relaciones de uno-a-muchos en combinación con columnas UUID de PostgreSQL.
Convirtiendo Relaciones "Many" a Relaciones "Has One"
A menudo, al recuperar un solo modelo utilizando los métodos latestOfMany
, oldestOfMany
o ofMany
, ya tienes una relación "tiene muchos" definida para el mismo modelo. Para mayor comodidad, Laravel te permite convertir fácilmente esta relación en una relación "tiene uno" invocando el método one
en la relación:
/** * Get the user's orders. */public function orders(): HasMany{ return $this->hasMany(Order::class);} /** * Get the user's largest order. */public function largestOrder(): HasOne{ return $this->orders()->one()->ofMany('price', 'max');}
Relaciones Avanzadas de Uno a Muchos
Es posible construir relaciones más avanzadas de "tiene uno de muchos". Por ejemplo, un modelo Product
puede tener muchos modelos Price
asociados que se mantienen en el sistema incluso después de que se publiquen nuevos precios. Además, es posible que los nuevos datos de precios para el producto puedan publicarse por adelantado para tener efecto en una fecha futura a través de una columna published_at
.
Entonces, en resumen, necesitamos recuperar los precios publicados más recientes donde 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 ID más grande. Para lograr esto, debemos pasar un array al método ofMany
que contenga las columnas ordenables que determinan el precio más reciente. Además, se proporcionará una función anónima como segundo argumento al método ofMany
. Esta función anónima será responsable 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(): HasOne{ return $this->hasOne(Price::class)->ofMany([ 'published_at' => 'max', 'id' => 'max', ], function (Builder $query) { $query->where('published_at', '<', now()); });}
Tiene Uno a Través
La relación "has-one-through" define una relación uno a uno con otro modelo. Sin embargo, esta relación indica que el modelo que declara puede coincidir con una instancia de otro modelo al proceder a través de un tercer modelo.
Por ejemplo, en una aplicación de taller de reparación de vehículos, cada modelo Mechanic
puede estar asociado con un modelo Car
, y cada modelo Car
puede estar asociado con un modelo Owner
. Mientras que 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 la tabla para la relación, definamos la relación en el modelo Mechanic
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\HasOneThrough; class Mechanic extends Model{ /** * Get the car's owner. */ public function carOwner(): HasOneThrough { 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.
O bien, si las relaciones relevantes ya han sido definidas en todos los modelos involucrados en la relación, puedes definir de manera fluida una relación de "has-one-through" invocando el método through
y suministrando los nombres de esas relaciones. Por ejemplo, si el modelo Mechanic
tiene una relación cars
y el modelo Car
tiene una relación owner
, puedes definir una relación de "has-one-through" conectando el mecánico y el propietario así:
Se utilizarán las convenciones típicas de clave foránea de Eloquent al realizar las consultas de la relación. Si deseas personalizar las claves de la relación, puedes pasarlas como el tercer y cuarto argumento al método hasOneThrough
. El tercer argumento es el nombre de la clave foránea en el modelo intermedio. El cuarto argumento es el nombre de la clave foránea 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(): HasOneThrough { 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... ); }}
O, como se discutió anteriormente, si las relaciones relevantes ya han sido definidas en todos los modelos involucrados en la relación, puedes definir de manera fluida una relación de "has-one-through" invocando el método through
y suministrando los nombres de esas relaciones. Este enfoque ofrece la ventaja de reutilizar las convenciones de clave ya definidas en las relaciones existentes:
// String based syntax...return $this->through('cars')->has('owner'); // Dynamic syntax...return $this->throughCars()->hasOwner();
Has Many Through
La relación "has-many-through" proporciona una forma conveniente 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 Project
podría acceder a muchos modelos Deployment
a través de un modelo Environment
intermedio. 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;use Illuminate\Database\Eloquent\Relations\HasManyThrough; class Project extends Model{ /** * Get all of the deployments for the project. */ public function deployments(): HasManyThrough { 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.
O, si las relaciones relevantes ya han sido definidas en todos los modelos involucrados en la relación, puedes definir de manera fluida una relación "has-many-through" invocando el método through
y suministrando los nombres de esas relaciones. Por ejemplo, si el modelo Project
tiene una relación environments
y el modelo Environment
tiene una relación deployments
, puedes definir una relación "has-many-through" conectando el proyecto y las implementaciones de la siguiente manera:
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 del modelo intermedio Environment
. Después de encontrar los IDs de entorno relevantes, se utilizan para consultar la tabla del modelo Deployment
.
Se utilizarán las convenciones típicas de claves foráneas de Eloquent al realizar las consultas de la relación. Si deseas personalizar las claves de la relación, puedes pasarlas como el tercer y cuarto argumento al método hasManyThrough
. El tercer argumento es el nombre de la clave foránea en el modelo intermedio. El cuarto argumento es el nombre de la clave foránea 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(): HasManyThrough { 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... ); }}
O, como se discutió anteriormente, si las relaciones relevantes ya han sido definidas en todos los modelos involucrados en la relación, puedes definir de manera fluida una relación "has-many-through" invocando el método through
y suministrando los nombres de esas relaciones. Este enfoque ofrece la ventaja de reutilizar las convenciones de clave ya definidas en las relaciones existentes:
// String based syntax...return $this->through('environments')->has('deployments'); // Dynamic syntax...return $this->throughEnvironments()->hasDeployments();
Relaciones Muchos a Muchos
Las relaciones muchos a muchos 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í que, un usuario tiene muchos roles y un rol tiene muchos usuarios.
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 una tabla intermedia que vincula a los usuarios y roles.
Recuerda 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 solo podría pertenecer a un solo usuario. Para proporcionar soporte a que los roles sean asignados a múltiples usuarios, se necesita la tabla role_user
. Podemos resumir la estructura de la tabla 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
Las relaciones de muchos a muchos se definen escribiendo un método que devuelve el resultado del método belongsToMany
. El método belongsToMany
es proporcionado por la clase base Illuminate\Database\Eloquent\Model
que utilizan todos los modelos Eloquent de tu 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 del modelo relacionado:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsToMany; class User extends Model{ /** * The roles that belong to the user. */ public function roles(): BelongsToMany { return $this->belongsToMany(Role::class); }}
Una vez que se define la relación, puedes 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, puedes añadir más restricciones a la consulta de la relación llamando al método roles
y continuando a encadenar condiciones en la consulta:
$roles = User::find(1)->roles()->orderBy('name')->get();
Para determinar el nombre de la tabla del tabla intermedia de la relación, Eloquent unirá los dos nombres de los modelos relacionados en orden alfabético. Sin embargo, puedes sobrescribir esta convención. Puedes 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 puedes personalizar los nombres de las columnas de las claves en la tabla pasando argumentos adicionales al método belongsToMany
. El tercer argumento es el nombre de la clave foránea del modelo en el que estás definiendo la relación, mientras que el cuarto argumento es el nombre de la clave foránea del modelo al que te estás uniendo:
return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
Para definir la "inversa" de una relación de 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 de usuario / rol, definamos el método users
en el modelo Role
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Role extends Model{ /** * The users that belong to the role. */ public function users(): BelongsToMany { return $this->belongsToMany(User::class); }}
Como puedes ver, la relación se define exactamente igual que su contraparte del 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.
Recuperando Columnas de la Tabla Intermedia
Como ya has aprendido, trabajar con relaciones de muchos a muchos requiere la presencia de una tabla intermedia. Eloquent ofrece algunas formas muy útiles de interactuar con esta tabla. Por ejemplo, supongamos que nuestro modelo User
tiene muchos modelos Role
a los que está relacionado. Después de acceder a esta relación, podemos acceder a la tabla intermedia utilizando el atributo pivot
en los modelos:
use App\Models\User; $user = User::find(1); foreach ($user->roles as $role) { echo $role->pivot->created_at;}
Nota que cada modelo Role
que recuperamos se asigna automáticamente a un atributo pivot
. Este atributo contiene un modelo que representa la tabla intermedia.
Por defecto, solo las claves del modelo estarán presentes en el modelo pivot
. Si tu tabla intermedia contiene atributos adicionales, debes especificarlos al definir la relación:
return $this->belongsToMany(Role::class)->withPivot('active', 'created_by');
Si deseas que tu tabla intermedia tenga marcas de tiempo created_at
y updated_at
que sean mantenidas automáticamente por Eloquent, llama al método withTimestamps
al definir la relación:
return $this->belongsToMany(Role::class)->withTimestamps();
[!WARNING] Las tablas intermedias que utilizan las marcas de tiempo mantenidas automáticamente por Eloquent deben tener ambas columnas de marca de tiempo
created_at
yupdated_at
.
Personalizando el Nombre del Atributo pivot
Como se mencionó anteriormente, los atributos de la tabla intermedia pueden ser accedidos 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 tu aplicación contiene usuarios que pueden suscribirse a podcasts, probablemente tengas una relación de muchos a muchos entre usuarios y podcasts. Si este es el caso, es posible que desees renombrar el atributo de tu tabla intermedia a subscription
en lugar de pivot
. Esto se puede hacer utilizando el método as
al definir la relación:
return $this->belongsToMany(Podcast::class) ->as('subscription') ->withTimestamps();
Una vez que se ha especificado el atributo de la tabla intermedia personalizada, puedes 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;}
Filtrando Consultas a través de Columnas de Tablas Intermedias
También puedes filtrar los resultados devueltos por las consultas de relaciones 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');
Ordenando Consultas a través de Columnas de Tablas Intermedias
Puedes ordenar los resultados devueltos por las consultas de relaciones belongsToMany
utilizando el método orderByPivot
. En el siguiente ejemplo, recuperaremos todas las insignias más recientes del usuario:
return $this->belongsToMany(Badge::class) ->where('rank', 'gold') ->orderByPivot('created_at', 'desc');
Definición de Modelos de Tabla Intermedia Personalizados
Si deseas definir un modelo personalizado para representar la tabla intermedia de tu relación de muchos a muchos, puedes llamar al método using
al definir la relación. Los modelos de pivote personalizados te ofrecen la oportunidad de definir un comportamiento adicional en el modelo de pivote, como métodos y castings.
Los modelos pivote muchos a muchos personalizados deben extender la clase Illuminate\Database\Eloquent\Relations\Pivot
, mientras que los modelos pivote muchos a muchos polimórficos personalizados deben extender la clase Illuminate\Database\Eloquent\Relations\MorphPivot
. Por ejemplo, podemos definir un modelo Role
que utiliza un modelo pivote RoleUser
personalizado:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Role extends Model{ /** * The users that belong to the role. */ public function users(): BelongsToMany { return $this->belongsToMany(User::class)->using(RoleUser::class); }}
Al definir el modelo RoleUser
, debes extender la clase Illuminate\Database\Eloquent\Relations\Pivot
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Relations\Pivot; class RoleUser extends Pivot{ // ...}
[!WARNING] Los modelos pivot no pueden utilizar el trait
SoftDeletes
. Si necesitas eliminar registros pivot de forma suave, considera convertir tu modelo pivot en un modelo Eloquent real.
Modelos Pivot Personalizados e IDs Incrementales
Si has definido una relación muchos a muchos que utiliza un modelo pivot personalizado, y ese modelo pivot tiene una clave primaria autoincremental, debes asegurarte de que la clase de tu modelo pivot personalizado defina una propiedad incrementing
que esté configurada 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 sola asociación. Por ejemplo, imagina que estás construyendo una aplicación que permite a los usuarios compartir publicaciones de blog y videos. En una aplicación así, un modelo Comment
podría pertenecer tanto a los modelos Post
como a Video
.
Uno a Uno (Polimórfico)
Una relación polimórfica uno a uno es similar a una relación uno a uno típica; sin embargo, el modelo hijo puede pertenecer a más de un tipo de modelo utilizando una sola asociación. Por ejemplo, un Post
de blog y un User
pueden compartir una relación polimórfica con un modelo Image
. Usar una relación polimórfica uno a uno te permite tener una sola tabla de imágenes únicas que pueden estar asociadas con publicaciones 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
Ten en cuenta las columnas imageable_id
y imageable_type
en la tabla images
. La columna imageable_id
contendrá el valor ID de la publicación o el usuario, mientras que la columna imageable_type
contendrá el nombre de la clase del modelo padre. La columna imageable_type
es utilizada por Eloquent para determinar qué "tipo" de modelo padre devolver al acceder a la relación imageable
. En este caso, la columna contendría ya sea App\Models\Post
o App\Models\User
.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphTo; class Image extends Model{ /** * Get the parent imageable model (user or post). */ public function imageable(): MorphTo { return $this->morphTo(); }} use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphOne; class Post extends Model{ /** * Get the post's image. */ public function image(): MorphOne { return $this->morphOne(Image::class, 'imageable'); }} use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphOne; class User extends Model{ /** * Get the user's image. */ public function image(): MorphOne { return $this->morphOne(Image::class, 'imageable'); }}
Una vez que tu tabla de base de datos y modelos estén definidos, puedes acceder a las relaciones a través de tus modelos. Por ejemplo, para recuperar la imagen de una publicación, podemos acceder a la propiedad de relación dinámica image
:
use App\Models\Post; $post = Post::find(1); $image = $post->image;
Puedes recuperar el padre del modelo polimórfico accediendo al nombre del método que realiza la llamada a morphTo
. En este caso, ese es el método imageable
en el modelo Image
. Así que 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á ya sea una instancia de Post
o User
, dependiendo de qué tipo de modelo posee la imagen.
Concepciones Clave
Si es necesario, puedes especificar el nombre de las columnas "id" y "type" utilizadas por tu modelo hijo polimórfico. Si lo haces, asegúrate de que siempre pases el nombre de la relación como primer argumento al método morphTo
. Típicamente, este valor debe coincidir con el nombre del método, por lo que puedes usar la constante __FUNCTION__
de PHP:
/** * Get the model that the image belongs to. */public function imageable(): MorphTo{ return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id');}
Uno a Muchos (Polimórfico)
Una relación polimórfica de uno a muchos es similar a una relación típica de uno a muchos; sin embargo, el modelo hijo puede pertenecer a más de un tipo de modelo utilizando una sola asociación. Por ejemplo, imagina que los usuarios de tu aplicación pueden "comentar" en publicaciones y videos. Utilizando relaciones polimórficas, puedes usar una sola tabla comments
para contener comentarios tanto para publicaciones como para videos. Primero, examinemos la estructura de la tabla requerida 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
A continuación, examinemos las definiciones de los modelos necesarias para construir esta relación:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphTo; class Comment extends Model{ /** * Get the parent commentable model (post or video). */ public function commentable(): MorphTo { return $this->morphTo(); }} use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphMany; class Post extends Model{ /** * Get all of the post's comments. */ public function comments(): MorphMany { return $this->morphMany(Comment::class, 'commentable'); }} use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphMany; class Video extends Model{ /** * Get all of the video's comments. */ public function comments(): MorphMany { return $this->morphMany(Comment::class, 'commentable'); }}
Una vez que tu tabla de base de datos y modelos estén definidos, puedes acceder a las relaciones a través de las propiedades dinámicas de relación de tu modelo. Por ejemplo, para acceder a todos los comentarios de un post, podemos usar la propiedad dinámica comments
:
use App\Models\Post; $post = Post::find(1); foreach ($post->comments as $comment) { // ...}
También puedes recuperar el padre de un modelo hijo polimórfico accediendo al nombre del método que realiza la llamada a morphTo
. En este caso, ese es el método commentable
en el modelo Comment
. Así que accesaremos a ese método como una propiedad de relación dinámica para poder 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á ya sea una instancia de Post
o de Video
, dependiendo de qué tipo de modelo sea el padre del comentario.
Uno de Muchos (Polimórfico)
A veces un modelo puede tener muchos modelos relacionados, pero deseas recuperar fácilmente el modelo relacionado "más reciente" o "más antiguo" de la relación. Por ejemplo, un modelo User
puede estar relacionado con muchos modelos Image
, pero deseas definir una forma conveniente de interactuar con la imagen más reciente que el usuario ha subido. Puedes lograr esto utilizando el tipo de relación morphOne
combinado con los métodos ofMany
:
/** * Get the user's most recent image. */public function latestImage(): MorphOne{ return $this->morphOne(Image::class, 'imageable')->latestOfMany();}
Del mismo modo, puedes definir un método para recuperar el modelo relacionado "más antiguo", o el primero, de una relación:
/** * Get the user's oldest image. */public function oldestImage(): MorphOne{ 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 según la clave primaria del modelo, que debe ser ordenable. Sin embargo, a veces es posible que desees recuperar un solo modelo de una relación más grande utilizando un criterio de ordenación diferente.
Por ejemplo, utilizando el método ofMany
, puedes recuperar la imagen más "gustada" del usuario. El método ofMany
acepta la columna ordenable como su primer argumento y qué función de agregación (min
o max
) aplicar al consultar el modelo relacionado:
/** * Get the user's most popular image. */public function bestImage(): MorphOne{ return $this->morphOne(Image::class, 'imageable')->ofMany('likes', 'max');}
[!NOTA] Es posible construir relaciones "uno de muchos" más avanzadas. Para obtener más información, consulta la documentación sobre "tiene uno de muchos".
Muchos a Muchos (Polimórfico)
Estructura de la Tabla
Las relaciones polimórficas muchas a muchas 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
. Usar una relación polimórfica muchas a muchas en esta situación permitiría que tu aplicación tenga una sola tabla de etiquetas únicas que pueden estar asociadas con publicaciones o videos. Primero, examinemos la estructura de la tabla necesaria para construir 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
[!NOTE] Antes de profundizar en las relaciones polimórficas de muchos a muchos, puede que te beneficie leer la documentación sobre las relaciones típicas 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 base del modelo Eloquent.
El método morphToMany
acepta el nombre del modelo relacionado así como el "nombre de la relación". Basado en el nombre que asignamos a nuestra tabla intermedia y las claves que contiene, nos referiremos a la relación como "taggable":
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphToMany; class Post extends Model{ /** * Get all of the tags for the post. */ public function tags(): MorphToMany { return $this->morphToMany(Tag::class, 'taggable'); }}
Definiendo la Inversa de la Relación
A continuación, en el modelo Tag
, debes definir un método para cada uno de sus posibles modelos padre. Así que, 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". Basándonos en el nombre que asignamos a nuestro nombre de tabla intermedia y las claves que contiene, nos referiremos a la relación como "taggable":
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphToMany; class Tag extends Model{ /** * Get all of the posts that are assigned this tag. */ public function posts(): MorphToMany { return $this->morphedByMany(Post::class, 'taggable'); } /** * Get all of the videos that are assigned this tag. */ public function videos(): MorphToMany { return $this->morphedByMany(Video::class, 'taggable'); }}
Recuperando la Relación
Una vez que tu tabla de base de datos y modelos estén definidos, puedes acceder a las relaciones a través de tus modelos. Por ejemplo, para acceder a todas las etiquetas de una publicación, puedes usar la propiedad de relación dinámica tags
:
use App\Models\Post; $post = Post::find(1); foreach ($post->tags as $tag) { // ...}
Puedes recuperar el padre de una relación polimórfica desde el modelo hijo polimórfico accediendo al nombre del método que realiza la llamada a morphedByMany
. En este caso, esos son los métodos posts
o videos
en el 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 de clase completamente calificado para almacenar el "tipo" del modelo relacionado. Por ejemplo, dado el ejemplo de relación uno a muchos anterior donde un modelo Comment
puede pertenecer a un modelo Post
o Video
, el commentable_type
predeterminado sería App\Models\Post
o App\Models\Video
, respectivamente. Sin embargo, es posible que desees desacoplar estos valores de la estructura interna de tu aplicación.
Por ejemplo, en lugar de usar los nombres de los modelos como el "tipo", podemos usar cadenas simples como post
y video
. Al hacerlo, los valores de la columna "tipo" polimórfica en nuestra base de datos seguirán siendo válidos incluso si se renombraron los modelos:
use Illuminate\Database\Eloquent\Relations\Relation; Relation::enforceMorphMap([ 'post' => 'App\Models\Post', 'video' => 'App\Models\Video',]);
Puedes llamar al método enforceMorphMap
en el método boot
de tu clase App\Providers\AppServiceProvider
o crear un proveedor de servicios separado si lo deseas.
Puedes determinar el alias de morfismo de un modelo dado en tiempo de ejecución utilizando el método getMorphClass
del modelo. Por el contrario, puedes determinar el nombre de clase completamente calificado asociado con un alias de morfismo utilizando el método Relation::getMorphedModel
:
use Illuminate\Database\Eloquent\Relations\Relation; $alias = $post->getMorphClass(); $class = Relation::getMorphedModel($alias);
[!WARNING] Al agregar un "morph map" a tu aplicación existente, cada valor de columna
*_type
que sea morphable en tu base de datos y que aún contenga una clase totalmente cualificada deberá ser convertido a su nombre de "mapa".
Relaciones Dinámicas
Puedes usar el método resolveRelationUsing
para definir relaciones entre modelos Eloquent en tiempo de ejecución. Aunque no se recomienda típicamente para el desarrollo normal de aplicaciones, esto puede ser útil ocasionalmente al desarrollar paquetes de Laravel.
El método resolveRelationUsing
acepta el nombre de la relación deseada como su primer argumento. El segundo argumento que se pasa al método debe ser una función anónima que acepte la instancia del modelo y devuelva una definición de relación Eloquent válida. Típicamente, debes configurar relaciones dinámicas dentro del método boot de un service provider:
use App\Models\Order;use App\Models\Customer; Order::resolveRelationUsing('customer', function (Order $orderModel) { return $orderModel->belongsTo(Customer::class, 'customer_id');});
[!WARNING] Al definir relaciones dinámicas, siempre proporciona argumentos de nombre de clave explícitos a los métodos de relación de Eloquent.
Consultando Relaciones
Dado que todas las relaciones Eloquent se definen a través de métodos, puedes 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 funcionan como construcciones de consultas, lo que te permite seguir encadenando restricciones a la consulta de la relación antes de ejecutar finalmente la consulta SQL contra tu 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;use Illuminate\Database\Eloquent\Relations\HasMany; class User extends Model{ /** * Get all of the posts for the user. */ public function posts(): HasMany { return $this->hasMany(Post::class); }}
Puedes consultar la relación posts
y agregar restricciones adicionales a la relación de la siguiente manera:
use App\Models\User; $user = User::find(1); $user->posts()->where('active', 1)->get();
Puedes utilizar cualquiera de los métodos del query builder de Laravel en la relación, así que asegúrate de explorar la documentación del query builder para aprender sobre todos los métodos que tienes a tu disposición.
Encadenando cláusulas orWhere
después de relaciones
Como se demuestra en el ejemplo anterior, puedes añadir restricciones adicionales a las relaciones al consultarlas. Sin embargo, ten cuidado al encadenar cláusulas orWhere
en una relación, ya que las cláusulas orWhere
se agruparán lógicamente al mismo nivel que la restricción de la relación:
$user->posts() ->where('active', 1) ->orWhere('votes', '>=', 100) ->get();
El ejemplo anterior generará el siguiente SQL. Como puedes ver, la cláusula or
instruye a la consulta a devolver cualquier publicación con más de 100 votos. La consulta ya no está restringida a un usuario específico:
select *from postswhere user_id = ? and active = 1 or votes >= 100
En la mayoría de las situaciones, deberías usar 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 producirá el siguiente SQL. Ten en cuenta que el agrupamiento lógico ha agrupado correctamente las restricciones y la consulta sigue estando restringida a un usuario específico:
select *from postswhere user_id = ? and (active = 1 or votes >= 100)
Métodos de Relación vs. Propiedades Dinámicas
Si no necesitas añadir restricciones adicionales a una consulta de relación Eloquent, puedes acceder a la relación como si fuera una propiedad. Por ejemplo, continuando con 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 "carga perezosa", lo que significa que solo cargarán sus datos de relación cuando realmente los accedas. Debido a esto, los desarrolladores a menudo utilizan carga ansiosa para pre-cargar relaciones que saben que se accederán después de cargar el modelo. La carga ansiosa proporciona una reducción significativa en las consultas SQL que deben ejecutarse para cargar las relaciones de un modelo.
Consultando la Existencia de una Relación
Al recuperar registros de modelos, es posible que desees limitar tus resultados en función de la existencia de una relación. Por ejemplo, imagina que deseas recuperar todas las publicaciones de blog que tienen al menos un comentario. Para hacerlo, puedes 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 puedes especificar un operador y un valor de conteo para personalizar aún más la consulta:
// Retrieve all posts that have three or more comments...$posts = Post::has('comments', '>=', 3)->get();
Se pueden construir declaraciones has
anidadas utilizando la notación de "punto". Por ejemplo, puedes recuperar todas las publicaciones que tienen al menos un comentario que tiene 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 usar 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();
[!WARNING] Eloquent no admite actualmente la consulta de 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 deseas consultar la existencia de una relación con una sola condición where simple adjunta a la consulta de relación, es posible que te resulte más conveniente usar los métodos whereRelation
, orWhereRelation
, whereMorphRelation
y orWhereMorphRelation
. Por ejemplo, podemos consultar todas las publicaciones 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 puedes especificar un operador:
$posts = Post::whereRelation( 'comments', 'created_at', '>=', now()->subHour())->get();
Consultando la Ausencia de Relaciones
Al recuperar registros de modelos, es posible que desees limitar tus resultados en función de la ausencia de una relación. Por ejemplo, imagina que quieres recuperar todas las publicaciones de blog que no tienen comentarios. Para hacerlo, puedes pasar el nombre de la relación a los métodos doesntHave
y orDoesntHave
:
use App\Models\Post; $posts = Post::doesntHave('comments')->get();
Si necesitas aún más potencia, puedes usar los métodos whereDoesntHave
y orWhereDoesntHave
para añadir restricciones adicionales a tus 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();
Puedes usar la notación "punto" para ejecutar una consulta contra una relación anidada. Por ejemplo, la siguiente consulta recuperará todas las publicaciones que no tienen comentarios; sin embargo, las publicaciones que tienen comentarios de autores que no están baneados se incluirán en los resultados:
use Illuminate\Database\Eloquent\Builder; $posts = Post::whereDoesntHave('comments.author', function (Builder $query) { $query->where('banned', 0);})->get();
Consultando Relaciones Morph To
Para consultar la existencia de relaciones "morph to", puedes usar los métodos whereHasMorph
y whereDoesntHaveMorph
. Estos métodos aceptan el nombre de la relación como su primer argumento. A continuación, los métodos aceptan los nombres de los modelos relacionados que deseas incluir en la consulta. Finalmente, puedes proporcionar una función anónima 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();
Puede que ocasionalmente necesites añadir restricciones de consulta basadas en el "tipo" del modelo polimórfico relacionado. La función anónima pasada al método whereHasMorph
puede recibir un valor $type
como segundo argumento. Este argumento te 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, string $type) { $column = $type === Post::class ? 'content' : 'title'; $query->where($column, 'like', 'code%'); })->get();
Consultando Todos los Modelos Relacionados
En lugar de pasar un array de posibles modelos polimórficos, puedes proporcionar *
como un valor comodín. Esto indicará a Laravel que recupere todos los posibles tipos polimórficos 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();
Agregando Modelos Relacionados
Contando Modelos Relacionados
A veces es posible que desees contar el número de modelos relacionados para una relación dada sin cargar realmente los modelos. Para lograr esto, puedes usar 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
, puedes añadir los "contadores" para múltiples relaciones así como agregar 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 puedes alias el resultado del conteo de la relación, permitiendo múltiples conteos 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;
Usando el método loadCount
, puedes cargar un conteo de relación después de que el modelo padre ya haya sido recuperado:
$book = Book::first(); $book->loadCount('genres');
Si necesitas establecer restricciones de consulta adicionales en la consulta de recuento, puedes pasar un array con las claves de las relaciones que deseas contar. Los valores del array deben ser funciones anónimas que reciban la instancia del constructor de consultas:
$book->loadCount(['reviews' => function (Builder $query) { $query->where('rating', 5);}])
Contar Relaciones y Consultas de Selección Personalizadas
Si estás combinando withCount
con una declaración select
, asegúrate 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 métodos withMin
, withMax
, withAvg
, withSum
y withExists
. Estos métodos agregarán un atributo {relation}_{function}_{column}
en tus modelos resultantes:
use App\Models\Post; $posts = Post::withSum('comments', 'votes')->get(); foreach ($posts as $post) { echo $post->comments_sum_votes;}
Si deseas acceder al resultado de la función de agregación utilizando otro nombre, puedes especificar tu 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 están disponibles versiones diferidas de estos métodos. Estas operaciones de agregado adicionales se pueden realizar en modelos Eloquent que ya han sido recuperados:
$post = Post::first(); $post->loadSum('comments', 'votes');
Si estás combinando estos métodos de agregación con una declaración select
, asegúrate de llamar a los métodos de agregación después del método select
:
$posts = Post::select(['title', 'body']) ->withExists('comments') ->get();
Contando Modelos Relacionados en Relaciones Morph To
Si deseas cargar de forma anticipada una relación "morph to", así como los conteos de modelos relacionados para las diversas entidades que pueden ser devueltas por esa relación, puedes utilizar el método with
en combinación con el método morphWithCount
de la relación morphTo
.
En este ejemplo, supongamos que los modelos Photo
y Post
pueden crear modelos ActivityFeed
. Asumiremos que el modelo ActivityFeed
define una relación de "morph to" llamada parentable
que nos permite recuperar el modelo Photo
o Post
padre para una instancia dada de ActivityFeed
. Además, supongamos 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 cargar de manera anticipada los modelos padre parentable
para 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 publicación 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 Conteo Diferido
Supongamos que ya hemos recuperado un conjunto de modelos ActivityFeed
y ahora nos gustaría cargar los recuentos de las relaciones anidadas para los diversos modelos parentable
asociados con los feeds de actividad. Puedes usar el método loadMorphCount
para lograr esto:
$activities = ActivityFeed::with('parentable')->get(); $activities->loadMorphCount('parentable', [ Photo::class => ['tags'], Post::class => ['comments'],]);
Carga Eager
Al acceder a las relaciones de Eloquent como propiedades, los modelos relacionados se "cargan de manera perezosa". Esto significa que los datos de la relación no se cargan realmente hasta que accedes por primera vez a la propiedad. Sin embargo, Eloquent puede "cargar de manera anticipada" las relaciones en el momento en que consultas el modelo padre. La carga anticipada alivia el problema de consulta "N + 1". Para ilustrar el problema de consulta N + 1, considera un modelo Book
que "pertenece a" un modelo Author
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo; class Book extends Model{ /** * Get the author that wrote the book. */ public function author(): BelongsTo { 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 por cada libro para poder recuperar el autor del libro. Así que, 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 usar la carga anticipada para reducir esta operación a solo dos consultas. Al construir una consulta, puedes especificar qué relaciones deben cargarse anticipadamente utilizando el método with
:
$books = Book::with('author')->get(); foreach ($books as $book) { echo $book->author->name;}
Para esta operación, solo se ejecutarán dos consultas: una consulta para recuperar todos los libros y una consulta para recuperar todos los autores de todos los libros:
select * from books select * from authors where id in (1, 2, 3, 4, 5, ...)
Carga Eager de Múltiples Relaciones
A veces es posible que necesites cargar de manera ansiosa varias relaciones diferentes. Para hacerlo, simplemente pasa un array de relaciones al método with
:
$books = Book::with(['author', 'publisher'])->get();
Carga Eager Anidada
Para cargar de manera ansiosa las relaciones de las relaciones, puedes usar la sintaxis de "punto". Por ejemplo, carguemos de manera ansiosa todos los autores del libro y todos los contactos personales del autor:
$books = Book::with('author.contacts')->get();
Alternativamente, puedes especificar relaciones anidadas cargadas de forma anticipada proporcionando un array anidado al método with
, lo cual puede ser conveniente al cargar múltiples relaciones anidadas:
$books = Book::with([ 'author' => [ 'contacts', 'publisher', ],])->get();
Carga Eager Anidada de Relaciones morphTo
Si deseas cargar de forma anticipada una relación morphTo
, así como relaciones anidadas en las diversas entidades que pueden ser devueltas por esa relación, puedes usar 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:
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 anticipada de columnas específicas
Es posible que no siempre necesites todas las columnas de las relaciones que estás recuperando. Por esta razón, Eloquent te permite especificar qué columnas de la relación te gustaría recuperar:
$books = Book::with('author:id,name,book_id')->get();
[!WARNING] Cuando utilices esta función, siempre debes incluir la columna
id
y cualquier columna de clave foránea relevante en la lista de columnas que deseas recuperar.
Carga anticipada por defecto
A veces es posible que desees cargar siempre algunas relaciones al recuperar un modelo. Para lograr esto, puedes definir una propiedad $with
en el modelo:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo; 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(): BelongsTo { return $this->belongsTo(Author::class); } /** * Get the genre of the book. */ public function genre(): BelongsTo { return $this->belongsTo(Genre::class); }}
Si deseas eliminar un elemento de la propiedad $with
para una sola consulta, puedes usar el método without
:
$books = Book::without('author')->get();
Si deseas anular todos los elementos dentro de la propiedad $with
para una sola consulta, puedes usar el método withOnly
:
$books = Book::withOnly('genre')->get();
Restringiendo Cargas Anticipadas
A veces es posible que desees cargar de forma ansiosa una relación pero también especificar condiciones de consulta adicionales para la consulta de carga ansiosa. Puedes lograr esto pasando un array de relaciones al método with
, donde la clave del array es un nombre de relación y el valor del array es una función anónima que añade restricciones adicionales a la consulta de carga ansiosa:
use App\Models\User;use Illuminate\Contracts\Database\Eloquent\Builder; $users = User::with(['posts' => function (Builder $query) { $query->where('title', 'like', '%code%');}])->get();
En este ejemplo, Eloquent solo cargará de manera ansiosa las publicaciones donde la columna title
de la publicación contenga la palabra code
. Puedes llamar a otros métodos de constructor de consultas para personalizar aún más la operación de carga ansiosa:
$users = User::with(['posts' => function (Builder $query) { $query->orderBy('created_at', 'desc');}])->get();
Limitando la Carga Eager de Relaciones morphTo
Si estás utilizando la carga ansiosa de una relación morphTo
, Eloquent ejecutará múltiples consultas para obtener cada tipo de modelo relacionado. Puedes añadir restricciones adicionales a cada una de estas consultas utilizando el método constrain
de la relación MorphTo
:
use Illuminate\Database\Eloquent\Relations\MorphTo; $comments = Comment::with(['commentable' => function (MorphTo $morphTo) { $morphTo->constrain([ Post::class => function ($query) { $query->whereNull('hidden_at'); }, Video::class => function ($query) { $query->where('type', 'educational'); }, ]);}])->get();
En este ejemplo, Eloquent solo cargará anticipadamente las publicaciones que no han sido ocultadas y los videos que tienen un valor type
de "educational".
Restringiendo Cargas Eager con la Existencia de Relaciones
A veces es posible que necesites verificar la existencia de una relación mientras cargas simultáneamente la relación en función de las mismas condiciones. Por ejemplo, es posible que desees recuperar solo los modelos User
que tienen modelos Post
hijos que coinciden con una condición de consulta dada, mientras que también cargas las publicaciones que coinciden. Puedes lograr esto utilizando el método withWhereHas
:
use App\Models\User; $users = User::withWhereHas('posts', function ($query) { $query->where('featured', true);})->get();
Carga perezosa ansiosa
A veces es posible que necesites cargar de manera anticipada una relación después de que ya se haya recuperado el modelo padre. Por ejemplo, esto puede ser útil si necesitas decidir dinámicamente si cargar modelos relacionados:
use App\Models\Book; $books = Book::all(); if ($someCondition) { $books->load('author', 'publisher');}
Si necesitas establecer restricciones de consulta adicionales en la consulta de carga ansiosa, puedes pasar un array con las claves de las relaciones que deseas cargar. Los valores del array deben ser instancias de funciones anónimas que reciban la instancia de la consulta:
$author->load(['books' => function (Builder $query) { $query->orderBy('published_date', 'asc');}]);
Para cargar una relación solo cuando no se ha cargado ya, utiliza el método loadMissing
:
$book->loadMissing('author');
Carga Perezosa Eager Anidada y morphTo
Si deseas cargar de manera anticipada una relación morphTo
, así como relaciones anidadas en las diversas entidades que pueden ser devueltas por esa relación, puedes usar el método loadMorph
.
Este método acepta el nombre de la relación morphTo
como su primer argumento, y un array de pares modelo / relación como su segundo argumento. Para ayudar a ilustrar este método, consideremos el siguiente modelo:
<?php use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphTo; class ActivityFeed extends Model{ /** * Get the parent of the activity feed record. */ public function parentable(): MorphTo { return $this->morphTo(); }}
En este ejemplo, supongamos que los modelos Event
, Photo
y Post
pueden crear modelos ActivityFeed
. Además, supongamos que los modelos Event
pertenecen a un modelo Calendar
, los modelos Photo
están asociados con modelos Tag
, y los modelos Post
pertenecen a un modelo Author
.
Usando estas definiciones de modelos y relaciones, podemos recuperar instancias del modelo ActivityFeed
y cargar de manera ansiosa todos los modelos parentable
y sus respectivas relaciones anidadas:
$activities = ActivityFeed::with('parentable') ->get() ->loadMorph('parentable', [ Event::class => ['calendar'], Photo::class => ['tags'], Post::class => ['author'], ]);
Previniendo la Carga Perezosa
Como se discutió anteriormente, cargar relaciones de manera ansiosa puede ofrecer beneficios de rendimiento significativos a su aplicación. Por lo tanto, si lo desea, puede instruir a Laravel para que siempre evite la carga perezosa de relaciones. Para lograr esto, puede invocar el método preventLazyLoading
ofrecido por la clase de modelo Eloquent base. Típicamente, debe llamar a este método dentro del método boot
de la clase AppServiceProvider
de su aplicación.
El método preventLazyLoading
acepta un argumento booleano opcional que indica si se debe prevenir la carga diferida. Por ejemplo, es posible que desees desactivar la carga diferida solo en entornos no productivos, de modo que tu entorno de producción continúe funcionando con normalidad incluso si una relación cargada de forma diferida está presente accidentalmente en el código de producción:
use Illuminate\Database\Eloquent\Model; /** * Bootstrap any application services. */public function boot(): void{ Model::preventLazyLoading(! $this->app->isProduction());}
Después de prevenir la carga perezosa, Eloquent lanzará una excepción Illuminate\Database\LazyLoadingViolationException
cuando tu aplicación intente cargar de manera perezosa 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 instruir a las violaciones de carga perezosa para que solo se registren en lugar de interrumpir la ejecución de la aplicación con excepciones:
Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) { $class = $model::class; info("Attempted to lazy load [{$relation}] on model [{$class}].");});
Insertando y Actualizando Modelos Relacionados
El método save
Eloquent proporciona métodos convenientes para agregar nuevos modelos a las relaciones. Por ejemplo, puede que necesites añadir un nuevo comentario a una publicación. 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);
Nota que no accedimos a la relación comments
como una propiedad dinámica. En su lugar, llamamos 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 necesitas guardar múltiples modelos relacionados, puedes usar 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 del modelo dado, pero no añadirán los modelos recién persistidos a ninguna relación en memoria que ya esté cargada en el modelo padre. Si planeas acceder a la relación después de usar los métodos save
o saveMany
, es posible que desees 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;
Guardando Modelos y Relaciones de Forma Recursiva
Si deseas guardar
tu modelo y todas sus relaciones asociadas, puedes usar 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();
El método pushQuietly
se puede utilizar para guardar un modelo y sus relaciones asociadas sin activar ningún evento:
$post->pushQuietly();
El Método create
Además de los métodos save
y saveMany
, también puedes usar el método create
, que acepta un array de atributos, crea un modelo e inserta en la base de datos. La diferencia entre save
y create
es que save
acepta una instancia completa de un modelo Eloquent, mientras que create
acepta un array
PHP simple. 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.',]);
Puedes usar 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.'],]);
Los métodos createQuietly
y createManyQuietly
se pueden utilizar para crear un modelo(s) sin despachar ningún evento:
$user = User::find(1); $user->posts()->createQuietly([ 'title' => 'Post title.',]); $user->posts()->createManyQuietly([ ['title' => 'First post.'], ['title' => 'Second post.'],]);
También puedes utilizar los métodos findOrNew
, firstOrNew
, firstOrCreate
y updateOrCreate
para crear y actualizar modelos en relaciones.
[!NOTA] Antes de usar el método
create
, asegúrate de revisar la documentación de asignación masiva.
Relaciones "Belongs To"
Si deseas asignar un modelo hijo a un nuevo modelo padre, puedes usar 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 foránea 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, puedes usar el método dissociate
. Este método establecerá la clave foránea de la relación en null
:
$user->account()->dissociate(); $user->save();
Relaciones Muchos a Muchos
Adjuntando / Desadjuntando
Eloquent también proporciona métodos para hacer que trabajar con relaciones de muchos a muchos sea más conveniente. Por ejemplo, imaginemos que un usuario puede tener muchos roles y un rol puede tener muchos usuarios. Puedes usar 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);
Al adjuntar una relación a un modelo, también puedes pasar un array de datos adicionales que se inserten 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, utiliza el método detach
. El método detach
eliminará el registro apropiado 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 arrays de IDs como entrada:
$user = User::find(1); $user->roles()->detach([1, 2, 3]); $user->roles()->attach([ 1 => ['expires' => $expires], 2 => ['expires' => $expires],]);
Sincronizando Asociaciones
También puedes usar el método sync
para construir asociaciones de muchos a muchos. El método sync
acepta un array de ID para colocar en la tabla intermedia. Cualquier ID que no esté en el array dado será eliminado de la tabla intermedia. Así que, después de que se complete esta operación, solo existirá en la tabla intermedia los ID en el array dado:
$user->roles()->sync([1, 2, 3]);
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
Si deseas insertar los mismos valores de la tabla intermedia con cada uno de los IDs del modelo sincronizado, puedes usar el método syncWithPivotValues
:
$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);
Si no deseas desasociar los IDs existentes que faltan del array dado, puedes usar el método syncWithoutDetaching
:
$user->roles()->syncWithoutDetaching([1, 2, 3]);
Alternando Asociaciones
La relación muchos a muchos también proporciona un método toggle
que "cambia" el estado de adjunto de los IDs de modelo relacionados dados. Si el ID dado está actualmente adjunto, se desadherirá. Del mismo modo, si está actualmente desadherido, se adjuntará:
$user->roles()->toggle([1, 2, 3]);
También puedes pasar valores de tabla intermedios adicionales con los IDs:
$user->roles()->toggle([ 1 => ['expires' => true], 2 => ['expires' => true],]);
Actualizando un Registro en la Tabla Intermedia
Si necesitas actualizar una fila existente en la tabla intermedia de tu relación, puedes usar el método updateExistingPivot
. Este método acepta la clave foránea del registro intermedio y un array de atributos a actualizar:
$user = User::find(1); $user->roles()->updateExistingPivot($roleId, [ 'active' => false,]);
Actualizando las marcas de tiempo de los padres
Cuando un modelo define una relación belongsTo
o belongsToMany
a otro modelo, como un Comment
que pertenece a un Post
, a veces es útil actualizar la marca de tiempo del padre cuando se actualiza el modelo hijo.
Por ejemplo, cuando se actualiza un modelo Comment
, es posible que desees "tocar" automáticamente la marca de tiempo updated_at
de la Post
propietaria para que se ajuste a la fecha y hora actuales. Para lograr esto, puedes añadir una propiedad touches
a tu modelo hijo que contenga los nombres de las relaciones que deben tener sus marcas de tiempo updated_at
actualizadas cuando se actualice el modelo hijo:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo; 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(): BelongsTo { return $this->belongsTo(Post::class); }}
[!WARNING] Las marcas de tiempo del modelo padre solo se actualizarán si el modelo hijo se actualiza utilizando el método
save
de Eloquent.