← Laravel: requests, data and application decisions
03 / 10 · 65 MIN

Routes, binding and middleware

Interpret method, parameters, binding and middleware as distinct request contracts. Locate failures before the controller and avoid confusing resource membership with permission to modify it.

Method, path and intent

A URL alone does not define an action. A POST route creating orders should not become any merely because a client sent GET. First reconcile operation intent with its method and intended controls.

The route name supports URL generation and should be unique; it grants no authorization and does not change accepted methods. Named references allow changing a path without maintaining literal copies across views.

Constraints before the controller

whereNumber limits a segment’s format. For a request containing AB7, a numeric constraint does not convert text to zero: the route does not match. If no other route or fallback matches, normal handling returns 404.

Syntactic validity also does not establish resource existence. When moving from numeric IDs to codes, review both the constraint and the model lookup key; increasing SQL timeout does not help if the controller was never reached.

Resolve models with consistent names

In implicit binding, the typed model parameter should match the segment name. In the example, Ticket $ticket matches {ticket}. A string signature expresses a different intent; importing the model without using the correct type does not define binding either.

When there is no matching model, the framework returns 404 under normal handling. This helps recognize resolution failure before the action without confusing it with an SQL exception inside the controller.

Membership and permission are distinct decisions

scopeBindings resolves the child through the parent according to relationship conventions. With Team::tickets and the example’s segments, another team’s ticket does not satisfy lookup. But a user may point to an existing team they cannot access: a valid parent-child pair does not authorize reading or editing data.

The example therefore retains a separate policy. Binding, authentication and authorization address location, identity and permitted operation respectively.

Middleware: entry, return and scope

Imagine A wrapping B and handler H. If both call $next once with no exceptions or interruptions, entry is A,B,H and return is B,A. A control placed only after $next may find that the write has already occurred.

Also distinguish global assignment from route assignment: withoutMiddleware can exclude route middleware but does not remove a class from the global stack. Checking effective assignment is more useful than changing the HTTP method or class name.

Ordering dependencies and groups

If CheckTenantAccess depends on context created by ResolveTenant, document and configure the required precedence. Laravel supports priorities in withMiddleware, including adding relationships to the existing list. Do not discard framework-component ordering without analysis.

Under the installed default registration, routes/api.php receives the api group and /api prefix; this does not authenticate every route. A public route and one with auth:sanctum can coexist in the same file.

Published and loaded definitions

Route cache is a router artifact. When a release retains that old artifact, editing the route file does not establish that the process is using the new definition. Regenerate the cache during delivery and compare effective routes with the expected contract.

Distinguish it from config:cache, view cache and browser cache. In the ID-migration scenario, a code correction still needs to match the router version actually loaded.

<?php
// Route excerpt: imports/application controller omitted.
Route::get('/teams/{team}/tickets/{ticket}', [TicketController::class, 'show'])
    ->whereNumber('team')
    ->whereNumber('ticket')
    ->scopeBindings()
    ->middleware('auth')
    ->name('teams.tickets.show');

// Controller signature excerpt; Team::tickets() is the matching relation.
public function show(Team $team, Ticket $ticket)
{
    // A separate policy check is required before disclosing this resource.
    Gate::authorize('view', $ticket);
    return view('tickets.show', ['ticket' => $ticket]);
}
IN PRACTICE

The excerpt is for interpreting a read route. Both segments are numeric and their names match the typed models. scopeBindings limits ticket lookup to the team’s tickets relationship. auth identifies the user; the application-defined view policy decides ticket access.

Imports, relationship, policy and view are not implemented in this excerpt, and execution is not requested.

Common pitfalls

Fixing a wrong method with any; treating numeric constraints as type conversion; assuming scopeBindings runs policies; excluding global middleware through withoutMiddleware; changing files while retaining an old route cache.

Related topics: Request lifecycle and configuration · Validation and request contracts · Authentication, authorization and sessions

Take this idea with you

The route defines which request may reach the action. Binding resolves a resource within a scope; authorization decides access. Middleware wraps the action and may interrupt it. Diagnosis should show which contract failed and which router version was loaded.

Create account

Reference: HTTP methods and route matching · Laravel 13.x; PHP 8.3+