Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Attributes Laravel Package

milwad/laravel-attributes

Laravel Attributes lets you configure Eloquent models using native PHP 8 attributes, replacing boilerplate properties and methods for fillable, hidden, casts, accessors, and more. Keep model classes clean, declarative, and easier to scan.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install — composer require milwad/laravel-attributes. Laravel's package auto-discovery registers the service provider; no manual config/app.php entry needed on Laravel 9+.
  2. Publish config (if offered) — php artisan vendor:publish --tag="laravel-attributes-config". Look at the discovery paths and any cache settings. This is the first file to read if routes "don't show up."
  3. Read two places before writing code — the README's attribute table (it documents each attribute's constructor signature) and vendor/milwad/laravel-attributes/src/Attributes/ — the attribute classes are the API surface. Skim the service provider to see how routes are registered.
  4. First use case — routes on a controller:
use Milwad\LaravelAttributes\Attributes\Route;

class PostController
{
    #[Route(uri: '/posts', methods: ['get'], name: 'posts.index')]
    public function index()
    {
        return PostResource::collection(Post::paginate());
    }
}
  1. Verify — php artisan route:list --name=posts should show the route. Hit it in the browser or with curl.
  2. Clear caches immediately after adding/removing attributes — php artisan optimize:clear. Route discovery is boot-time; stale caches are the #1 "it doesn't work" cause on day one.

Start with one controller (a simple index + show) before migrating anything. The package's value is proportional to how many controllers you have; prove the pattern on one, then scale.

Implementation Patterns

Class-level prefix + middleware, method-level routes. Use a controller attribute for the shared concerns and reserve #[Route] for the endpoint-specific data:

#[Controller(prefix: 'api/v1/posts', middleware: ['auth:sanctum'])]
class PostController
{
    #[Route(uri: '/', methods: ['get'], name: 'api.posts.index')]
    public function index() { /* ... */ }

    #[Route(uri: '/{post}', methods: ['get'], name: 'api.posts.show')]
    public function show(Post $post) { /* ... */ }

    #[Route(uri: '/', methods: ['post'], name: 'api.posts.store', middleware: ['throttle:60,1'])]
    public function store(StorePostRequest $request) { /* ... */ }
}

This mirrors Route::group(['prefix' => ..., 'middleware' => ...], fn () => ...) but colocated with the code it affects.

Incremental migration from routes/*.php. Don't convert everything at once. Pick a module — typically a new API surface — and let attribute discovery own those routes. Leave legacy routes in routes/web.php. The two systems coexist because both ultimately feed the same router. Just keep name prefixes distinct (api.v1.* vs web.*) so you can tell them apart in route:list.

CRUD-heavy controllers. If the package ships a resource attribute, use it for genuine CRUD. For anything with conditional verbs or irregular URIs, write explicit #[Route] entries — generated routes are harder to grep and to debug than five explicit lines.

Validation / authorization stays in the framework. Attributes here describe routing and middleware, not request logic. Keep FormRequest classes and $this->authorize() / Policy calls exactly where they are. Don't try to express authorization rules as attributes unless the package explicitly supports it — you lose policy auto-discovery.

Route model binding still works via type-hints on the controller method; the attribute only supplies the URI pattern. Ensure the {post} placeholder matches the parameter name.

Versioning. Model API versions with #[Controller(prefix: 'api/v2')] on a separate controller class, or a subnamespace Controllers/Api/V2. The latter is more idiomatic Laravel and easier to reason about in discovery.

Testing. Nothing changes. $this->getJson('/api/v1/posts') exercises the same stack. Add a small smoke test that asserts key routes exist (Route::has('api.posts.index')) so a discovery/config regression fails CI rather than production.

Deploy pipeline. Add php artisan route:clear (or optimize:clear) to your deploy script if the package's discovery interacts with cached routes. Confirm whether route:cache captures attribute routes before you rely on it in production.

Gotchas and Tips

Discovery scope is the silent failure mode. The package scans configured directories (typically app/Http/Controllers). Controllers living outside that tree — domain modules, src/, package controllers — will not be registered. Symptom: 404 with no error, route absent from route:list. Fix by publishing the config and adding the path.

Only public methods are scanned. A private or protected method with #[Route] is ignored without warning. If a route is missing, confirm the method is public.

Caches are the other silent failure. Attribute routes computed at boot mean:

  • php artisan route:clear after editing attributes
  • php artisan optimize:clear when odd things happen in local dev
  • php artisan route:cache in production — verify it actually includes attribute routes; if not, don't cache routes, or cache after discovery runs.

Duplicate route names throw at registration. Because names are strings with no IDE assistance, collisions across a migrated module are common. Run php artisan route:list --name=api. after each batch and grep for duplicates. Consider defining route names as constants in one class per module to get autocompletion and catch typos early.

Middleware ordering matters and is easy to get wrong. Class-level middleware applies to every method; method-level runs additionally. If auth is at class level and can:update,post is at method level, order is preserved — but if you put auth only on some methods, expect subtle authorization failures. Keep auth at the class level and let policies do per-method checks.

No static analysis / IDE support for URIs or names. #[Route(uri: '/posts/{post}')] is a string; a typo in {post} vs the Post $post parameter is a runtime ModelNotFoundException, not a compile-time error. Cover the important routes with feature tests.

Debugging checklist for a 404:

  1. php artisan route:list -v | grep posts — is it there at all?
  2. If absent: is the controller in a discovery path? Is the method public? Is the URI pattern valid?
  3. If present but 404: caches (route:clear), then middleware (a throttle/auth returning 404 from a custom handler).
  4. If 500: check the attribute's named arguments against the class constructor — a missing required argument throws a TypeError at reflection time, often surfacing as a boot failure.

Prefer named arguments in attributes. #[Route(uri: '/x', methods: ['get'])] is far more readable (and refactor-safe) than positional arguments, and it protects you from signature changes between minor releases.

Extension point. Writing your own attribute is straightforward: define a class with #[Attribute(Attribute::TARGET_METHOD)] (or TARGET_CLASS), then register a handler with the package's registrar in a service provider — check how the package's own provider wires #[Route] and follow that pattern. If the extension API feels thin, you can drop back to plain PHP reflection in a custom service provider and handle your attribute yourself; the package doesn't add framework magic, so nothing prevents this.

Pin the version and check the PHP/Laravel constraints in composer.json. This is a small, single-maintainer package; minor releases have moved fast. Lock the constraint to ^x.y and run your route smoke tests after upgrades. Given the low dependent count, treat upgrades as you would any less-traveled dependency — a quick manual test of a few attribute-routed endpoints before shipping.

Don't mix paradigms inside one controller. A controller whose methods are half attribute-routed and half referenced from routes/web.php works, but is a maintenance trap. Pick one per class.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle