I have a seemingly stupid question here but I can’t find how one is supposed to define explicit model binding resolution logic using resolveRouteBinding
method on a Model.
I managed to write down its logic which is based on the value of the field
parameter but I can’t seem to work out how to pass a value to this parameter when I define the route.
Can anyone please help me out?
EDIT: The proper question is how to tell the framework what value to pass to the field
parameter?
EDIT 2: Here’s an example of what I’m doing without luck.
Basically I want to implement a custom logic to retrieve a class representing a many-to-many relationship.
These are the master classes.
class Competition extends Model { // ... /** * Get the editions of the competition. */ public function competitionSeasons() { return $this->hasMany(CompetitionSeason::class); } }
class Season extends Model { // ... /** * Get the editions of the competitions in this season */ public function competitionsSeasons() { return $this->hasMany(CompetitionSeason::class); } }
This is the relationship class.
class CompetitionSeason extends Model { // ... /** * Get the competition of this competition edition. */ public function competition() { return $this->belongsTo(Competition::class); } /** * Get the season of this competition edition. */ public function season() { return $this->belongsTo(Season::class); } public function resolveRouteBinding($value, $field = null) { error_log("[resolveRouteBinding] Field: " . $field); // TODO: make the query } public function resolveChildRouteBinding($childType, $value, $field = null) { error_log("[resolveChildRouteBinding] Field: " . $field); // TODO: make the query } }
What I want now is to have a CompetitionSeason injected in the controller as a nested resource under Season (and also under Competition later on).
So this is how the route has been defined.
// api.php Route::apiResource('seasons.competitionSeasons', MockController::class)->scoped([ 'competitionSeason' => 'competition' ]);
Since I’m using slugs on the routes rather than IDs, competition
here would be needed in any of the resolveBinding
methods to navigate the relationship and get the slug from the Competition
instance.
Basically, the request would be like this:
GET .../api/seasons/19-20/competitionSeasons/premier-league
where premier-league
is the slug of the referenced Competition
, the other end of the many-to-many relationship basically.
This is the Controller.. completely useless for now.
class MockController extends Controller { public function show(Season $season, CompetitionSeason $competitionSeason) { error_log($competitionSeason); } }
I know how to implment the missing logic, I have it very clear, what I would like to see is the $field
being printed when the framework attempts to resolve the required instance of CompetitionSeason
so that I can get the slug of the associated resource as well as to know which relationship to navigate when CompetitionSeason
will be a child resource of Competition
too.
Advertisement
Answer
I find out how it is supposed to work even though I don’t know why it’s like this.. (some considerations below).
Basically the public function resolveChildRouteBinding
I should override is the parent, so Season
in this case.
So now Season
looks like this:
class Season extends Model { // ... public function competitionsSeasons() { return $this->hasMany(CompetitionSeason::class); } public function resolveChildRouteBinding($childType, $value, $field = null) { error_log("[resolveChildRouteBinding] Field: " . $field); if ($childType == 'competitionSeason') { if ($field == 'competition' { // TODO: make the query } } } }
and here I can see $field
being competition
with $value
equal to premier-league
.
Now some considerations: why should the parent class be the one that returns the instance of the child?
Basically, if I go on and implement it like this, the resolveChildRouteBinding
will be the one to return the CompetitionSeason
while I would have expected the resolution logic on CompetitionSeason
itself to be what determines the instance to inject. Am I the only one?
Thank you to @lagbox for pointing out the existence of resolveChildRouteBinding
which I completely ignored for whatever reason (maybe deserves a little more space in the docs?)