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

Attributeutils Laravel Package

crell/attributeutils

Utilities for PHP 8+ attributes: a cached AttributeReader that fetches and instantiates attributes on classes, methods, properties, and parameters, with helpers for repeatable attributes, inheritance, and object mapping. A lightweight base for attribute-driven config and dependency injection.

Deep Wiki
Context7

Getting Started

Install via Composer — there is no Laravel service provider or facade, so it's a plain library:

composer require crell/attributeutils

First look at three things:

  1. Crell\AttributeUtils\ClassAnalyzer — the main entry point. Given a class name and an attribute class, it returns a fully-populated "definition" object aggregating every attribute instance on the class, its properties, methods, and parameters.
  2. Crell\AttributeUtils\AttributeUtils — factory helpers (createClassAnalyzer(), createClassScanner()) so you don't wire interfaces manually.
  3. Crell\AttributeUtils\Attributes\Reflect\... — the attribute interfaces (ScansClassDefinition, ScansProperties, ScansMethods) that let your own attribute class opt in to deeper scanning.

Minimal first use case — annotate a controller and read the attribute back:

use Crell\AttributeUtils\Attributes\Reflect\ReflectClass;
use Crell\AttributeUtils\Http\Attribute\Route;
use Crell\AttributeUtils\ClassAnalyzer;
use Crell\AttributeUtils\AttributeUtils;

#[Attribute(Attribute::TARGET_CLASS)]
class Route
{
    public function __construct(
        public readonly string $path,
        public readonly string $method = 'GET',
    ) {}
}

#[Route('/dashboard', 'GET')]
class DashboardController {}

$analyzer = AttributeUtils::createClassAnalyzer();
$route = $analyzer->analyze(DashboardController::class, Route::class);

echo $route->path; // /dashboard

For a single attribute, plain ReflectionAttribute::newInstance() is fine; reach for this package once you need inheritance, merging, repeatables, or caching.

Implementation Patterns

Analyzer as a singleton, resolved from the container. Bind ClassAnalyzer once and inject it. Do not instantiate it per request in a hot path — reflection is the expensive part.

// A service provider
$this->app->singleton(ClassAnalyzer::class, fn () => AttributeUtils::createClassAnalyzer());

Definition objects. The real value isn't a single attribute — it's the aggregated definition. Have your attribute's target be a small value object you build in analyze(), then treat that object as your runtime metadata. This is the same shape as Symfony's routing metadata or how Laravel resolves policy/ability metadata — good for:

  • Route registration from controller attributes (#[Route], #[Middleware]).
  • Policy/ability discovery from model attributes to auto-register Gate::policy().
  • DTO validation rules (#[Validate('required|email')] on properties).
  • Queue job metadata (queue, timeout, retries) read by a worker base class.
  • Serialization strategy per property (#[SerializeAs('json')] on a property).

Interface-driven scanning. Implement ScansClassDefinition, ScansProperties, ScansMethods, ScansParameters on your attribute class to tell the analyzer how deep to recurse. This is where the package earns its keep versus raw reflection: you declare what you care about and let inheritance/merge/repeat rules work.

Attribute inheritance and merge order. Classes can inherit attribute values from parents and interfaces. Order is child → parent → interface; last-write wins per property, but interfaces contribute first. This is what makes the package usable for base controller/model attributes that subclasses should be able to override.

Repeatable attributes. Use #[Attribute(Attribute::IS_REPEATABLE)] and collect them into an array. The scanner preserves declaration order — useful when order encodes priority (middleware chains, layered rules).

Property attribute collection. Scanning a class scans all properties at once, so batch work into one analyze() call instead of one call per attribute. If you need ReflectionProperty, ReflectionMethod, ReflectionParameter, the analyzer gives you a ReflectionClass you can hang on to — don't re-reflect.

Caching layer. The analyzer accepts a PSR-16 SimpleCache. In Laravel, wrap around the cache store:

$cache = new \Crell\AttributeUtils\Cache\Psr16Cache(
    Cache::store('array')->psr(), // or a file store
    'attribute-meta',
);
$analyzer = AttributeUtils::createClassAnalyzer($cache);

Key on class name + attribute class. Use a deploy-time tag/cache-version bump so a code change invalidates everything.

Custom analyzers. Implement ClassAnalyzer for domain-specific behavior (e.g. scanning only app/Http/Controllers/** and returning a registry keyed by route signature). Wrap the library analyzer rather than reimplementing reflection.

Gotchas and Tips

  • #[\Attribute] targets are enforced silently-ish. If TARGET_PROPERTY is set but the attribute is placed on a method, PHP will reject it at newInstance() time — but only when the analyzer reaches that node. Misplaced attributes can surface as confusing "no definition" outcomes rather than a clear error.
  • Inheritance is opt-in behavior, not PHP behavior. ReflectionClass::getAttributes() does not walk parents for you. The library handles inheritance only when your attribute implements the appropriate Scans* interface — if you skip it, parent attributes are invisible.
  • Cache invalidation bites. Attribute changes are code changes; if your cache key is just the class name, stale metadata survives a deploy. Use filemtime() of the class file, a git SHA in the namespace, or flush the tag on deploy.
  • Laravel Octane. Singleton analyzers with in-memory caches are fine, but the content must be invalidated on code reload. Either cache to a shared store (Redis) or key on the file mtime so workers don't return stale definitions after a composer dump-autoload.
  • Don't scan in boot(). Do the scanning lazily inside the container binding closure or on-first-use, not during the service provider boot — otherwise every request pays for it even when the metadata isn't needed.
  • Constructor-parameter attributes. Attributes on promoted constructor params live on ReflectionParameter, not ReflectionProperty, even though the value is a property. Use the parameter scanning interface if you need them.
  • Repeatable attribute merging is not "append only". If your attribute merges multiple declarations into a value object, the merge rules are yours — the package only collects and orders them. Document your merge precedence in the attribute class itself.
  • Debugging: var_dump($analyzer->analyze(Foo::class, MyAttr::class)) is the fastest diagnostic. If it returns null, the attribute likely isn't #[\Attribute]-flagged for that target, or the class is in a namespace the autoloader can't reflect (e.g. eval'd or dynamically generated strings).
  • Extension point: implement ScansClassDefinition (etc.) on your attribute to add per-node logic. That's the intended hook — subclassing ClassAnalyzer is the escape hatch for cross-cutting changes, but it will tie you to internals.
  • Performance ceiling. Reflection is ~10–100× slower than a plain array lookup. Once you're past a handful of classes per request, the cache is not optional; treat uncached scanning as a CLI/testing-only mode.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console