Eloquent, Laravel’s ORM, is a powerful tool for interacting with relational databases. It models entity relationships such as one-to-many and many-to-many, but production applications often need to produce very specific data structures that cannot be directly generated from Eloquent built-in methods. Instead of looping through collections to wrangle your query results, it is possible to extend Laravel’s basic relation classes and implement custom relations that you can easily reuse within and among Laravel projects. You will dive into the guts of Eloquent and emerge an effective power user.
2. Basic Relationships
student subject
Alice Freestyling
Alice Beatboxing
David Beatboxing
David Turntabling
name team
Alice London
David Liverpool
Abdullah London
person partner
Alice David
Abdullah Louis
Model::hasOne
Model::hasMany
Model::belongsToMany
5. Ternary Relationships
• Why can’t we just model this as two m:m relationships
instead?
• What happens if we try to use a BelongsToMany
relationship on a ternary pivot table?
public function jobs()
{
$this->belongsToMany(EloquentTestJob::class, ’assignments',
'worker_id', 'job_id');
}
…
$worker->jobs()->get();
$worker->load(jobs.locations)->get();
6. Using Two BelongsToMany
// $worker->jobs()->get();
{
'name': 'soldier'
},
{
'name': 'soldier'
},
{
'name': 'attendant'
}
9. Goals
• Understand Eloquent’s Model and query builder classes
• Understand how Eloquent implements database relationships
• Understand how Eloquent solves the N+1 problem
• Implement a basic BelongsToTernary relationship
• Implement eager loading for BelongsToTernary
• Implement loading of the tertiary models as a nested
collection
https://github.com/alexweissman/phpworld2017
11. Retrieving a relation on a single model
$user = User::find(1);
$roles = $user->roles()->get();
$users = User::where(‘active’, ‘1’)
->with(‘roles’)
->get();
Retrieving a relation on a collection of models (eager load)
$users = User::where(‘active’, ‘1’)->get();
$users->load(‘roles’);
get() is a method of Relation!
get() is a method of EloquentBuilder!
Need to override this!
Don’t need to
override this.
12. Retrieving a relation on a single model
select * from `jobs`
inner join `job_workers`
on `job_workers`.`job_id` = `jobs`.`id`
and `job_workers`.`worker_id` = 1
many-to-many
$user = User::find(1);
$emails = $user->emails()->get();
select * from `emails`
where `user_id` = 1
one-to-many
$worker = Worker::find(1);
$jobs = $worker->jobs()->get();
13. Retrieving a relation on a single model, many-to-many
Stack trace time!
$worker = Worker::find(1);
$jobs = $worker->jobs()->get();
BelongsToMany::performJoin
BelongsToMany::addConstraints
Relation::__construct
BelongsToMany::__construct
Model::belongsToMany
Constructing the query
Assembling the Collection
EloquentBuilder::getModels
BelongsToMany::get
14. Retrieving a relation on a collection, many-to-many
select * from `workers`;
select * from `jobs`
inner join `job_workers`
on `job_workers`.`job_id` = `jobs`.`id`
and `job_workers`.`worker_id` in (1,2);
many-to-many
$users = User::with(‘emails’)->get();
select * from `users`;
select * from `emails` where `user_id` in (1,2);
one-to-many
$workers = Worker::with(‘jobs’)->get();
15. Retrieving a relation on a collection, many-to-many
select * from `workers`;
select * from `jobs`
inner join `job_workers`
on `job_workers`.`job_id` = `jobs`.`id`
and `job_workers`.`worker_id` in (1,2);
many-to-many
$users = User::with(‘emails’)->get();
select * from `users`;
select * from `emails` where `user_id` in (1,2);
one-to-many
$workers = Worker::with(‘jobs’)->get();
solves the n+1
problem!
16. Retrieving a relation on a collection, many-to-many
Stack trace time!
BelongsToMany::performJoin
BelongsToMany::addConstraints
Relation::__construct
BelongsToMany::__construct
Model::belongsToMany
Constructing the query
Assembling the Collection
Relation::getEager
BelongsToMany::match
EloquentBuilder::eagerLoadRelation
EloquentBuilder::eagerLoadRelations
EloquentBuilder::get
$workers = Worker::with(‘jobs’)->get();
20. Task 2
Modify BelongsToTernary::match, which is
responsible for matching eager-loaded models
to their parents.
Again, we have provided you with the default
implementation from BelongsToMany::match,
but you must modify it to collapse rows with the
same worker_id and job_id (for example) into a
single child model.
21. Task 3
By default, BelongsToTernary::buildDictionary returns a dictionary that maps parent models to their
children. Modify it so that it also returns a nestedDictionary, which maps parent->child->tertiary
models.
For example:
[
// Worker 1
'1' => [
// Job 3
'3' => [
Location1,
Location2
],
...
],
...
]
You will also need to further modify condenseModels to retrieve the tertiary dictionary and call
matchTertiaryModels to match the tertiary models with each of the child models, if withTertiary is being
used.
22. Try this at home
BelongsToManyThrough
$user->permissions()->get();
User m:m Role m:m Permission
Full implementations in https://github.com/userfrosting/UserFrosting