hacking laravel - custom relationships with eloquent
Post on 22-Jan-2018
254 Views
Preview:
TRANSCRIPT
Hacking LaravelCustom relationships with Eloquent
Alex Weissmanhttps://chat.userfrosting.com
@userfrosting
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
Ternary Relationships
worker job location title
Alice soldier Hatchery Grunt
Alice soldier BroodChamber
Guard
Alice attendant BroodChamber
Feeder
David attendant BroodChamber
Midwife
David attendant Pantry Inspector
[
{
‘name’: ‘Alice’,
‘jobs’: [
{‘name’: ‘soldier’,
‘locations’: [‘Hatchery’,
‘Brood Chamber’
]
},
{‘name’: ‘attendant’,
‘locations’: [‘Brood Chamber’
]
}
]
},
{
‘name’: ‘David’,
‘jobs’: [
{‘name’: ‘attendant’,
‘locations’: [‘Brood Chamber’,
‘Pantry’
]
}
]
}
]
Ternary Relationships
• Why can’t we just model this as two m:m relationships instead?
• What happens if we try to use a BelongsToManyrelationship 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();
Using Two BelongsToMany
// $worker->jobs()->get();
{
'name': 'soldier'
},
{
'name': 'soldier'
},
{
'name': 'attendant'
}
Using Two BelongsToMany
// $worker->load(jobs.locations)->get();
{
'name': 'soldier',
'locations': {
'Hatchery',
'Brood Chamber'
}
},
{
'name': 'soldier',
'locations': {
'Hatchery',
'Brood Chamber'
}
},
{
'name': 'attendant',
'locations': {
'Brood Chamber',
'Brood Chamber',
'Pantry'
}
}
Using BelongsToTernary
// $worker->jobs()->withTertiary(‘locations’)->get();
{
'name': 'soldier',
'locations': {
'Hatchery',
'Brood Chamber'
}
},
{
'name': 'attendant',
'locations': {
'Brood Chamber’
}
}
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
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 Eloquent\Builder!
Need to override this!
Don’t need to override this.
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();
Retrieving a relation on a single model, many-to-manyStack 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
Eloquent\Builder::getModels
BelongsToMany::get
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();
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!
Retrieving a relation on a collection, many-to-manyStack trace time!
BelongsToMany::performJoin
BelongsToMany::addConstraints
Relation::__construct
BelongsToMany::__construct
Model::belongsToMany
Constructing the query
Assembling the Collection
Relation::getEager
BelongsToMany::match
Eloquent\Builder::eagerLoadRelation
Eloquent\Builder::eagerLoadRelations
Eloquent\Builder::get
$workers = Worker::with(‘jobs’)->get();
match
Alice
David
$models
(from main Eloquent\Builder)
row1
$results
(from the joined query in BelongsToMany)
row2
row3
row4
row5
buildDictionary
Alice
David
$models
(from main Eloquent\Builder)
row1
$results
(from the joined query in BelongsToMany)
row2
row3
row4
row5
Task 1
Implement BelongsToTernary::condenseModels, which collapses these rows into a single model. For now, don't worry about extracting the tertiary models (locations) for the sub-relationship.
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.
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.
Try this at home
BelongsToManyThrough
$user->permissions()->get();
User m:m Role m:m Permission
Full implementations in https://github.com/userfrosting/UserFrosting
top related