Laravel PHP Attributes

/ About

The Problem With PHP Attributes

PHP 8 introduced native attributes, a declarative way to attach metadata to classes, methods, and properties. The syntax is nice:

#[Attribute]
class RateLimit
{
    public function __construct(
        public int $maxAttempts,
        public int $decayMinutes = 1
    ) {}
}

#[RateLimit(maxAttempts: 60, decayMinutes: 1)]
class ApiController
{
    #[RateLimit(maxAttempts: 10, decayMinutes: 5)]
    public function store() { }
}

But actually reading those attributes? That's where PHP leaves you on your own. You need the Reflection API:

$reflection = new ReflectionClass(ApiController::class);
$attributes = $reflection->getAttributes(RateLimit::class);

if (count($attributes)) {
    $instance = $attributes[0]->newInstance();
    // Now you can use $instance->maxAttempts, etc.
}

For a single lookup, that's manageable. But if you're using attributes across your application for routing metadata, authorization rules, validation constraints, or feature flags, you'll end up writing the same reflection boilerplate everywhere.

What This Package Does

Laravel PHP Attributes handles three things:

  1. Auto-discovery: scans your app directory for all PHP 8 Attributes and their usages
  2. Caching: stores the results so subsequent requests skip the filesystem scan entirely
  3. Simple API: provides an Attributes facade for querying attributes without touching Reflection

The Facade API

use TWithers\LaravelAttributes\Facades\Attributes;

// Find all attributes on a class
$target = Attributes::findByClass(ApiController::class);
$attrs = $target->allAttributes();

// Find attributes on a specific method
$target = Attributes::findByClassMethod(ApiController::class, 'store');
$rateLimit = $target->findByName(RateLimit::class);

// Find every class/method/property that uses a specific attribute
$targets = Attributes::findTargetsWithAttribute(RateLimit::class);

The AttributeTarget

Every result comes back as an AttributeTarget with a consistent interface:

  • $type: whether the target is a class, method, or property
  • $className: the fully qualified class name
  • $identifier: the method or property name (null for class-level attributes)
  • allAttributes(): returns all attached attributes as AttributeInstance objects
  • hasAttribute(string $name): quick boolean check
  • findByName(string $name): filtered lookup

Each AttributeInstance gives you both the attribute $name and the instantiated $instance with all constructor values populated.

Configuration

Publish the config to customize behavior:

php artisan vendor:publish --provider="TWithers\LaravelAttributes\AttributesServiceProvider" --tag="config"

The config lets you:

  • Toggle caching: enabled by default (you should leave it on)
  • Restrict attributes: list specific attribute classes to scan for instead of discovering all of them
  • Limit directories: narrow the scan to specific namespaces instead of the entire app directory
return [
    'use_cache' => true,

    'attributes' => [
        // Empty = discover all. Or list specific ones:
        // App\Attributes\RateLimit::class,
    ],

    'directories' => [
        'App' => app_path(),
        // Or narrow it down:
        // 'App\Http\Controllers' => app_path('Http/Controllers'),
    ],
];

Standalone Usage

If you don't want the service provider's automatic loading, you can use the AttributeAccessor class directly:

use TWithers\LaravelAttributes\AttributeAccessor;

$attrs = AttributeAccessor::forClass(SomeClass::class);
$attrs = AttributeAccessor::forClassMethod(SomeClass::class, 'someMethod');
$attrs = AttributeAccessor::forClassProperty(SomeClass::class, 'someProperty');

These methods use Reflection on demand, no caching, no auto-discovery. Useful for one-off lookups or testing.

Cache Management

The attribute cache lives at bootstrap/cache/attributes.php. Clear it during deployments:

php artisan attributes:clear

Add this to your deployment script alongside config:cache and route:cache to ensure newly added or modified attributes are picked up.

Installation

composer require twithers/laravel-php-attributes

Requires PHP 8.0+ and Laravel 8+. The package auto-discovers, no manual service provider registration needed.