DazedAndConfused
DazedAndConfused

Reputation: 115

Laravel Route Model Binding - Laravel 5.7

I'm trying to use Laravel's route model binding. I've setup a binding in the RoutesServiceProvider to perform some custom resolution logic. This works fine for the attributable parameter which requires both a string name and an id to resolve.

However, when I attempt to type cast a method to take advantage of implicit binding for another model it fails with the error

Argument 2 passed to Illuminate\Routing\Router::{closure}() must be an instance of App\Models\Staff, string given, called in /var/www/html/ngj_form/vendor/laravel/framework/src/Illuminate/Routing/Route.php on line 198

After some debugging I can see that it's passing the {attrId} part of the route as the second typecast parameter in the method definition below. ID is a string therefore it fails. But why is it even trying to pass this parameter?

The Route looks like this:

Route::get('/admin/create-staff-payment/{attributable}/{attrId}/staff-member/{staff}/', 'Admin\StaffBalances@granularStaffBalance');

The typecast controller method looks like this:

 public function granularStaffBalance(Attributable $attributable, Staff $staff)
{
    dd('huh?');
}

And the RouteServiceProvider looks like this:

  public function boot()
{

    // Bind Attributable (wedding|trial)
    Route::bind('attributable', function ($attributable, $route) {

        $attributableId = $route->parameter('attrId');

        switch($attributable){
            case 'wedding':
                $attributable = Wedding::class;
                break;
            case 'trial':
                $attributable = Trial::class;
                break;
            default:
                throw new \Exception('Type parameter provided is not supported.'); //TODO change this to 404 redirect
        }

        return $attributable::where('id', $attributableId)->firstOrFail();
    });

...

Upvotes: 0

Views: 789

Answers (1)

DazedAndConfused
DazedAndConfused

Reputation: 115

Ok, after further research (no thanks to the Laravel docs) I think this is expected behaviour. If you pass three parameters in a route your controller method will expect to receive them in the order they appear in the url. I guess I'd assumed from the docs that the selection of parameters was more intelligent than this but I guessed wrong.

The way I've gotten around it is via an answer in another post.

Apparently you can forget parameters as a means of discarding them and this lets my controller method accept two parameters instead of three.

So now my RouteServiceProvider looks like this (posting here in case it helps someone else):

 public function boot()
    {

        parent::boot();

        // Bind Attributable (wedding|trial)
        Route::bind('attributable', function ($attributable, $route) {

            $attributableId = $route->parameter('attrId');

            switch($attributable){
                case 'wedding':
                    $attributable = Wedding::class;
                    break;
                case 'trial':
                    $attributable = Trial::class;
                    break;
                default:
                    throw new \Exception('Type parameter provided is not supported.'); //TODO change this to 404 redirect
            }

            // We don't want to use this parameter in any controller methods so we can drop it here.
            // This fixes an issue where it was being passed into methods that needs $attributable, $staff
            $route->forgetParameter('attrId');

            return $attributable::where('id', $attributableId)->firstOrFail();
        });
...

Upvotes: 0

Related Questions