iWpress's avatar

Disable "route model binding"

Can you please help me? I need to cancel "route model binding" for some models. I tried the following:

class RouteServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->routes(function () {
            Route::prefix('api')
                ->middleware('api')
                ->namespace($this->namespace)
                ->group(base_path('routes/api.php'));

            Route::middleware('web')
                ->namespace($this->namespace)
                ->group(base_path('routes/web.php')); 
        });
        $this->disableRouteModelBinding();
    }

    protected function disableRouteModelBinding(): void
    {
        $models = [
            'language' => \App\Models\Language::class,
            'member'   => \App\Models\Member::class,
            'page'     => \App\Models\Page::class,
        ];

        foreach ($models as $key => $model) {
            Route::bind($key, function ($value) use ($key) {
                return $value;
            });
        }
    }
}

This does not work for the 'language.edit' resource route. Only for it.

0 likes
8 replies
Snapey's avatar

in your controller method, dont type hint the model

1 like
Tray2's avatar

Use id, on your routes instead of the model name books/{id} instead of books/{book} and then as @snapey says, don't type hint in your controller.

public function show($id)

Instead of

public function show(Book $book)
iWpress's avatar

Thanks to everyone who helped! I found a simple solution:

Route::resource(config('app.dashboard') . '/language', LanguageController::class)->parameters(['language' => 'id']);    
martinbean's avatar

@iWpress The “simple” solution is just to not type-hint any model class, like @snapey suggested. Why write code to “solve” a problem that doesn’t exist?

1 like
iWpress's avatar

@martinbean, @snapey Thank you for your help and for not being indifferent to other people's mistakes. Previously, I did it the way @snapey suggested. In the controller methods, I was getting an int $itemId, but it doesn't work for the language.edit resource route. Actually, I'm not sure, maybe it's not my fault, maybe the "language" has its own specifics, the main thing is that I have only this route with this behavior. This route goes through the entire web.php and produces a 404. In the route, {language} is an identifier, but a model is expected, although the controller has an int parameter. In fact, it works as it is written for "route model binding". Sorry if this is stupid, I'm a beginner.

martinbean's avatar

@iWpress If something doesn’t work as intended or documented, then that’s when you stop, evaluate the code you‘ve done, and look for where the issue may have been introduced. You don’t just write more and more and more code to try and “patch” behaviour, because then you just end up with a messy codebase and compound any bugs, because you’ve wrote fixes for fixes for fixes, instead of just addressing the original underlying issue.

iWpress's avatar

@martinbean I understand that you are right, but I could not fix the error (I hope it is a error). Perhaps I need to do the language.edit route in another project that doesn't have language settings. Actually, I got really carried away with "route model binding" and decided that if I have many resource routes and only language.edit doesn't work with ID (it seemed to me that it works like "route model binding"), then it's better to do it "correctly" - the model name and route parameter should be different, then Laravel will 100% not substitute the model instead of ID. Thank you again for your help.

iWpress's avatar

@martinbean The story of my problem. In my project, I had a lot of almost similar tables (clients, contracts, ... languages, ... users) and all these tables had their own controller. I passed the IDs to the methods: client, contract, ... language, ... user, and in the controller I received and processed the models (Language $language). All the controllers were similar, some had additional logic, but if I needed to make some general change, I edited all the controllers (again, I'm a beginner) and constantly thought about OOP! One day I couldn't stand it anymore and decided to rewrite almost the entire backend. I made a basic controller:

class BaseController extends Controller
{
    protected Request  $request;       // The request instance
    protected ?string  $front_theme;   // Frontend theme
    protected ?string  $back_theme;    // Backend theme
    protected Language $current_lang;  // Current content language

    protected string   $itemModel;     // Model of the current item
    protected string   $itemRequest;   // Request validation for the current item
    protected ?bool    $editRequest;   // EditRequest differs from Request (null - use the basic Request) 
    protected ?string  $whoseCategory; // The name ('Post') of the item for which this category was created
    protected ?string  $categoryModel; // Item category model
    protected bool     $itemHasOwner;  // The item has an owner
 
    public function __construct(Request $request)
    {

        $this->request       = $request;
        $this->front_theme   = Cookie::get('front_theme');
        $this->back_theme    = Cookie::get('back_theme');      
        $this->current_lang  = $this->setContentLang($request->language);

        $this->editRequest   = false;
        $this->whoseCategory = null;
        $this->categoryModel = null;
        $this->itemHasOwner  = false;
     
    }
// Сontroller methods for processing resource routes

And then most of my controllers got rid of the code altogether:

class MemberController extends BaseController
{
    public function __construct(Request $request)
    { 
        parent::__construct($request);
        $this->itemModel     = Member::class;
        $this->itemRequest   = MemberRequest::class;
    }
}

or, if there is additional logic:

class ClientController extends BaseController
{
    public function __construct(Request $request)
    { 
        parent::__construct($request);
        $this->itemModel     = Client::class;
        $this->itemRequest   = ClientRequest::class;
        $this->itemHasOwner  = true;
    
    }

    protected function itemQuery(Builder $query): void
    {

        if (!Auth::user()->isAdmin()) {
            $query->where('user_id', Auth::user()->id);
        }

    }

    public function store(): RedirectResponse
    {

        $response   = parent::store();
        $request    = app($this->itemRequest);
        $item       = $request->validated();
        $latestItem = $this->itemModel::latest()->first();
    
        if (isset($item['user_id'])) {
            $latestItem->update(['user_id' => $item['user_id']]);
        }
    
        return $response;
    }
}

But I was unable to make a universal method that receives different models. I naively hoped for:

   public function index(Model $item): View  

You understand that I was expecting disappointment here. This is a mistake. And then I decided that my problem was solved only by transferring the ID:

public function index(int $itemId): View 

Everything worked very well until I started testing the language.edit route. Everything worked very well until I started testing the language.edit route. It returned a 404 error without entering either the controller or the middleware. I tried to manage it even in the RouteServiceProvider. I couldn't change anything, so I abandoned the resource routes for the Language model and wrote custom ones. I was already afraid of "route model binding" and therefore used the {id} parameter instead of {language} in the routes. The language.edit route started working as expected. It was then, under the influence of (perhaps a misunderstanding of) route model binding, that I changed the route parameter in all routes:

...*LanguageController*::class)->parameters(['language' => 'id']);

But I did it after I addressed the community in this post.

Please or to participate in this conversation.