Download the PHP package ahmednour/laravel-attribute-routing without Composer
On this page you can find all versions of the php package ahmednour/laravel-attribute-routing. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download ahmednour/laravel-attribute-routing
More information about ahmednour/laravel-attribute-routing
Files in ahmednour/laravel-attribute-routing
Package laravel-attribute-routing
Short Description Define Laravel routes with PHP 8 attributes on your controllers — colocated, composable, and route-cache friendly.
License MIT
Homepage https://github.com/ahmed-nour-dev/laravel-attribute-routing
Informations about the package laravel-attribute-routing
Laravel Attribute Routing
Define Laravel routes with PHP 8 attributes, on the controller methods that handle them.
That controller is its own routing documentation. There is no api.php entry to keep in sync.
Why
Past a couple of hundred routes, routes/api.php becomes a wall of text, and understanding one
endpoint means holding two files in your head at once — the path and middleware live in one place,
the code that serves them in another.
Colocation is how the rest of the ecosystem already works: React colocates components, Vue has
<script setup>, Symfony has shipped attribute routing for years. This package brings the same
idea to Laravel, as an opt-in layer — your existing web.php and api.php keep working
untouched, and you can move one controller at a time.
Three things you get that route files can't give you:
- Find usages actually works. "Find all references" on a permission enum case lists every route that requires it.
- Middleware composition reads top to bottom. Class-level attributes apply to the whole
controller, method-level attributes stack on top,
#[WithoutMiddleware]opts a single method out. - Nothing costs anything in production. Discovery is skipped entirely when routes are cached.
Installation
The service provider is auto-discovered. Publish the config if you need to change the scanned paths:
Requires PHP 8.2+ and Laravel 12 or 13. (Laravel 11 is not supported: every 11.x release is now blocked by unpatched security advisories.)
Usage
Import the attributes and put them on your controller:
HTTP verbs
| Attribute | Registers |
|---|---|
#[Get('users')] |
GET, HEAD |
#[Post('users')] |
POST |
#[Put('users/{user}')] |
PUT |
#[Patch('users/{user}')] |
PATCH |
#[Delete('users/{user}')] |
DELETE |
#[Options('users')] |
OPTIONS |
#[Any('webhook')] |
every verb |
#[MatchRoute(['put', 'patch'], 'password')] |
the verbs you list |
Every verb attribute takes the same optional arguments:
They are repeatable — one handler can answer on more than one path:
Class-level composition
| Attribute | Effect |
|---|---|
#[Prefix('api/leads')] |
URI prefix for every route in the class. Repeatable — segments join in declaration order. |
#[Version('v1')] |
Same as Prefix, named for intent: #[Prefix('api')] #[Version('v1')] → api/v1/... |
#[Middleware(['api', 'auth:sanctum'])] |
Middleware for every route in the class. |
#[Name('leads.')] |
Name prefix for every named route in the class. |
#[Throttle(60, 1)] |
60 requests per minute, applied to the whole class. |
#[WithPermission(...)] |
Permission middleware for the whole class. |
#[WithoutMiddleware('auth:sanctum')] |
Excludes inherited middleware. |
#[SkipDiscovery] |
Excludes the class from scanning entirely. |
All of them work on methods too, where they stack on top of the class-level value:
Middleware is applied in a predictable order: class-level, then method-level, then the verb
attribute's own middleware: argument. Duplicates are removed, first occurrence wins.
Route names
A route is named only when the method supplies a name. A class-level #[Name('leads.')] is a
prefix, never a name on its own — so adding one can't silently collapse five routes onto a single
name. #[Name] on a method overrides the verb attribute's name: argument.
Permissions
#[WithPermission] turns permission enum cases into middleware. Implement Permitted on your enum
to control the exact string:
Three forms are accepted:
- a
Permittedenum case → uses its owngetMiddleware() - any other backed enum → formatted with
attribute-routing.permission_middleware(permission:%s) - a raw string → passed straight through if it contains a
:(e.g.'can:download-audit'), otherwise formatted
Pass several at once for "any of" (OR) semantics — #[WithPermission(Permission::A, Permission::B)]
collapses into a single permission:a,b middleware entry, and the user needs only one of them.
To require several different permissions (AND), stack the attribute instead — it's repeatable,
and each instance contributes its own middleware entry:
Configuration
Narrowing paths to the directory that actually holds your controllers makes boot faster:
Performance and route caching
Discovery runs at boot in development. It scans the configured directories, skips any file with no
#[ in it before autoloading, and only then reflects over classes.
In production you run php artisan route:cache as usual. Cached routes already contain everything
discovery would have produced, so the package does no work at all on a cached boot.
Inspecting what was discovered
php artisan route:list shows attribute routes alongside the rest. When a route doesn't show up
and you want to know whether the class was even scanned:
Each route shows the Controller@method it came from, and the footer reports how many classes were
scanned — usually enough to spot a paths config that doesn't cover your controllers.
Add -v for the middleware stack, excluded middleware, and parameter constraints, and to see
controller names in full rather than shortened against your application namespace:
Gotchas
- Inherited methods are not re-registered. A route attribute on a method in an abstract base controller registers nothing; each subclass would otherwise register the same URI. Put the attribute on the concrete method. Trait methods do register, once per using class.
- Abstract classes, interfaces, and enums are skipped.
- Discovery autoloads the classes it scans. Anything with side effects at class-definition time will run at boot. Attribute-free files are skipped before that happens.
Matchis a reserved word, hence#[MatchRoute].
Testing
Credits
Extracted from the routing layer of a production Laravel application, where it registers 200+ routes.
Built by Ahmed Nour.
License
MIT. See LICENSE.
All versions of laravel-attribute-routing with dependencies
illuminate/console Version ^12.0|^13.0
illuminate/routing Version ^12.0|^13.0
illuminate/support Version ^12.0|^13.0