Laravel Eloquent

Eloquent: Getting Started
Eloquent: Relationships
Eloquent: Collections

Active record pattern
Active Record Basics
Active Record – Object-relational mapping in Rails
ORM, Ruby and ActiveRecord.
Ask HN: Raw SQL vs. ORM?
What is an ORM, how does it work, and how should I use one? [closed]

Generating Model Classes

Generate a new model

1
php artisan make:model Flight

Generate a model and a migration, factory, seeder, and controller

1
php artisan make:model Flight -mfsc

Eloquent Model Conventions

1
2
protected $fillable = ['name', 'email', 'password'];	设置了可以更新的字段。
protected $hidden = ['password', 'remember_token']; 设置了哪些字段会被隐藏。

Table Names

protected $table = 'users';

Primary Keys

protected $primaryKey = 'user_id';

If you wish to use a non-incrementing or a non-numeric primary key you must define a public $incrementing property on your model that is set to false

public $incrementing = false;

If your model’s primary key is not an integer, you should define a protected $keyType property on your model.

protected $keyType = 'string';

Eloquent requires each model to have at least one uniquely identifying “ID” that can serve as its primary key. “Composite” primary keys are not supported by Eloquent models.

Timestamps

Eloquent will automatically set these column’s values when models are created or updated. If you do not want these columns to be automatically managed by Eloquent, you should define a $timestamps property on your model with a value of false

public $timestamps = false;

Database Connections

If you would like to specify a different connection that should be used when interacting with a particular model, you should define a $connection property on the model

protected $connection = 'sqlite';

Default Attribute Values

If you would like to define the default values for some of your model’s attributes, you may define an $attributes property on your model

1
2
3
protected $attributes = [
'delayed' => false,
];

Retrieving Models

1
2
3
4
5
6
7
8
9
10
11
12
13
foreach (Flight::all() as $flight) {
echo $flight->name;
}

// Building Queries
$flights = Flight::where('active', 1)
->orderBy('name')
->take(10)
->get();

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

Chunking Results

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

Streaming Results Lazily

The lazy method works similarly to the chunk method in the sense that, behind the scenes, it executes the query in chunks. However, instead of passing each chunk directly into a callback as is, the lazy method returns a flattened LazyCollection of Eloquent models, which lets you interact with the results as a single stream:

1
2
3
4
5
6
7
foreach (Flight::lazy() as $flight) {
//
}

Flight::where('departed', true)
->lazyById(200, $column = 'id')
->each->update(['departed' => false]);

Cursors

Similar to the lazy method, the cursor method may be used to significantly reduce your application’s memory consumption when iterating through tens of thousands of Eloquent model records.

The cursor method will only execute a single database query; however, the individual Eloquent models will not be hydrated until they are actually iterated over. Therefore, only one Eloquent model is kept in memory at any given time while iterating over the cursor.

1
2
3
foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) {
//
}

Since the cursor method only ever holds a single Eloquent model in memory at a time, it cannot eager load relationships. If you need to eager load relationships, consider using the lazy method instead.

Although the cursor method uses far less memory than a regular query (by only holding a single Eloquent model in memory at a time), it will still eventually run out of memory. This is due to PHP’s PDO driver internally caching all raw query results in its buffer. If you’re dealing with a very large number of Eloquent records, consider using the lazy method instead.

Advanced Subqueries

1
2
3
4
5
return Destination::addSelect(['last_flight' => Flight::select('name')
->whereColumn('destination_id', 'destinations.id')
->orderByDesc('arrived_at')
->limit(1)
])->get();

Retrieving Single Models / Aggregates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Retrieve a model by its primary key...
$flight = Flight::find(1);

// Retrieve the first model matching the query constraints...
$flight = Flight::where('active', 1)->first();

// Alternative to retrieving the first model matching the query constraints...
$flight = Flight::firstWhere('active', 1);

// Sometimes you may wish to retrieve the first result of a query or perform some other action if no results are found.
$model = Flight::where('legs', '>', 3)->firstOr(function () {
// ...
});

// Sometimes you may wish to throw an exception if a model is not found. This is particularly useful in routes or controllers.
$flight = Flight::findOrFail(1);
$flight = Flight::where('legs', '>', 3)->firstOrFail();

Retrieving Or Creating Models

The firstOrCreate method will attempt to locate a database record using the given column / value pairs. If the model can not be found in the database, a record will be inserted with the attributes resulting from merging the first array argument with the optional second array argument

The firstOrNew method, like firstOrCreate, will attempt to locate a record in the database matching the given attributes. However, if a model is not found, a new model instance will be returned. Note that the model returned by firstOrNew has not yet been persisted to the database. You will need to manually call the save method to persist it

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Retrieve flight by name or create it if it doesn't exist...
$flight = Flight::firstOrCreate([
'name' => 'London to Paris'
]);

// Retrieve flight by name or create it with the name, delayed, and arrival_time attributes...
$flight = Flight::firstOrCreate(
['name' => 'London to Paris'],
['delayed' => 1, 'arrival_time' => '11:30']
);

// Retrieve flight by name or instantiate a new Flight instance...
$flight = Flight::firstOrNew([
'name' => 'London to Paris'
]);

// Retrieve flight by name or instantiate with the name, delayed, and arrival_time attributes...
$flight = Flight::firstOrNew(
['name' => 'Tokyo to Sydney'],
['delayed' => 1, 'arrival_time' => '11:30']
);

Retrieving Aggregates

these methods return a scalar value instead of an Eloquent model instance

1
2
$count = Flight::where('active', 1)->count();
$max = Flight::where('active', 1)->max('price');

Inserting & Updating Models

Inserts

1
2
3
4
5
6
7
8
$flight = new Flight;
$flight->name = $request->name;
$flight->save();

// Alternatively, you may use the create method to "save" a new model using a single PHP statement.
$flight = Flight::create([
'name' => 'London to Paris',
]);

Updates

The save method may also be used to update models that already exist in the database.

1
2
3
$flight = Flight::find(1);
$flight->name = 'Paris to London';
$flight->save();

Mass Updates

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

When issuing a mass update via Eloquent, the saving, saved, updating, and updated model events will not be fired for the updated models. This is because the models are never actually retrieved when issuing a mass update.

Examining Attribute Changes

The isDirty method determines if any of the model’s attributes have been changed since the model was retrieved. You may pass a specific attribute name to the isDirty method to determine if a particular attribute is dirty. The isClean will determine if an attribute has remained unchanged since the model was retrieved.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$user = User::create([
'first_name' => 'Taylor',
'last_name' => 'Otwell',
'title' => 'Developer',
]);

$user->title = 'Painter';

$user->isDirty(); // true
$user->isDirty('title'); // true
$user->isDirty('first_name'); // false

$user->isClean(); // false
$user->isClean('title'); // false
$user->isClean('first_name'); // true

$user->save();

$user->isDirty(); // false
$user->isClean(); // true

The wasChanged method determines if any attributes were changed when the model was last saved within the current request cycle.

1
2
3
4
5
6
7
8
9
10
11
12
13
$user = User::create([
'first_name' => 'Taylor',
'last_name' => 'Otwell',
'title' => 'Developer',
]);

$user->title = 'Painter';

$user->save();

$user->wasChanged(); // true
$user->wasChanged('title'); // true
$user->wasChanged('first_name'); // false

The getOriginal method returns an array containing the original attributes of the model regardless of any changes to the model since it was retrieved. If needed, you may pass a specific attribute name to get the original value of a particular attribute:

1
2
3
4
5
6
7
8
9
10
$user = User::find(1);

$user->name; // John
$user->email; // john@example.com

$user->name = "Jack";
$user->name; // Jack

$user->getOriginal('name'); // John
$user->getOriginal(); // Array of original attributes...

Mass Assignment

Before using the create method, you will need to specify either a fillable or guarded property on your model class. These properties are required because all Eloquent models are protected against mass assignment vulnerabilities by default.

You may do this using the $fillable property on the model.

1
2
3
4
5
6
7
protected $fillable = ['name'];

// If you already have a model instance, you may use the fill method to populate it with an array of attributes
$flight->fill(['name' => 'Amsterdam to Frankfurt']);

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

Upserts

Occasionally, you may need to update an existing model or create a new model if no matching model exists. Like the firstOrCreate method, the updateOrCreate method persists the model

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

If you would like to perform multiple “upserts” in a single query, then you should use the upsert method instead. The method’s first argument consists of the values to insert or update, while the second argument lists the column(s) that uniquely identify records within the associated table. The method’s third and final argument is an array of the columns that should be updated if a matching record already exists in the database.

1
2
3
4
Flight::upsert([
['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], ['departure', 'destination'], ['price']);

Deleting Models

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$flight = Flight::find(1);
$flight->delete();

// Deleting Models Using Queries, Like mass updates, mass deletes will not dispatch model events for the models that are deleted:
$deletedRows = Flight::where('active', 0)->delete();

// You may call the truncate method to delete all of the model's associated database records. The truncate operation will also reset any auto-incrementing IDs on the model's associated table
Flight::truncate();

// if you know the primary key of the model, you may delete the model without explicitly retrieving it by calling the destroy method
Flight::destroy(1);

Flight::destroy(1, 2, 3);

Flight::destroy([1, 2, 3]);

Flight::destroy(collect([1, 2, 3]));

The destroy method loads each model individually and calls the delete method so that the deleting and deleted events are properly dispatched for each model.

Soft Deleting

To enable soft deletes for a model, add the Illuminate\Database\Eloquent\SoftDeletes trait to the model, a deleted_at attribute is set on the model indicating the date and time at which the model was “deleted”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use SoftDeletes;

// The Laravel schema builder contains a helper method to create this column
Schema::table('flights', function (Blueprint $table) {
$table->softDeletes();
});

Schema::table('flights', function (Blueprint $table) {
$table->dropSoftDeletes();
});

// To determine if a given model instance has been soft deleted, you may use the trashed method
if ($flight->trashed()) {
//
}

// Restoring Soft Deleted Models
$flight->restore();

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

// The restore method may also be used when building relationship queries
$flight->history()->restore();

Permanently Deleting Models

1
2
3
$flight->forceDelete();
// You may also use the forceDelete method when building Eloquent relationship queries:
$flight->history()->forceDelete();

Querying Soft Deleted Models

1
2
3
4
5
6
7
8
9
10
11
$flights = Flight::withTrashed()
->where('account_id', 1)
->get();

// The withTrashed method may also be called when building a relationship query
$flight->history()->withTrashed()->get();

// The onlyTrashed method will retrieve only soft deleted models
$flights = Flight::onlyTrashed()
->where('airline_id', 1)
->get();

Replicating Models

1
2
3
4
5
6
7
8
9
10
11
12
13
$shipping = Address::create([
'type' => 'shipping',
'line_1' => '123 Example Street',
'city' => 'Victorville',
'state' => 'CA',
'postcode' => '90001',
]);

$billing = $shipping->replicate()->fill([
'type' => 'billing'
]);

$billing->save();

Query Scopes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Writing Global Scopes
class AncientScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*/
public function apply(Builder $builder, Model $model)
{
$builder->where('created_at', '<', now()->subYears(2000));
}
}

// Applying Global Scopes
class User extends Model
{
/**
* The "booted" method of the model.
*/
protected static function booted()
{
static::addGlobalScope(new AncientScope);
}
}

// Anonymous Global Scopes
class User extends Model
{
/**
* The "booted" method of the model.
*/
protected static function booted()
{
static::addGlobalScope('ancient', function (Builder $builder) {
$builder->where('created_at', '<', now()->subYears(2000));
});
}
}

// Removing Global Scopes
User::withoutGlobalScope(AncientScope::class)->get();

// if you defined the global scope using a closure
User::withoutGlobalScope('ancient')->get();

// Remove all of the global scopes...
User::withoutGlobalScopes()->get();

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

Local Scopes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class User extends Model
{
/**
* Scope a query to only include popular users.
*/
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}

/**
* Scope a query to only include active users.
*/
public function scopeActive($query)
{
return $query->where('active', 1);
}
}

// Utilizing A Local Scope
$users = User::popular()->active()->orderBy('created_at')->get();

$users = User::popular()->orWhere(function (Builder $query) {
$query->active();
})->get();

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

// Dynamic Scopes
class User extends Model
{
/**
* Scope a query to only include users of a given type.
*/
public function scopeOfType($query, $type)
{
return $query->where('type', $type);
}
}

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

Comparing Models

The is and isNot methods may be used to quickly verify two models have the same primary key, table, and database connection or not

1
2
3
4
5
6
7
if ($post->is($anotherPost)) {
//
}

if ($post->isNot($anotherPost)) {
//
}

The is and isNot methods are also available when using the belongsTo, hasOne, morphTo, and morphOne relationships.

Events

Eloquent models dispatch several events, allowing you to hook into the following moments in a model’s lifecycle: retrieved, creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored, and replicating.

To start listening to model events, define a $dispatchesEvents property on your Eloquent model.

1
2
3
4
5
6
7
8
9
10
11
12
class User extends Authenticatable
{
use Notifiable;

/**
* The event map for the model.
*/
protected $dispatchesEvents = [
'saved' => UserSaved::class,
'deleted' => UserDeleted::class,
];
}

Using Closures

1
2
3
4
5
6
7
8
9
10
11
12
class User extends Model
{
/**
* The "booted" method of the model.
*/
protected static function booted()
{
static::created(function ($user) {
//
});
}
}

Observers

1
2
// create a new observer class
php artisan make:observer UserObserver --model=User

To register an observer, you need to call the observe method on the model you wish to observe. You may register observers in the boot method of your application’s App\Providers\EventServiceProvider service provider:

1
2
3
4
public function boot()
{
User::observe(UserObserver::class);
}

Muting Events

1
2
3
4
5
6
7
8
9
10
$user = User::withoutEvents(function () use () {
User::findOrFail(1)->delete();

return User::find(2);
});

// Saving A Single Model Without Events
$user = User::findOrFail(1);
$user->name = 'Victoria Faith';
$user->saveQuietly();

One To One
One To Many
Many To Many
Has One Through
Has Many Through
One To One (Polymorphic)
One To Many (Polymorphic)
Many To Many (Polymorphic)

Null object pattern

One To One

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User extends Model
{
/**
* Get the phone associated with the user.
*/
public function phone()
{
return $this->hasOne(Phone::class);
// return $this->hasOne(Phone::class, 'foreign_key');
// return $this->hasOne(Phone::class, 'foreign_key', 'local_key');
}
}

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

Defining The Inverse Of The Relationship

1
2
3
4
5
6
7
8
9
10
11
12
class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo(User::class);
// return $this->belongsTo(User::class, 'foreign_key');
// return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
}
}

One To Many

A one-to-many relationship is used to define relationships where a single model is the parent to one or more child models.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Post extends Model
{
/**
* Get the comments for the blog post.
*/
public function comments()
{
return $this->hasMany(Comment::class);
// return $this->hasMany(Comment::class, 'foreign_key');
// return $this->hasMany(Comment::class, 'foreign_key', 'local_key');
}
}

$comments = Post::find(1)->comments;

foreach ($comments as $comment) {
//
}

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

One To Many (Inverse) / Belongs To

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Comment extends Model
{
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo(Post::class);
// return $this->belongsTo(Post::class, 'foreign_key');
// return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');
}
}

$comment = Comment::find(1);
return $comment->post->title;

Default Models

The belongsTo, hasOne, hasOneThrough, and morphOne relationships allow you to define a default model that will be returned if the given relationship is null. This pattern is often referred to as the Null Object pattern and can help remove conditional checks in your code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* Get the author of the post.
*/
public function user()
{
return $this->belongsTo(User::class)->withDefault();
}

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

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

Has One Of Many

Sometimes a model may have many related models, yet you want to easily retrieve the “latest” or “oldest” related model of the relationship. For example, a User model may be related to many Order models, but you want to define a convenient way to interact with the most recent order the user has placed. You may accomplish this using the hasOne relationship type combined with the ofMany methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Get the user's most recent order.
*/
public function latestOrder()
{
return $this->hasOne(Order::class)->latestOfMany();
}

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

/**
* The ofMany method accepts the sortable column as its first argument and which aggregate function (min or max) to apply when querying for the related model
* Get the user's largest order.
*/
public function largestOrder()
{
return $this->hasOne(Order::class)->ofMany('price', 'max');
}

Advanced Has One Of Many Relationships

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Get the current pricing for the product.
*/
public function currentPricing()
{
return $this->hasOne(Price::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function ($query) {
$query->where('published_at', '<', now());
});
}

Has One Through

1
2
3
4
5
6
7
8
9
10
class Mechanic extends Model
{
/**
* Get the car's owner.
*/
public function carOwner()
{
return $this->hasOneThrough(Owner::class, Car::class);
}
}

The first argument passed to the hasOneThrough method is the name of the final model we wish to access, while the second argument is the name of the intermediate model.

Key Conventions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Mechanic extends Model
{
/**
* Get the car's owner.
*/
public function carOwner()
{
return $this->hasOneThrough(
Owner::class,
Car::class,
'mechanic_id', // Foreign key on the cars table...
'car_id', // Foreign key on the owners table...
'id', // Local key on the mechanics table...
'id' // Local key on the cars table...
);
}
}

Has Many Through

1
2
3
4
5
6
7
8
9
10
class Project extends Model
{
/**
* Get all of the deployments for the project.
*/
public function deployments()
{
return $this->hasManyThrough(Deployment::class, Environment::class);
}
}

The first argument passed to the hasManyThrough method is the name of the final model we wish to access, while the second argument is the name of the intermediate model.

Key Conventions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Project extends Model
{
public function deployments()
{
return $this->hasManyThrough(
Deployment::class,
Environment::class,
'project_id', // Foreign key on the environments table...
'environment_id', // Foreign key on the deployments table...
'id', // Local key on the projects table...
'id' // Local key on the environments table...
);
}
}

Many To Many Relationships

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class User extends Model
{
/**
* The roles that belong to the user.
*/
public function roles()
{
return $this->belongsToMany(Role::class);
// return $this->belongsToMany(Role::class, 'role_user');
// return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
}
}

$user = User::find(1);
foreach ($user->roles as $role) {
//
}
$roles = User::find(1)->roles()->orderBy('name')->get();

Defining The Inverse Of The Relationship

the relationship is defined exactly the same as its User model

Retrieving Intermediate Table Columns

we may access the intermediate table using the pivot attribute on the models:

1
2
3
4
5
$user = User::find(1);

foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}

Notice that each Role model we retrieve is automatically assigned a pivot attribute. This attribute contains a model representing the intermediate table.

By default, only the model keys will be present on the pivot model. If your intermediate table contains extra attributes, you must specify them when defining the relationship:

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

If you would like your intermediate table to have created_at and updated_at timestamps that are automatically maintained by Eloquent

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

Customizing The pivot Attribute Name

1
2
3
4
5
6
7
8
9
return $this->belongsToMany(Podcast::class)
->as('subscription')
->withTimestamps();

$users = User::with('podcasts')->get();

foreach ($users->flatMap->podcasts as $podcast) {
echo $podcast->subscription->created_at;
}

Filtering Queries Via Intermediate Table Columns

You can also filter the results returned by belongsToMany relationship queries using the wherePivot, wherePivotIn, wherePivotNotIn, wherePivotBetween, wherePivotNotBetween, wherePivotNull, and wherePivotNotNull methods when defining the relationship

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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');

Defining Custom Intermediate Table Models

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Role extends Model
{
/**
* The users that belong to the role.
*/
public function users()
{
return $this->belongsToMany(User::class)->using(RoleUser::class);
}
}

class RoleUser extends Pivot
{

// Custom Pivot Models And Incrementing IDs
public $incrementing = true;
}

Pivot models may not use the SoftDeletes trait. If you need to soft delete pivot records consider converting your pivot model to an actual Eloquent model.

Polymorphic Relationships

A polymorphic relationship allows the child model to belong to more than one type of model using a single association. For example, imagine you are building an application that allows users to share blog posts and videos. In such an application, a Comment model might belong to both the Post and Video models.

One To One (Polymorphic)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Image extends Model
{
/**
* Get the parent imageable model (user or post).
*/
public function imageable()
{
return $this->morphTo();
// return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id');
}
}

class Post extends Model
{
/**
* Get the post's image.
*/
public function image()
{
return $this->morphOne(Image::class, 'imageable');
}
}

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

$post = Post::find(1);
$image = $post->image;

$image = Image::find(1);
$imageable = $image->imageable;

One To Many (Polymorphic)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Comment extends Model
{
/**
* Get the parent commentable model (post or video).
*/
public function commentable()
{
return $this->morphTo();
}
}

class Post extends Model
{
/**
* Get all of the post's comments.
*/
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}

class Video extends Model
{
/**
* Get all of the video's comments.
*/
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}

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

$comment = Comment::find(1);
$commentable = $comment->commentable;

One Of Many (Polymorphic)

1
2
3
4
5
6
7
8
9
10
public function latestImage()
{
return $this->morphOne(Image::class)->latestOfMany();
}

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

Many To Many (Polymorphic)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Post extends Model
{
/**
* Get all of the tags for the post.
*/
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}

class Tag extends Model
{
/**
* Get all of the posts that are assigned this tag.
*/
public function posts()
{
return $this->morphedByMany(Post::class, 'taggable');
}

/**
* Get all of the videos that are assigned this tag.
*/
public function videos()
{
return $this->morphedByMany(Video::class, 'taggable');
}
}

$post = Post::find(1);
foreach ($post->tags as $tag) {
//
}
$tag = Tag::find(1);
foreach ($tag->posts as $post) {
//
}
foreach ($tag->videos as $video) {
//
}

Custom Polymorphic Types

1
2
3
4
5
6
7
Relation::morphMap([
'post' => 'App\Models\Post',
'video' => 'App\Models\Video',
]);

$alias = $post->getMorphClass();
$class = Relation::getMorphedModel($alias);

Dynamic Relationships

1
2
3
Order::resolveRelationUsing('customer', function ($orderModel) {
return $orderModel->belongsTo(Customer::class, 'customer_id');
});

Querying Relations

Imagine a blog application in which a User model has many associated Post models

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class User extends Model
{
/**
* Get all of the posts for the user.
*/
public function posts()
{
return $this->hasMany(Post::class);
}
}

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

// Chaining orWhere Clauses After Relationships
$user->posts()
->where(function (Builder $query) {
return $query->where('active', 1)
->orWhere('votes', '>=', 100);
})
->get();

Relationship Methods Vs. Dynamic Properties

Dynamic relationship properties perform “lazy loading”, meaning they will only load their relationship data when you actually access them. Because of this, developers often use eager loading to pre-load relationships they know will be accessed after loading the model. Eager loading provides a significant reduction in SQL queries that must be executed to load a model’s relations.

Querying Relationship Existence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Retrieve all posts that have at least one comment...
$posts = Post::has('comments')->get();

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

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

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

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

Querying Relationship Absence

1
2
3
4
5
6
7
8
9
$posts = Post::doesntHave('comments')->get();

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

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

Querying Morph To Relationships

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Retrieve comments associated to posts or videos with a title like code%...
$comments = Comment::whereHasMorph(
'commentable',
[Post::class, Video::class],
function (Builder $query) {
$query->where('title', 'like', 'code%');
}
)->get();

// Retrieve comments associated to posts with a title not like code%...
$comments = Comment::whereDoesntHaveMorph(
'commentable',
Post::class,
function (Builder $query) {
$query->where('title', 'like', 'code%');
}
)->get();

$comments = Comment::whereHasMorph(
'commentable',
[Post::class, Video::class],
function (Builder $query, $type) {
$column = $type === Post::class ? 'content' : 'title';

$query->where($column, 'like', 'code%');
}
)->get();

Querying All Related Models

1
2
3
$comments = Comment::whereHasMorph('commentable', '*', function (Builder $query) {
$query->where('title', 'like', 'foo%');
})->get();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$posts = Post::withCount('comments')->get();

foreach ($posts as $post) {
echo $post->comments_count;
}

$posts = Post::withCount(['votes', 'comments' => function (Builder $query) {
$query->where('content', 'like', 'code%');
}])->get();

echo $posts[0]->votes_count;
echo $posts[0]->comments_count;

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

Deferred Count Loading

Using the loadCount method, you may load a relationship count after the parent model has already been retrieved

1
2
3
4
5
6
$book = Book::first();
$book->loadCount('genres');

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

Relationship Counting & Custom Select Statements

If you’re combining withCount with a select statement, ensure that you call withCount after the select method

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

Other Aggregate Functions

In addition to the withCount method, Eloquent provides withMin, withMax, withAvg, withSum, and withExists methods. These methods will place a {relation}_{function}_{column} attribute on your resulting models

1
2
3
4
5
6
7
8
9
$posts = Post::withSum('comments', 'votes')->get();

foreach ($posts as $post) {
echo $post->comments_sum_votes;
}

// deferred versions of these methods
$post = Post::first();
$post->loadSum('comments', 'votes');

Counting Related Models On Morph To Relationships

let’s assume that Photo and Post models may create ActivityFeed models. We will assume the ActivityFeed model defines a “morph to” relationship named parentable that allows us to retrieve the parent Photo or Post model for a given ActivityFeed instance. Additionally, let’s assume that Photo models “have many” Tag models and Post models “have many” Comment models.

let’s imagine we want to retrieve ActivityFeed instances and eager load the parentable parent models for each ActivityFeed instance. In addition, we want to retrieve the number of tags that are associated with each parent photo and the number of comments that are associated with each parent post

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$activities = ActivityFeed::with([
'parentable' => function (MorphTo $morphTo) {
$morphTo->morphWithCount([
Photo::class => ['tags'],
Post::class => ['comments'],
]);
}])->get();

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

Eager Loading

1
2
3
4
5
6
7
8
9
$comment = new Comment(['message' => 'A new comment.']);
$post = Post::find(1);
$post->comments()->save($comment);

// If you need to save multiple related models, you may use the saveMany method
$post->comments()->saveMany([
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another new comment.']),
]);

If you plan on accessing the relationship after using the save or saveMany methods, you may wish to use the refresh method to reload the model and its relationships

1
2
3
4
5
$post->comments()->save($comment);
$post->refresh();

// All comments, including the newly saved comment...
$post->comments;

Recursively Saving Models & Relationships

If you would like to save your model and all of its associated relationships, you may use the push method. In this example, the Post model will be saved as well as its comments and the comment’s authors

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

The create Method

1
2
3
4
5
6
7
8
9
$post = Post::find(1);
$comment = $post->comments()->create([
'message' => 'A new comment.',
]);

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

You may also use the findOrNew, firstOrNew, firstOrCreate, and updateOrCreate methods to create and update models on relationships.

Belongs To Relationships

1
2
3
4
5
6
7
$account = Account::find(10);
$user->account()->associate($account);
$user->save();

// To remove a parent model from a child model, you may use the dissociate method. This method will set the relationship's foreign key to null
$user->account()->dissociate();
$user->save();

Many To Many Relationships

let’s imagine a user can have many roles and a role can have many users. You may use the attach method to attach a role to a user by inserting a record in the relationship’s intermediate table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$user = User::find(1);
$user->roles()->attach($roleId);

// When attaching a relationship to a model, you may also pass an array of additional data to be inserted into the intermediate table
$user->roles()->attach($roleId, ['expires' => $expires]);

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

// Detach all roles from the user...
$user->roles()->detach();

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

You may also use the sync method to construct many-to-many associations. The sync method accepts an array of IDs to place on the intermediate table. Any IDs that are not in the given array will be removed from the intermediate table.

1
2
3
4
5
6
7
8
9
10
$user->roles()->sync([1, 2, 3]);

// You may also pass additional intermediate table values with the IDs
$user->roles()->sync([1 => ['expires' => true], 2, 3]);

// If you would like to insert the same intermediate table values with each of the synced model IDs, you may use the syncWithPivotValues method
$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);

// If you do not want to detach existing IDs that are missing from the given array, you may use the syncWithoutDetaching method:
$user->roles()->syncWithoutDetaching([1, 2, 3]);

The many-to-many relationship also provides a toggle method which “toggles” the attachment status of the given related model IDs. If the given ID is currently attached, it will be detached. Likewise, if it is currently detached, it will be attached

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

If you need to update an existing row in your relationship’s intermediate table, you may use the updateExistingPivot method. This method accepts the intermediate record foreign key and an array of attributes to update

1
2
3
$user->roles()->updateExistingPivot($roleId, [
'active' => false,
]);

Touching Parent Timestamps

When a model defines a belongsTo or belongsToMany relationship to another model, such as a Comment which belongs to a Post, it is sometimes helpful to update the parent’s timestamp when the child model is updated.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Comment extends Model
{
/**
* All of the relationships to be touched.
*/
protected $touches = ['post'];

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