Laravel / Basic: request and data foundations / Lesson 2
Routes, parameters, and generated URLs
Define route contracts by method, path, parameters, and name. Distinguish matching from authorization and generate links without scattering hard-coded paths across the application.
On this page
Lesson goals
- Trace route matching and parameter constraints.
- Use named routes as a maintained link contract.
- Explain model binding and middleware limits.
A route is more than a URL-shaped string
A route associates allowed HTTP methods and a URI pattern with an action. A GET read should not create a reservation simply because a query parameter requests it. Keep route order and overlapping patterns reviewable, and constrain parameters where the contract requires a numeric identifier or bounded slug. A matched route does not prove the current actor may access the resource. Middleware and policy checks still own authentication and authorization. Name routes deliberately so templates and redirects depend on a stable application contract instead of copying physical paths into many unrelated files.
Inspect matching without dispatching an application
The local collection contains one constrained course route. Matching a synthetic request selects the route and exposes its id parameter without running a controller, middleware, database lookup, or network listener. This is useful evidence for the matching rule, but it cannot establish web behavior beyond that rule. Add a separate negative case for a disallowed parameter and an integration case for the actual application group when appropriate. Treat a not-found result differently from a controller exception; both can look like a failed page while originating at different stages of the lifecycle.
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
$routes = new Illuminate\Routing\RouteCollection();
$route = new Illuminate\Routing\Route(['GET'], 'courses/{id}', fn() => 'unused');
$route->where('id', '[0-9]+')->name('courses.show');
$routes->add($route);
$request = Illuminate\Http\Request::create('https://learning.example/courses/7', 'GET');
$matched = $routes->match($request);
echo $matched->getName(), ' ', $matched->parameter('id'), PHP_EOL;
courses.show 7
The callback is not executed. The output establishes route selection and extracted text, not a loaded course or permission.
Model binding finds data; policies decide access
Implicit or explicit route model binding can translate a route value into a model under defined rules. A missing model may become a not-found response, but a found model is not automatically authorized. Nested resource binding needs deliberate scoping so a child cannot be selected outside its parent merely by changing an identifier. Soft-deleted records, custom keys, and tenant boundaries deserve explicit tests. Do not trust a numeric constraint as an ownership check. Review whether lookup itself should conceal existence, and coordinate the resulting response with the product’s privacy policy.
Generate named links with explicit parameters
A URL generator uses the named route and supplied parameters to build the intended address. The example fixes a synthetic origin and generates a course link without contacting it. Real origins, trusted proxies, HTTPS termination, and locale prefixes are deployment or application concerns that should be configured and tested separately. A generated URL is still subject to permission at the destination. Avoid embedding secrets or session identifiers in query strings, and do not accept arbitrary redirect destinations from users without an allow policy. Stable names help refactoring only when references and required parameters remain maintained together.
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
$routes = new Illuminate\Routing\RouteCollection();
$route = new Illuminate\Routing\Route(['GET'], 'courses/{id}', fn() => 'unused');
$route->name('courses.show');
$routes->add($route);
$request = Illuminate\Http\Request::create('https://learning.example', 'GET');
$urls = new Illuminate\Routing\UrlGenerator($routes, $request);
echo $urls->route('courses.show', ['id' => 7]), PHP_EOL;
https://learning.example/courses/7
No request is sent. The result verifies the name/parameter contract in this owned route collection.
Diagnose the first routing boundary that failed
For an unexpected 404 or 405, record method, effective path, host, parameter constraints, route name, and the relevant group middleware. Compare those facts with the declared route before changing a controller or database query. A stale route cache can preserve an older definition, but clearing every cache blindly is not a diagnosis. Inspect the intended environment and documented build workflow. Tests should cover permitted and rejected methods, representative parameters, generated links, and scoped binding behavior. Keep local collection tests distinct from full HTTP tests that exercise application bootstrap and exception rendering.
Common errors and fixes
A numeric id is assumed to establish ownership
- Why it happens
- Route constraints are confused with authorization.
- How to fix it
- Apply scoped lookup and a policy for the actual object.
- Prevent it next time
- Test another owner and parent/child mismatch.
A route name changes but links are not updated
- Why it happens
- The name/parameter contract is not maintained as one unit.
- How to fix it
- Update references and generated-link tests together.
- Prevent it next time
- Use stable route names and explicit required parameters.
Every 404 triggers a database rewrite
- Why it happens
- Matching and later model/action failures are conflated.
- How to fix it
- Trace method, path, match, binding, and action in order.
- Prevent it next time
- Preserve a minimal synthetic routing reproducer.
Check your understanding
1. What does matching /courses/7 prove in the local example?
Show worked solution
It proves the constrained collection selects courses.show and extracts id text 7. It does not query a model, run middleware, authorize a user, or emit HTTP. Add those checks at their own integration boundaries instead of inflating a matching test into a full access claim.
2. A child course id belongs to another organization. Which checks matter?
Show worked solution
Scope lookup to the intended organization or parent relationship and authorize the action against the actual bound course. A numeric pattern and successful model lookup are insufficient. Verify the denied case returns the agreed safe response without leaking fields or changing state.
Key takeaways
- Route matching, data binding, and authorization answer different questions.
- Named URL contracts need explicit parameters and maintained evidence.