⬆️ Menú principal ⬅️ Anterior (DB Models and Eloquent) ➡️ SIguiente (Migrations)
- OrderBy on Eloquent relationships
- Add where statement to Many-to-Many relation
- Get the newest (or oldest) item of another relation
- Conditional relationships
- Raw DB Queries: havingRaw()
- Eloquent has() deeper
- Has Many. How many exactly?
- Default model
- Use hasMany to create Many
- Multi level Eager Loading
- Eager Loading with Exact Columns
- Touch parent updated_at easily
- Always Check if Relationship Exists
- Use withCount() to Calculate Child Relationships Records
- Extra Filter Query on Relationships
- Load Relationships Always, but Dynamically
- Instead of belongsTo, use hasMany
- Rename Pivot Table
- Update Parent in One Line
- Laravel 7+ Foreign Keys
- Combine Two "whereHas"
- Check if Relationship Method Exists
- Pivot Table with Extra Relations
- Load Count on-the-fly
- Randomize Relationship Order
- Filter hasMany relationships
- Filter by many-to-many relationship pivot column
- A shorter way to write whereHas
- You can add conditions to your relationships
- New
whereBelongsTo()
Eloquent query builder method - The
is()
method of one-to-one relationships for comparing models whereHas()
multiple connections- Update an existing pivot record
- Relation that will get the newest (or oldest) item
- Replace your custom queries with ofMany
- Avoid data leakage when using orWhere on a relationship
Pudes espeicificar orderBy()
para order dada la columna, directamente en las relaciones Eloquent.
public function products()
{
return $this->hasMany(Product::class);
}
public function productsByName()
{
return $this->hasMany(Product::class)->orderBy('name');
}
En las relaciones muchos a muchos, puedes añadir clausulas where
a las tablas intermedias (pivot) usando el método wherePivot
.
class Developer extends Model
{
// Get all clients related to this developer
public function clients()
{
return $this->belongsToMany(Clients::class);
}
// Get only local clients
public function localClients()
{
return $this->belongsToMany(Clients::class)
->wherePivot('is_local', true);
}
}
⭐ Aportación de @cosmeescobedo
Desde Laravel 8.42, en los modelos Eloquent puedes definir una relación para traer los más nuevos o los más viejos.
/**
* Los usuarios más nuevos
*/
public function latestOrder()
{
return $this->hasOne(Order::class)->latestOfMany();
}
/**
* Los uusarios mas antiguo
*/
public function oldestOrder()
{
return $this->hasOne(Order::class)->oldestOfMany();
}
Si te das cuenta que usas las mismas relaciones con propiedades similares como "where" puedes crear una nueva relación con ellas mismas.
Modelo:
public function comments()
{
return $this->hasMany(Comment::class);
}
public function approved_comments()
{
return $this->hasMany(Comment::class)->where('approved', 1);
}
Puedes usar consultas Raw DB
en varios lugares, funciones incluidas comohavingRaw()
después groupBy()
.
Product::groupBy('category_id')->havingRaw('COUNT(*) > 1')->get();
Puedes usar la función Eloquent has()
para hacer queries en las relaciones en más de dos niveles de profundidad.
// Author -> hasMany(Book::class);
// Book -> hasMany(Rating::class);
$authors = Author::has('books.ratings')->get();
En las relaciones Eloquent hasMany()
puedes filtrar los registros que tienen X cantidades de relaciones.
// Author -> hasMany(Book::class)
$authors = Author::has('books', '>', 5)->get();
Puedes asignar un modelo por defecto en las relaciones belongsTo
, para evitar errores fatales cuando llamamos las relaciones como: {{ $post->user->name }}
, si el $post->user no existe.
public function user()
{
return $this->belongsTo(User::class)->withDefault();
}
Si tienes realciones hasMany()
, puedes usar el método saveMany()
para guardar multiples entradas desde el modelo padre al modelo hijo con una sola linea.
$post = Post::find(1);
$post->comments()->saveMany([
new Comment(['message' => 'First comment']),
new Comment(['message' => 'Second comment']),
]);
En Laravel puedes realizar Eager Loading en multiples niveles de relaciones, por ejemplo: cargamos a el modelo Book su author y dentro del author su relación país:
$users = Book::with('author.country')->get();
Cuando realizamos Eager Loading podemos especificar qué columnas debemos traer nada más.
$users = Book::with('author:id,name')->get();
Incluso podemos hacerlolo en un segundo nivel:
$users = Book::with('author.country:id,name')->get();
Si quieres actuaizar un registro y asu vez actualizar la columna updated_at
de la relación padre, solo usa la propiedad $touches = ['post'];
en el modelo hijo .
class Comment extends Model
{
protected $touches = ['post'];
}
Nunca accedas a un campo de la relación hijo ( $model->relationship->field
) sin antes revisar si la relación existe.
Podrían haberlo borrado por la razón que sea.
Podrías utilizar una condicional como: {{ $model->relationship->field ?? '' }}
en Blade o {{ optional($model->relationship)->field }}
. Con la llegada de PHP 8.0 existe nullsafe operator que funciona así: {{ $model->relationship?->field) }}
Si tienes relaciones hasMany()
y quieres calcular cuantas relaciones hijas contiene, no necesitas escribir consultas especiales. Por ejemplo si tienes posts y comentarios en el modelo Usuario, escribes withCount()
:
public function index()
{
$users = User::withCount(['posts', 'comments'])->get();
return view('users', compact('users'));
}
Para usar los resultados de la consulta, en las vistas accedemos a la variable:
{relationship}_count
:
@foreach ($users as $user)
<tr>
<td>{{ $user->name }}</td>
<td class="text-center">{{ $user->posts_count }}</td>
<td class="text-center">{{ $user->comments_count }}</td>
</tr>
@endforeach
También puedes ordernar el campo así:
User::withCount('comments')->orderBy('comments_count', 'desc')->get();
Cuando traes relaciones a los modelos, puedes especificar algunos limitadores u ordenarlos en una función de cierre. Por ejemplo: si quieres obtener todos los Paises con los mayores habitantes aquí está el código:
$countries = Country::with(['cities' => function($query) {
$query->orderBy('population', 'desc');
}])->get();
Tu solo no puedes especificar qué relaciones SIEMPRE traer en tus modelos, sino también hacerlo dinamicamente en el método constructor
class ProductTag extends Model
{
protected $with = ['product'];
public function __construct() {
parent::__construct();
$this->with = ['product'];
if (auth()->check()) {
$this->with[] = 'user';
}
}
}
Cuando usamos la relación belongsTo
, en vez de pasar el id padre cuando creamos registros hijos podemos usar la relación hasMany
para hacer más cortas las lineas de código.
// if Post -> belongsTo(User), and User -> hasMany(Post)...
// Then instead of passing user_id...
Post::create([
'user_id' => auth()->id(),
'title' => request()->input('title'),
'post_text' => request()->input('post_text'),
]);
// Do this
auth()->user()->posts()->create([
'title' => request()->input('title'),
'post_text' => request()->input('post_text'),
]);
Si necesitas renombrar la plabra "pivot" de las relaciones intermedias, especifica en la relación ->as('name')
.
Modelo:
public function podcasts() {
return $this->belongsToMany(Podcast::class)
->as('subscription')
->withTimestamps();
}
Controlador:
$podcasts = $user->podcasts();
foreach ($podcasts as $podcast) {
// instead of $podcast->pivot->created_at ...
echo $podcast->subscription->created_at;
}
Si tienes una relacion belongsTo()
, puedes actualizar el modelo en una sola sentencia.
// if Project -> belongsTo(User::class)
$project->user->update(['email' => 'some@gmail.com']);
Desde Laravel 7 en las migraciones no necesitas escribir dos lineas donde creas la columna y después asignas la tabla foranea, en vez utiliza el método foreignId()
. ``
//Antes de Laravel 7
Schema::table('posts', function (Blueprint $table)) {
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
}
// Desde Laravel 7
Schema::table('posts', function (Blueprint $table)) {
$table->foreignId('user_id')->constrained();
}
// o si tu campo es diferente de la tabla de referencia
Schema::table('posts', function (Blueprint $table)) {
$table->foreignId('created_by_id')->constrained('users', 'column');
}
En Eloquent puedes combinar el método whereHas()
y orDoesntHave()
en una consulta.
User::whereHas('roles', function($query) {
$query->where('id', 1);
})
->orDoesntHave('roles')
->get();
Si en tus relaciones de Eloquent los nombres son dinámicos y necesitas revisar si la relación existe en el objeto, usa la función method_exists($object, $methodName)
de PHP.
$user = User::first();
if (method_exists($user, 'roles')) {
// Do something with $user->roles()->...
}
In las relaciones muchos a muchos, la tabla pivot puede contener campos extras e incluso extra relaciones de otros modelos.
Entonces necesitamos generar por seperado un Modelo que apunte comot abla Pivot.
php artisan make:model RoleUser --pivot
Después, especificamos en la relación belongsToMany()
con el método ->using()
. Entonces así podemos hacer la mágia, como en este ejemplo:
// in app/Models/User.php
public function roles()
{
return $this->belongsToMany(Role::class)
->using(RoleUser::class)
->withPivot(['team_id']);
}
// app/Models/RoleUser.php: notice extends Pivot, not Model
use Illuminate\Database\Eloquent\Relations\Pivot;
class RoleUser extends Pivot
{
public function team()
{
return $this->belongsTo(Team::class);
}
}
// Then, in Controller, you can do:
$firstTeam = auth()->user()->roles()->first()->pivot->team->name;
Además en el método withCount()
Eloquent que sirve para contar los registros de la relación, pero tambíen puedes cargarlo hasta que lo necesites con loadCount()
:
//si tu modelo Book tiene muchos Reviews
$book = Book::first();
$book->loadCount('reviews');
// Entonces obtienes acceso a $book->reviews_count;
// O incluso con una condición extra
$book->loadCount(['reviews' => function ($query) {
$query->where('rating', 5);
}]);
Puedes usar inRandomOrder()
para ordenar de forma aleatoria los resultados, pero también Laravel es capaz de ordenar de forma aleatoria las relaciones.
// SI tienes un examen y quieres hacer aleatoria las preguntas
// 1. Si quieres obtener las preguntas en order aleatorio:
$questions = Question::inRandomOrder()->get();
// 2. Si quieres además obtener las preguntas en orden aleatorio y también sus opciones:
$questions = Question::with(['answers' => function($q) {
$q->inRandomOrder();
}])->inRandomOrder()->get();
Aquí te presento un código de mi proyecto como ejemplo, mostrando la posibilidad de filtrar las relaciones hasMany.
TagTypes -> hasMany Tags -> hasMany Examples
Y si quieres consultar todos los tipos que tengan sus Tags, pero solo esos que tengan Examples, odenandolo por los que contienen más Examples.
$tag_types = TagType::with(['tags' => function ($query) {
$query->has('examples')
->withCount('examples')
->orderBy('examples_count', 'desc');
}])->get();
Si tienes tienes relaciones muchas a muchas, en tabla pivot solo muestra las llaves foraneas ysi necesitas añadir una columna existente solo añde withPivot
.
class Tournament extends Model
{
public function countries()
{
return $this->belongsToMany(Country::class)->withPivot(['position']);
}
}
class TournamentsController extends Controller
{
public function whatever_method() {
$tournaments = Tournament::with(['countries' => function($query) {
$query->orderBy('position');
}])->latest()->get();
}
}
Una manera de escribir whereHas() más corto y con una simple condición apartir de Laravel 8.57.
// Before
User::whereHas('posts', function ($query) {
$query->where('published_at', '>', now());
})->get();
// After
User::whereRelation('posts', 'published_at', '>', now())->get();
class User
{
public function posts()
{
return $this->hasMany(Post::class);
}
// with a getter
public function getPublishedPostsAttribute()
{
return $this->posts->filter(fn ($post) => $post->published);
}
// with a relationship
public function publishedPosts()
{
return $this->hasMany(Post::class)->where('published', true);
}
}
⭐ Aportación de @anwar_nairi
Con la llegada de Laravel 8.63.0 nos trae un método whereBelongsTo()
Eloquent query builder . Esto nos permite eliminar los nombres de las llaves foraneas en nuestras consultas y usar los métodos inertes de las relaciones más limpio.
// From:
$query->where('author_id', $author->id)
// To:
$query->whereBelongsTo($author)
// Easily add more advanced filtering:
Post::query()
->whereBelongsTo($author)
->whereBelongsTo($cateogry)
->whereBelongsTo($section)
->get();
// Specify a custom relationship:
$query->whereBelongsTo($author, 'author')
⭐ Aportación de @danjharrin
Ahora podemos hacer comparaciones entre modelos sin hacer más consultas a base de datos.
// Antes: la llave foranea es tomada del modelo Post
$post->author_id === $user->id;
// Antes: una petición adicional es hecha para obtener el modelo User desde la relación Autor
$post->author->is($user);
// DESPUES
$post->author()->is($user);
⭐ Aportación de @PascalBaljet](https://twitter.com/pascalbaljet)
Con el método whereHas() además de poder realizar consultas más especificas, también podemos especificar qué conexiones.
// User Model
class User extends Model
{
protected $connection = 'conn_1';
public function posts()
{
return $this->hasMany(Post::class);
}
}
// Post Model
class Post extends Model
{
protected $connection = 'conn_2';
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
}
// wherehas()
$posts = Post::whereHas('user', function ($query) use ($request) {
$query->from('db_name_conn_1.users')->where(...);
})->get();
⭐ Aportación de @PascalBaljet
Si necesitas actualizar un registro intermedio en la tabla pivot, usa el método updateExistingPivot
en vez de syncWithPivotValues
.
// Migraciones
Schema::create('role_user', function ($table) {
$table->unsignedId('user_id');
$table->unsignedId('role_id');
$table->timestamp('assigned_at');
})
// Primer parametro para el id del registro y el segundo para actualizar las columnas
$user->roles()->updateExistingPivot(
$id, ['assigned_at' => now()],
);
⭐ Aportación de @sky_0xs
Ahora en Laravel 8.32 en los modelos Eloquent podemos definir una relación que obtenga los ulitmos(los mas viejos) registros.
public function historyItems(): HasMany
{
return $this
->hasMany(ApplicationHealthCheckHistoryItem::class)
->orderByDesc('created_at');
}
public function latestHistoryItem(): HasOne
{
return $this
->hasOne(ApplicationHealthCheckHistoryItem::class)
->latestOfMany();
}
class User extends Authenticable {
// Get most popular post of user
//Obtiene los post con más likes
public function mostPopularPost() {
return $this->hasOne(Post::class)->ofMany('like_count', 'max');
}
}
⭐ Aportación de@LaravelEloquent
$user->posts()
->where('active', 1)
->orWhere('votes', '>=', 100)
->get();
Retorna: todos los post donde los votos son mayores o iguales a 100.
select * from posts where user_id = ? and active = 1 or votes >= 100
use Illuminate\Database\Eloquent\Builder;
$users->posts()
->where(function (Builder $query) {
return $query->where('active', 1)
->orWhere('votes', '>=', 100);
})
->get();
Retorna: todos los post donde los votos son mayores o iguales a 100.
select * from posts where user_id = ? and (active = 1 or votes >= 100)
⭐ Aportación de @BonnickJosh