Documentación de Laravel 10.x
Aquí encontrarás fragmentos de código de Laravel y consejos útiles sobre desarrollo web.
Las tablas de bases de datos a menudo están relacionadas entre sí. Por ejemplo, una publicación de blog puede tener muchos comentarios o un pedido podría 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:
Las relaciones 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 relaciones como métodos proporciona capacidades poderosas de encadenamiento de métodos y consulta. 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 relaciones, aprendamos a definir cada tipo de relación admitida por Eloquent.
Una relación de 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
debería llamar al método hasOne
y devolver su resultado. El método hasOne
está disponible en 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{ /** * Obtener el teléfono asociado al usuario. */ 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 externa de la relación basándose en el nombre del modelo principal. En este caso, se asume automáticamente que el modelo Phone
tiene una clave externa 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 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 use un valor de clave primaria que no sea id
o la propiedad $primaryKey
de su modelo, puede pasar un tercer argumento al método hasOne
:
return $this->hasOne(Phone::class, 'foreign_key', 'local_key');
Entonces, podemos acceder al modelo Phone
desde nuestro modelo User
. A continuación, definamos una relación en el modelo Phone
que nos permitirá acceder al usuario que es dueño del teléfono. Podemos definir la inversa de una relación hasOne
usando el método belongsTo
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo; class Phone extends Model{ /** * Obtener al usuario dueño del teléfono. */ 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 externa examinando el nombre del método de relación y agregando el sufijo _id
al nombre del método. Entonces, en este caso, Eloquent asume que el modelo Phone
tiene una columna user_id
. Sin embargo, si la clave externa en el modelo Phone
no es user_id
, puede pasar un nombre de clave personalizado como segundo argumento al método belongsTo
:
/** * Obtener al usuario dueño del teléfono. */public function user(): BelongsTo{ return $this->belongsTo(User::class, 'foreign_key');}
Si el modelo principal no utiliza id
como su clave primaria, o si 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 principal:
/** * Obtener al usuario dueño del teléfono. */public function user(): BelongsTo{ return $this->belongsTo(User::class, 'foreign_key', 'owner_key');}
Una relación uno a muchos se utiliza para definir relaciones en las que un solo modelo es el padre de uno o más modelos secundarios. Por ejemplo, una publicación de blog puede tener un número infinito de comentarios. Al igual que todas las demás relaciones de Eloquent, las relaciones uno a muchos se definen mediante la creación de un método en su modelo Eloquent:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\HasMany; class Post extends Model{ /** * Obtener los comentarios para la publicación del blog. */ public function comments(): HasMany { return $this->hasMany(Comment::class); }}
Recuerda, 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 principal y lo sufijará 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 haya 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, puede agregar más restricciones a la consulta de relación llamando al método comments
y continuando encadenando condiciones en la consulta:
$comment = Post::find(1)->comments() ->where('title', 'foo') ->first();
Al igual que el método hasOne
, también puedes anular los nombres de clave foránea y local pasando argumentos adicionales al método hasMany
:
return $this->hasMany(Comment::class, 'foreign_key'); return $this->hasMany(Comment::class, 'foreign_key', 'local_key');
Ahora que podemos acceder a todos los comentarios de una publicación, definamos una relación que permita que un comentario acceda a su publicación principal. Para definir la inversa de una relación hasMany
, define un método de relación en el modelo secundario 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{ /** * Obtener la publicación que es dueña del comentario. */ public function post(): BelongsTo { return $this->belongsTo(Post::class); }}
Una vez que se haya definido la relación, podemos recuperar la publicación principal 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 de la clave foránea predeterminado examinando el nombre del método de relación y añadiendo el nombre del método como sufijo con un _
seguido del nombre de la columna de clave primaria del modelo principal. Entonces, 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 de tu relación no sigue estas convenciones, puedes pasar un nombre de clave foránea personalizado como segundo argumento al método belongsTo
:
/** * Obtener la publicación que es dueña del comentario. */public function post(): BelongsTo{ return $this->belongsTo(Post::class, 'foreign_key');}
Si tu modelo principal no utiliza id
como su clave primaria, o deseas encontrar el modelo asociado usando una columna diferente, puedes pasar un tercer argumento al método belongsTo
especificando la clave personalizada de la tabla principal:
/** * Obtener la publicación que es dueña del comentario. */public function post(): BelongsTo{ return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');}
Las relaciones belongsTo
, hasOne
, hasOneThrough
y morphOne
te permiten definir un modelo predeterminado que se devolverá si la relación dada es null
. Este patrón se conoce comúnmente como el patrón de objeto nulo y puede ayudar a eliminar comprobaciones condicionales en tu 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
:
/** * Obtener al autor de la publicación. */public function user(): BelongsTo{ return $this->belongsTo(User::class)->withDefault();}
Para poblar el modelo predeterminado con atributos, puedes pasar un array o cierre al método withDefault
:
/** * Obtener al autor de la publicación. */public function user(): BelongsTo{ return $this->belongsTo(User::class)->withDefault([ 'name' => 'Guest Author', ]);} /** * Obtener al autor de la publicación. */public function user(): BelongsTo{ return $this->belongsTo(User::class)->withDefault(function (User $user, Post $post) { $user->name = 'Guest Author'; });}
Cuando consultas 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 resultar más conveniente usar el método whereBelongsTo
, que determinará automáticamente la relación y la clave foránea adecuadas para el modelo proporcionado:
$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 principales dentro de la colección:
$users = User::where('vip', true)->get(); $posts = Post::whereBelongsTo($users)->get();
De forma predeterminada, Laravel determinará la relación asociada con el modelo proporcionado según el nombre de la clase del modelo; sin embargo, puedes especificar manualmente el nombre de la relación proporcionándolo como segundo argumento al método whereBelongsTo
:
$posts = Post::whereBelongsTo($user, 'author')->get();
A veces, un modelo puede tener muchos modelos relacionados, pero deseas recuperar fácilmente el modelo relacionado "más reciente" o "más antiguo". Por ejemplo, un modelo User
puede estar relacionado con muchos modelos Order
, pero deseas definir una manera conveniente de interactuar con el pedido más reciente que el usuario ha realizado. Puedes lograr esto usando el tipo de relación hasOne
combinado con los métodos ofMany
:
/** * Obtener el pedido más reciente del usuario. */public function latestOrder(): HasOne{ return $this->hasOne(Order::class)->latestOfMany();}
Del mismo modo, puedes definir un método para recuperar el modelo relacionado "más antiguo", o primero, de una relación:
/** * Obtener el pedido más antiguo del usuario. */public function oldestOrder(): HasOne{ return $this->hasOne(Order::class)->oldestOfMany();}
De forma predeterminada, 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 puedes desear recuperar un solo modelo de una relación más grande utilizando un criterio de ordenación diferente.
Por ejemplo, usando 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:
/** * Obtener el pedido más grande del usuario. */public function largestOrder(): HasOne{ return $this->hasOne(Order::class)->ofMany('price', 'max');}
Advertencia 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 entre muchos en combinación con columnas UUID de PostgreSQL.
A menudo, al recuperar un solo modelo usando los métodos latestOfMany
, oldestOfMany
o ofMany
, ya tienes una relación "has many" definida para el mismo modelo. Por conveniencia, Laravel te permite convertir fácilmente esta relación en una relación "has one" invocando el método one
en la relación:
/** * Obtener los pedidos del usuario. */public function orders(): HasMany{ return $this->hasMany(Order::class);} /** * Obtener el pedido más grande del usuario. */public function largestOrder(): HasOne{ return $this->orders()->one()->ofMany('price', 'max');}
Es posible construir relaciones "has one of many" más avanzadas. Por ejemplo, un modelo Product
puede tener muchos modelos Price
asociados que se retienen en el sistema incluso después de que se publique un nuevo precio. Además, los nuevos datos de precios para el producto pueden publicarse con anticipación para que surtan efecto en una fecha futura a través de una columna published_at
.
Entonces, en resumen, necesitamos recuperar la última tarificación publicada 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 mayor ID. Para lograr esto, debemos pasar un array al método ofMany
que contenga las columnas ordenables que determinan el último precio. Además, se proporcionará un cierre como segundo argumento al método ofMany
. Este cierre será responsable de agregar restricciones adicionales de fecha de publicación a la consulta de la relación:
/** * Obtener los precios actuales del producto. */public function currentPricing(): HasOne{ return $this->hasOne(Price::class)->ofMany([ 'published_at' => 'max', 'id' => 'max', ], function (Builder $query) { $query->where('published_at', '<', now()); });}
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 declarante se puede asociar 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 en 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{ /** * Obtener al dueño del automóvil. */ 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, si las relaciones relevantes ya se han definido en todos los modelos involucrados en la relación, puedes definir fluidamente una relación "has-one-through" invocando el método through
y proporcionando 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 "has-one-through" que conecte al mecánico y al propietario así:
// Sintaxis basada en cadena...return $this->through('cars')->has('owner'); // Dynamic syntax...return $this->throughCars()->hasOwner();
Se utilizarán las convenciones de clave foránea típicas de Eloquent al realizar las consultas de la relación. Si deseas personalizar las claves de la relación, puedes pasarlas como 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{ /** * Obtener al dueño del automóvil. */ 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 se han definido en todos los modelos involucrados en la relación, puedes definir fluidamente una relación "has-one-through" invocando el método through
y proporcionando los nombres de esas relaciones. Este enfoque ofrece la ventaja de reutilizar las convenciones de clave ya definidas en las relaciones existentes:
// Sintaxis basada en cadena...return $this->through('cars')->has('owner'); // Dynamic syntax...return $this->throughCars()->hasOwner();
La relación "has-many-through" proporciona una manera conveniente de acceder a relaciones distantes a través de una relación intermedia. Por ejemplo, supongamos que estamos construyendo una plataforma de implementación como Laravel Vapor. Un modelo Project
podría acceder a muchos modelos Deployment
a través de un modelo intermedio Environment
. Usando este ejemplo, podrías reunir fácilmente todas las implementaciones 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{ /** * Obtener todas las implementaciones para el proyecto. */ 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 fluidamente una relación de "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 de "has-many-through" que conecta el proyecto y los despliegues de la siguiente manera:
// Sintaxis basada en cadena...return $this->through('environments')->has('deployments'); // Dynamic syntax...return $this->throughEnvironments()->hasDeployments();
Aunque la tabla del modelo Deployment
no contiene una columna project_id
, la relación hasManyThrough
proporciona acceso a los despliegues de un proyecto mediante $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 entornos relevantes, se utilizan para consultar la tabla del modelo Deployment
.
Se utilizarán las convenciones típicas de clave externa de Eloquent al realizar consultas de la relación. Si deseas personalizar las claves de la relación, puedes pasarlas como tercer y cuarto argumento al método hasManyThrough
. El tercer argumento es el nombre de la clave externa 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(): 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 fluidamente una relación de "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:
// Sintaxis basada en cadena...return $this->through('environments')->has('deployments'); // Dynamic syntax...return $this->throughEnvironments()->hasDeployments();
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, un usuario puede tener asignados los roles de "Autor" y "Editor"; sin embargo, esos roles también pueden asignarse a otros usuarios. Entonces, 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 modelos relacionados y contiene las columnas user_id
e role_id
. Esta tabla se utiliza como tabla intermedia que vincula a los usuarios y roles.
Recuerda, 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 para que los roles se asignen a varios 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 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 es utilizada por 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{ /** * Los roles a los que pertenece el usuario. */ public function roles(): BelongsToMany { return $this->belongsToMany(Role::class); }}
Una vez que la relación está definida, puedes acceder a los roles del usuario usando la propiedad dinámica de relación 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 agregar restricciones adicionales a la consulta de la relación llamando al método roles
y continuando encadenando condiciones en la consulta:
$roles = User::find(1)->roles()->orderBy('name')->get();
Para determinar el nombre de la tabla de la tabla intermedia de la relación, Eloquent unirá los dos nombres de modelo relacionados en orden alfabético. Sin embargo, eres libre de anular 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 externa del modelo en el que estás definiendo la relación, mientras que el cuarto argumento es el nombre de la clave externa 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 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{ /** * Los usuarios que pertenecen al rol. */ public function users(): BelongsToMany { return $this->belongsToMany(User::class); }}
Como puedes ver, la relación se define exactamente de la misma manera que su contraparte del modelo User
, con la excepción de referenciar al modelo App\Models\User
. Dado que estamos reutilizando el método belongsToMany
, todas las opciones habituales de personalización de tabla y clave están disponibles al definir la "inversa" de relaciones muchos a muchos.
Como ya has aprendido, trabajar con relaciones muchos a muchos requiere la presencia de una tabla intermedia. Eloquent proporciona 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 usando el atributo pivot
en los modelos:
use App\Models\User; $user = User::find(1); foreach ($user->roles as $role) { echo $role->pivot->created_at;}
Observa que cada modelo Role
que recuperamos se le asigna automáticamente un atributo pivot
. Este atributo contiene un modelo que representa la tabla intermedia.
Por defecto, solo las claves de 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');
Como se señaló anteriormente, se pueden acceder a los atributos de la tabla intermedia en los modelos a través del atributo pivot
. Sin embargo, eres libre de personalizar el nombre de este atributo para reflejar mejor su propósito dentro de tu aplicació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 marcas de tiempo
created_at
yupdated_at
.
pivot
Por ejemplo, si tu aplicación contiene usuarios que pueden suscribirse a podcasts, es probable que tengas una relación de muchos a muchos entre usuarios y podcasts. Si este es el caso, es posible que desees cambiar el nombre del atributo de la tabla intermedia a subscription
en lugar de pivot
. Esto se puede hacer utilizando el método as
al definir la relación:
Una vez que se ha especificado el atributo personalizado de la tabla intermedia, puedes acceder a los datos de la tabla intermedia utilizando el nombre personalizado:
return $this->belongsToMany(Podcast::class) ->as('subscription') ->withTimestamps();
Una vez que se ha especificado el atributo personalizado de la tabla intermedia, 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;}
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');
Puedes ordenar los resultados devueltos por consultas de relaciones belongsToMany
utilizando el método orderByPivot
. En el siguiente ejemplo, recuperaremos todas las últimas insignias para el usuario:
return $this->belongsToMany(Badge::class) ->where('rank', 'gold') ->orderByPivot('created_at', 'desc');
Si deseas definir un modelo personalizado para representar la tabla intermedia de tu relación muchos a muchos, puedes llamar al método using
al definir la relación. Los modelos pivote personalizados te dan la oportunidad de definir comportamientos adicionales en el modelo pivote, como métodos y conversiones.
Los modelos pivote personalizados para relaciones muchos a muchos deben extender la clase Illuminate\Database\Eloquent\Relations\Pivot
, mientras que los modelos pivote polimórficos muchos a muchos deben extender la clase Illuminate\Database\Eloquent\Relations\MorphPivot
. Por ejemplo, podemos definir un modelo Role
que use un modelo pivote personalizado RoleUser
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Role extends Model{ /** * Los usuarios que pertenecen al rol. */ 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{ // ...}
Advertencia Los modelos pivote no pueden usar el rasgo
SoftDeletes
. Si necesitas eliminar suavemente registros pivote, considera convertir tu modelo pivote en un modelo Eloquent real.
Si has definido una relación muchos a muchos que utiliza un modelo pivote personalizado y ese modelo pivote tiene una clave primaria de incremento automático, asegúrate de que tu clase de modelo pivote personalizado defina una propiedad incrementing
que esté establecida en true
.
/** * Indica si los ID son autoincrementables. * * @var bool */public $incrementing = true;
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 entradas de blog y videos. En tal aplicación, un modelo Comment
podría pertenecer tanto a los modelos Post
como a Video
.
Una relación polimórfica de uno a uno es similar a una relación de uno a uno típica; sin embargo, el modelo hijo puede pertenecer a más de un tipo de modelo mediante una sola asociación. Por ejemplo, una entrada de blog Post
y un User
pueden compartir una relación polimórfica con un modelo Image
. El uso de una relación polimórfica de uno a uno te permite tener una sola 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
Observa las columnas imageable_id
e imageable_type
en la tabla images
. La columna imageable_id
contendrá el valor de ID de la entrada o usuario, mientras que la columna imageable_type
contendrá el nombre de clase del modelo principal. La columna imageable_type
se utiliza en Eloquent para determinar qué "tipo" de modelo principal devolver al acceder a la relación imageable
. En este caso, la columna contendría App\Models\Post
o App\Models\User
.
A continuación, examinemos las definiciones de modelos necesarias para construir esta relación:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphTo; class Image extends Model{ /** * Obtener el modelo parent del modelo imageable (usuario o publicación). */ public function imageable(): MorphTo { return $this->morphTo(); }} use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphOne; class Post extends Model{ /** * Obtener la imagen de la publicación. */ 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{ /** * Obtener la imagen del usuario. */ public function image(): MorphOne { return $this->morphOne(Image::class, 'imageable'); }}
Una vez que se han definido la tabla de la base de datos y los modelos, puedes acceder a las relaciones a través de tus modelos. Por ejemplo, para recuperar la imagen de una entrada, podemos acceder a la propiedad dinámica de relación image
:
use App\Models\Post; $post = Post::find(1); $image = $post->image;
Puedes recuperar el modelo principal 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
. Entonces, accederemos a ese método como una propiedad dinámica de relación:
use App\Models\Image; $image = Image::find(1); $imageable = $image->imageable;
La relación imageable
en el modelo Image
devolverá una instancia de Post
o User
, según el tipo de modelo que sea propietario de la imagen.
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 pasar siempre el nombre de la relación como primer argumento al método morphTo
. Normalmente, este valor debería coincidir con el nombre del método, por lo que puedes usar la constante __FUNCTION__
de PHP:
/** * Obtener el modelo al que pertenece la imagen. */public function imageable(): MorphTo{ return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id');}
Una relación polimórfica de uno a muchos es similar a una relación de uno a muchos típica; sin embargo, el modelo hijo puede pertenecer a más de un tipo de modelo mediante una sola asociación. Por ejemplo, imagina que los usuarios de tu aplicación pueden "comentar" en entradas y videos. Usando relaciones polimórficas, puedes usar una sola tabla comments
para contener comentarios tanto para entradas como para videos. Primero, examinemos la estructura de la tabla 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
A continuación, examinemos las definiciones de 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{ /** * Obtener el modelo parent del modelo commentable (publicación o video). */ public function commentable(): MorphTo { return $this->morphTo(); }} use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphMany; class Post extends Model{ /** * Obtener todos los comentarios de la publicación. */ 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{ /** * Obtener todos los comentarios del video. */ public function comments(): MorphMany { return $this->morphMany(Comment::class, 'commentable'); }}
Una vez que se han definido la tabla de la base de datos y los modelos, 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 una entrada, podemos usar 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, ese es el método commentable
en el modelo Comment
. Entonces, 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 de Post
o Video
, según el tipo de modelo que sea el padre del comentario.
A veces, un modelo puede tener muchos modelos relacionados, pero desea recuperar fácilmente el modelo relacionado más "reciente" o "antiguo". Por ejemplo, un modelo User
puede estar relacionado con muchos modelos Image
, pero desea definir una forma conveniente de interactuar con la imagen más reciente que el usuario ha subido. Puede lograr esto utilizando el tipo de relación morphOne
combinado con los métodos ofMany
:
/** * Obtener la imagen más reciente del usuario. */public function latestImage(): MorphOne{ return $this->morphOne(Image::class, 'imageable')->latestOfMany();}
De manera similar, puede definir un método para recuperar el modelo relacionado más "antiguo" o primero de una relación:
/** * Obtener la imagen más antigua del usuario. */public function oldestImage(): MorphOne{ return $this->morphOne(Image::class, 'imageable')->oldestOfMany();}
De forma predeterminada, los métodos latestOfMany
y oldestOfMany
recuperarán el modelo relacionado más reciente o antiguo según la clave primaria del modelo, que debe ser ordenable. Sin embargo, a veces puede desear recuperar un solo modelo de una relación más grande utilizando un criterio de clasificación diferente.
Por ejemplo, utilizando el método ofMany
, puede recuperar la imagen más "gustada" del usuario. El método ofMany
acepta la columna ordenable como su primer argumento y la función de agregación (min
o max
) que se aplicará al consultar el modelo relacionado:
/** * Obtener la imagen más popular del usuario. */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, consulte la documentación de uno de muchos avanzado.
Las relaciones polimórficas 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 muchos a muchos en esta situación permitiría que su 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
Nota Antes de adentrarse en relaciones polimórficas muchos a muchos, puede beneficiarse de leer la documentación sobre relaciones típicas muchos a muchos.
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 llamará 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". Según 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{ /** * Obtener todas las etiquetas para la publicación. */ public function tags(): MorphToMany { return $this->morphToMany(Tag::class, 'taggable'); }}
A continuación, en el modelo Tag
, debe definir un método para cada uno de sus posibles modelos padres. Entonces, en este ejemplo, definiremos un método posts
y un método videos
. Ambos de estos 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". Según 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 Tag extends Model{ /** * Obtener todas las publicaciones asignadas a esta etiqueta. */ public function posts(): MorphToMany { return $this->morphedByMany(Post::class, 'taggable'); } /** * Obtener todos los videos asignados a esta etiqueta. */ public function videos(): MorphToMany { return $this->morphedByMany(Video::class, 'taggable'); }}
Una vez que se definan la tabla de la base de datos y los modelos, podrá acceder a las relaciones a través de sus modelos. Por ejemplo, para acceder a todas las etiquetas de una publicación, puede usar 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 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) { // ...}
De forma predeterminada, Laravel utilizará el nombre de clase completamente cualificado 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 a un modelo Video
, el tipo predeterminado commentable_type
sería App\Models\Post
o App\Models\Video
, respectivamente. Sin embargo, es posible que desee desacoplar estos valores de la estructura interna de su aplicación.
Por ejemplo, en lugar de usar los nombres de modelo como el "tipo", podemos usar cadenas simples como post
y video
. Al hacerlo, los valores de la columna polimórfica "tipo" en nuestra base de datos seguirán siendo válidos incluso si los modelos se renombran:
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 separado si lo desea.
Puede determinar el alias morfo de un modelo dado en tiempo de ejecución utilizando el método getMorphClass
del modelo. A la inversa, puede determinar el nombre de clase completamente cualificado asociado con un alias morfo usando el método Relation::getMorphedModel
:
use Illuminate\Database\Eloquent\Relations\Relation; $alias = $post->getMorphClass(); $class = Relation::getMorphedModel($alias);
Advertencia Al agregar un "mapa de morfismo" a tu aplicación existente, cada valor de columna
*_type
morfable en tu base de datos que aún contenga una clase completamente calificada deberá convertirse a su nombre de "mapa".
Puede usar el método resolveRelationUsing
para definir relaciones entre modelos Eloquent en tiempo de ejecución. Aunque generalmente no se recomienda para el desarrollo normal de la aplicación, esto puede ser útil ocasionalmente al desarrollar paquetes de Laravel.
El método resolveRelationUsing
acepta el nombre de relación deseado como su primer argumento. El segundo argumento pasado al método debe ser un cierre que acepte la instancia del modelo y devuelva una definición de relación Eloquent válida. Por lo general, debe configurar relaciones dinámicas dentro del método boot
de un proveedor de servicios:
use App\Models\Order;use App\Models\Customer; Order::resolveRelationUsing('customer', function (Order $orderModel) { return $orderModel->belongsTo(Customer::class, 'customer_id');});
Advertencia Al definir relaciones dinámicas, siempre proporciona argumentos de nombres de clave explícitos a los métodos de relación de Eloquent.
Dado que todas las relaciones 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 continuar encadenando restricciones en la consulta de relación antes de ejecutar finalmente la consulta SQL contra su base de datos.
Por ejemplo, imagine 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{ /** * Obtener todas las publicaciones del usuario. */ public function posts(): HasMany { return $this->hasMany(Post::class); }}
Puede 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();
Puede utilizar cualquiera de los métodos del constructor de consultas de Laravel en la relación, así que asegúrese de explorar la documentación del constructor de consultas para conocer todos los métodos que tiene disponibles.
orWhere
Después de RelacionesComo se muestra en el ejemplo anterior, tiene libertad para agregar restricciones adicionales a las relaciones al consultarlas. Sin embargo, tenga 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 puede 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á limitada a un usuario específico:
select *from postswhere 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 producirá el siguiente SQL. Observe que el agrupamiento lógico ha agrupado correctamente las restricciones y la consulta sigue estando limitada a un usuario específico:
select *from postswhere user_id = ? and (active = 1 or votes >= 100)
Si no necesita agregar restricciones adicionales a una consulta de relación Eloquent, puede 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ámica realizan una "carga perezosa", lo que significa que solo cargarán sus datos de relación cuando realmente las acceda. Debido a esto, los desarrolladores a menudo utilizan carga ansiosa para cargar previamente 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.
Al recuperar registros de modelos, es posible que desee limitar los resultados en función de la existencia de una relación. Por ejemplo, imagine que desea recuperar todas las publicaciones de blog que tengan al menos un comentario. Para hacerlo, puede pasar el nombre de la relación a los métodos has
y orHas
:
use App\Models\Post; // Obtener todas las publicaciones que tienen al menos un comentario...$posts = Post::has('comments')->get();
También puede especificar un operador y un valor de recuento para personalizar aún más la consulta:
// Obtener todas las publicaciones que tienen tres o más comentarios...$posts = Post::has('comments', '>=', 3)->get();
Las declaraciones has
anidadas se pueden construir utilizando la notación "punto". Por ejemplo, puede recuperar todas las publicaciones que tengan al menos un comentario que tenga al menos una imagen:
// Obtener publicaciones que tienen al menos un comentario con imágenes...$posts = Post::has('comments.images')->get();
Si necesita aún más potencia, puede utilizar los métodos whereHas
y orWhereHas
para definir restricciones de consulta adicionales en sus consultas has
, como inspeccionar el contenido de un comentario:
use Illuminate\Database\Eloquent\Builder; // Obtener publicaciones con al menos un comentario que contiene palabras como code%...$posts = Post::whereHas('comments', function (Builder $query) { $query->where('content', 'like', 'code%');})->get(); // Obtener publicaciones con al menos diez comentarios que contienen palabras como code%...$posts = Post::whereHas('comments', function (Builder $query) { $query->where('content', 'like', 'code%');}, '>=', 10)->get();
Advertencia 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.
Si desea consultar la existencia de una relación con una única condición simple adjunta a la consulta de relación, puede resultar más conveniente utilizar 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 puede especificar un operador:
$posts = Post::whereRelation( 'comments', 'created_at', '>=', now()->subHour())->get();
Al recuperar registros de modelos, es posible que desee limitar los resultados en función de la ausencia de una relación. Por ejemplo, imagine que desea recuperar todas las publicaciones de blog que no tengan ningún comentario. Para hacerlo, 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 agregar 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 contra una relación anidada. Por ejemplo, la siguiente consulta recuperará todas las publicaciones que no tengan comentarios; sin embargo, se incluirán en los resultados las publicaciones que tengan comentarios de autores que no están prohibidos:
use Illuminate\Database\Eloquent\Builder; $posts = Post::whereDoesntHave('comments.author', function (Builder $query) { $query->where('banned', 0);})->get();
morphTo
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 su primer argumento. A continuación, los métodos aceptan los nombres de los modelos relacionados que desea incluir en la consulta. Finalmente, puede proporcionar un cierre que personalice la consulta de relación:
use App\Models\Comment;use App\Models\Post;use App\Models\Video;use Illuminate\Database\Eloquent\Builder; // Obtener comentarios asociados a publicaciones o videos con un título como code%...$comments = Comment::whereHasMorph( 'commentable', [Post::class, Video::class], function (Builder $query) { $query->where('title', 'like', 'code%'); })->get(); // Obtener comentarios asociados a publicaciones con un título que no sea como code%...$comments = Comment::whereDoesntHaveMorph( 'commentable', Post::class, function (Builder $query) { $query->where('title', 'like', 'code%'); })->get();
Ocasionalmente, puede que necesite agregar restricciones de consulta basadas en el "tipo" del modelo polimórfico relacionado. El cierre pasado al método whereHasMorph
puede recibir un valor $type
como su segundo argumento. Este argumento le 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();
En lugar de pasar un array de posibles modelos polimórficos, puede 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();
A veces, es posible que desee contar el número de modelos relacionados para una relación dada sin cargar realmente los modelos. Para lograr esto, puede utilizar el método withCount
. El método withCount
colocará un atributo {relación}_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 agregar 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 puede darle un alias al resultado del recuento de la relación, lo que permite realizar varios 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;
Utilizando el método loadCount
, puede cargar un recuento de relación después de que se haya recuperado el modelo principal:
$book = Book::first(); $book->loadCount('genres');
Si necesita establecer restricciones de consulta adicionales en la consulta de recuento, puede pasar un array indexado por las relaciones que desea contar. Los valores del array deben ser cierres que reciban la instancia del constructor de consultas:
$book->loadCount(['reviews' => function (Builder $query) { $query->where('rating', 5);}])
Si combina withCount
con una declaración select
, asegúrese de llamar a withCount
después del método select
:
$posts = Post::select(['title', 'body']) ->withCount('comments') ->get();
Además del método withCount
, Eloquent proporciona los métodos withMin
, withMax
, withAvg
, withSum
y withExists
. Estos métodos colocarán un atributo {relación}_{función}_{columna}
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 de agregación 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 están disponibles versiones diferidas de estos métodos. Estas operaciones de agregación adicionales se pueden realizar en modelos Eloquent que ya se han recuperado:
$post = Post::first(); $post->loadSum('comments', 'votes');
Si combina estos métodos de agregación con una declaración select
, asegúrese de llamar a los métodos de agregación después del método select
:
$posts = Post::select(['title', 'body']) ->withExists('comments') ->get();
morphTo
Si desea cargar ansiosamente 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, supongamos que los modelos Photo
y Post
pueden crear modelos ActivityFeed
. Supondremos que el modelo ActivityFeed
define una relación "morph to" llamada parentable
que nos permite recuperar el modelo principal Photo
o Post
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 ansiosamente los modelos principales parentable
para cada instancia de ActivityFeed
. Además, queremos recuperar el número de etiquetas asociadas con cada foto principal y el número de comentarios asociados con cada publicación principal:
use Illuminate\Database\Eloquent\Relations\MorphTo; $activities = ActivityFeed::with([ 'parentable' => function (MorphTo $morphTo) { $morphTo->morphWithCount([ Photo::class => ['tags'], Post::class => ['comments'], ]); }])->get();
Supongamos que ya hemos recuperado un conjunto de modelos ActivityFeed
y ahora nos gustaría cargar los recuentos de relaciones anidadas para los diversos modelos parentable
asociados con las actividades. Puede utilizar el método loadMorphCount
para lograr esto:
$activities = ActivityFeed::with('parentable')->get(); $activities->loadMorphCount('parentable', [ Photo::class => ['tags'], Post::class => ['comments'],]);
Cuando se accede a las relaciones de Eloquent como propiedades, los modelos relacionados se cargan "perezosamente". Esto significa que los datos de la relación no se cargan realmente hasta que accede a la propiedad por primera vez. Sin embargo, Eloquent puede cargar ansiosamente las relaciones en el momento en que consulta el modelo principal. La carga ansiosa alivia el problema de la consulta "N + 1". Para ilustrar el problema de la consulta N + 1, considere un modelo Book
que "pertenece a" a un modelo Author
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo; class Book extends Model{ /** * Obtener al autor que escribió el libro. */ 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 y luego otra consulta para cada libro con el fin de recuperar el autor del libro. Entonces, 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 ansiosa para reducir esta operación a solo dos consultas. Al construir una consulta, puede especificar qué relaciones deben cargarse ansiosamente 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, ...)
A veces es posible que necesite cargar ansiosamente varias relaciones diferentes. Para hacerlo, simplemente pase un array de relaciones al método with
:
$books = Book::with(['author', 'publisher'])->get();
Para cargar ansiosamente las relaciones de las relaciones de una relación, puede utilizar la notación "punto". Por ejemplo, carguemos ansiosamente todos los autores del libro y todos los contactos personales del autor:
$books = Book::with('author.contacts')->get();
Alternativamente, puede especificar relaciones anidadas cargadas ansiosamente proporcionando un array anidado al método with
, lo cual puede ser conveniente al cargar varias relaciones anidadas:
$books = Book::with([ 'author' => [ 'contacts', 'publisher', ],])->get();
morphTo
Si desea cargar ansiosamente una relación morphTo
, así como relaciones anidadas en las diversas 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 ilustrar este método, consideremos el siguiente modelo:
<?php use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphTo; class ActivityFeed extends Model{ /** * Obtener el padre del registro de actividad del feed. */ 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
.
Utilizando estas definiciones y relaciones de modelos, podemos recuperar instancias del modelo ActivityFeed
y cargar ansiosamente todos los modelos parentable
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();
No siempre necesitará todas las columnas de las relaciones que está recuperando. Por esta razón, Eloquent le permite especificar qué columnas de la relación le gustaría recuperar:
$books = Book::with('author:id,name,book_id')->get();
Advertencia Cuando uses esta función, siempre debes incluir la columna
id
y cualquier columna de clave externa relevante en la lista de columnas que deseas recuperar.
A veces es posible que desee cargar siempre algunas relaciones al recuperar un modelo. Para lograr esto, puede 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{ /** * Las relaciones que siempre deben cargarse. * * @var array */ protected $with = ['author']; /** * Obtener al autor que escribió el libro. */ public function author(): BelongsTo { return $this->belongsTo(Author::class); } /** * Obtener el género del libro. */ public function genre(): BelongsTo { 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 anular todos los elementos dentro de la propiedad $with
para una sola consulta, puede utilizar el método withOnly
:
$books = Book::withOnly('genre')->get();
A veces, puede desear cargar ansiosamente una relación pero también especificar condiciones de consulta adicionales para la consulta de carga ansiosa. Puede lograr esto pasando un array de relaciones al método with
, donde la clave del array es el nombre de la relación y el valor del array es un cierre que agrega condiciones 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á ansiosamente las publicaciones donde la columna title
de la publicación contiene la palabra code
. Puede llamar a otros métodos del generador 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();
Advertencia Los métodos constructores de consultas
limit
ytake
no se pueden usar al restringir las cargas ansiosas.
morphTo
Si está cargando ansiosamente una relación morphTo
, Eloquent ejecutará varias consultas para recuperar cada tipo de modelo relacionado. Puede agregar condiciones adicionales a cada una de estas consultas usando 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á ansiosamente las publicaciones que no hayan sido ocultas y los videos que tengan un valor type
de "educativo".
A veces puede ser necesario verificar la existencia de una relación al mismo tiempo que se carga la relación según las mismas condiciones. Por ejemplo, puede desear solo recuperar modelos User
que tengan modelos secundarios Post
que coincidan con una condición de consulta dada, al mismo tiempo que carga ansiosamente las publicaciones coincidentes. Puede lograr esto usando el método withWhereHas
:
use App\Models\User; $users = User::withWhereHas('posts', function ($query) { $query->where('featured', true);})->get();
A veces puede ser necesario cargar ansiosamente una relación después de que el modelo principal ya ha sido recuperado. Por ejemplo, esto puede ser útil si necesita decidir dinámicamente si cargar o no modelos relacionados:
use App\Models\Book; $books = Book::all(); if ($someCondition) { $books->load('author', 'publisher');}
Si necesita establecer condiciones de consulta adicionales en la consulta de carga ansiosa, puede pasar un array con claves de las relaciones que desea cargar. Los valores del array deben ser instancias de cierre 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 aún no se ha cargado, use el método loadMissing
:
$book->loadMissing('author');
morphTo
Si desea cargar ansiosamente una relación morphTo
, así como relaciones anidadas en las diversas entidades que pueden ser devueltas por esa relación, puede 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 ilustrar este método, consideremos el siguiente modelo:
<?php use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphTo; class ActivityFeed extends Model{ /** * Obtener el padre del registro de actividad del feed. */ 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
.
Utilizando estas definiciones y relaciones de modelos, podemos recuperar instancias del modelo ActivityFeed
y cargar ansiosamente 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'], ]);
Como se mencionó anteriormente, la carga ansiosa de relaciones a menudo puede proporcionar beneficios significativos de rendimiento para su aplicación. Por lo tanto, si lo desea, puede indicar a Laravel que siempre evite la carga perezosa de relaciones. Para lograr esto, puede invocar el método preventLazyLoading
ofrecido por la clase base del modelo Eloquent. Típicamente, debería 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 evitar la carga perezosa. Por ejemplo, es posible que desee desactivar la carga perezosa solo en entornos que no sean de producción para que su entorno de producción continúe funcionando normalmente incluso si hay una relación cargada perezosamente en el código de producción por accidente:
use Illuminate\Database\Eloquent\Model; /** * Inicializar todos los servicios de la aplicación. */public function boot(): void{ 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 cargar perezosamente cualquier relación de Eloquent.
Puede personalizar el comportamiento de las violaciones de carga perezosa utilizando el método handleLazyLoadingViolationsUsing
. Por ejemplo, mediante este método, puede instruir a las violaciones de carga perezosa 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}].");});
save
Eloquent proporciona métodos convenientes para agregar nuevos modelos a relaciones. Por ejemplo, tal vez necesitas agregar un nuevo comentario a una publicación. En lugar de establecer manualmente el atributo post_id
en el modelo Comment
, puedes insertar el comentario usando 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);
Ten en cuenta 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
agregará automáticamente el valor post_id
apropiado al nuevo modelo Comment
.
Si necesitas guardar varios 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 de modelos proporcionadas, pero no agregarán los modelos recién persistidos a ninguna relación en memoria que ya esté cargada en el modelo principal. Si planeas acceder a la relación después de usar los métodos save
o saveMany
, puedes usar el método refresh
para volver a cargar el modelo y sus relaciones:
$post->comments()->save($comment); $post->refresh(); // Todos los comentarios, incluido el comentario recién guardado...$post->comments;
Si deseas guardar
tu modelo y todas sus relaciones asociadas, puedes usar el método push
. En este ejemplo, se guardará el modelo Post
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 usar para guardar un modelo y sus relaciones asociadas sin lanzar eventos:
$post->pushQuietly();
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 lo inserta en la base de datos. La diferencia entre save
y create
es que save
acepta una instancia completa de 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.',]);
Puedes usar el método createMany
para crear varios 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 usar para crear un modelo o varios modelos sin despachar eventos:
$user = User::find(1); $user->posts()->createQuietly([ 'title' => 'Post title.',]); $user->posts()->createManyQuietly([ ['title' => 'First post.'], ['title' => 'Second post.'],]);
También puedes usar los métodos findOrNew
, firstOrNew
, firstOrCreate
y updateOrCreate
para crear y actualizar modelos en relaciones.
Nota Antes de usar el método
create
, asegúrese de revisar la documentación sobre asignación masiva.
Si deseas asignar un modelo secundario a un nuevo modelo principal, 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 secundario:
use App\Models\Account; $account = Account::find(10); $user->account()->associate($account); $user->save();
Para quitar un modelo principal de un modelo secundario, puedes usar el método dissociate
. Este método establecerá la clave foránea de la relación a null
:
$user->account()->dissociate(); $user->save();
Eloquent también proporciona métodos para facilitar 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 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 insertarán en la tabla intermedia:
$user->roles()->attach($roleId, ['expires' => $expires]);
A veces puede ser necesario quitar un rol de un usuario. Para quitar un registro de relación de muchos a muchos, usa 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:
// Desvincular un solo rol del usuario...$user->roles()->detach($roleId); // Desvincular todos los roles del usuario...$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],]);
También puedes usar el método sync
para construir asociaciones de muchos a muchos. El método sync
acepta un array de IDs para colocar en la tabla intermedia. Cualquier ID que no esté en el array dado se eliminará de la tabla intermedia. Entonces, después de que se complete esta operación, solo existirán en la tabla intermedia los IDs en el array dado:
$user->roles()->sync([1, 2, 3]);
También puedes pasar valores adicionales de la tabla intermedia con los IDs:
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
Si deseas insertar los mismos valores de la tabla intermedia con cada uno de los IDs de modelo sincronizados, puedes usar el método syncWithPivotValues
:
$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);
Si no deseas separar los IDs existentes que faltan en el array dado, puedes usar el método syncWithoutDetaching
:
$user->roles()->syncWithoutDetaching([1, 2, 3]);
La relación de 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 separará. Del mismo modo, si actualmente está separado, se adjuntará:
$user->roles()->toggle([1, 2, 3]);
También puedes pasar valores adicionales de la tabla intermedia con los IDs:
$user->roles()->toggle([ 1 => ['expires' => true], 2 => ['expires' => true],]);
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 para actualizar:
$user = User::find(1); $user->roles()->updateExistingPivot($roleId, [ 'active' => false,]);
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 padre cuando el modelo secundario se actualiza.
Por ejemplo, cuando se actualiza un modelo Comment
, es posible que desees "tocar" automáticamente la marca de tiempo updated_at
del Post
propietario para que se establezca en la fecha y hora actuales. Para lograr esto, puedes agregar una propiedad touches
a tu modelo secundario que contenga los nombres de las relaciones cuyas marcas de tiempo updated_at
deben actualizarse cuando se actualiza el modelo secundario:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo; class Comment extends Model{ /** * Todas las relaciones que deben actualizarse siempre. * * @var array */ protected $touches = ['post']; /** * Obtener la publicación a la que pertenece el comentario. */ public function post(): BelongsTo { return $this->belongsTo(Post::class); }}
Advertencia Las marcas de tiempo del modelo principal solo se actualizarán si el modelo secundario se actualiza mediante el método
save
de Eloquent.