PHP 8.0 introduced attributes — a first-class way to add structured metadata to classes, methods, properties, and more. But reading them programmatically with the native reflection API is verbose and repetitive. Spatie's PHP Attribute Reader wraps that boilerplate in a clean static API.
For example, reading a single class attribute with the native reflection API looks like this:
$reflectionClass = new ReflectionClass(PostController::class);$attributes = $reflectionClass->getAttributes(Route::class); if (count($attributes) > 0) { $route = $attributes[0]->newInstance();}
Now imagine doing that across every method, property, constant, and parameter of a class.
Installation
composer require spatie/php-attribute-reader
Reading Attributes
The package exposes a single Attributes class with static methods for all reads. Call get() with the target class as the first argument and the attribute class as the second to retrieve an instantiated attribute object, or null if not found:
Assuming we had defined our own custom Route attribute and used it in a controller:
use App\Attributes\Route; #[Route('/posts', methods: ['GET'])]class PostController{ // ...}
We can get or check for that attribute with the following:
use Spatie\Attributes\Attributes;use App\Attributes\Route; $route = Attributes::get(PostController::class, Route::class); Attributes::has(PostController::class, Route::class); // true or false
For methods, properties, constants, and parameters, the package provides dedicated methods:
Attributes::onMethod(PostController::class, 'index', Middleware::class);Attributes::onProperty(Post::class, 'title', Column::class);Attributes::onConstant(PostStatus::class, 'DRAFT', Label::class);Attributes::onParameter(PostController::class, 'show', 'slug', FromRoute::class);
Each returns the instantiated attribute object, or null if not found.
Finding Attributes Across a Class
Attributes::find() searches for all usages of a given attribute across a class — its class definition, methods, properties, constants, and parameters — in one call.
So if we had our own custom Validate attribute and used it in the following class:
use App\Attributes\Validate; class ContactForm{ #[Validate('required|string')] public string $name; #[Validate('required|email')] public string $email; #[Validate('required|string')] public string $message;}
We could then find all usages like this:
$results = Attributes::find(ContactForm::class, Validate::class); foreach ($results as $result) { $result->name; // e.g. 'name', 'email', 'message' $result->attribute; // the instantiated attribute object $result->target; // the underlying Reflection* object}
You can also omit the attribute class to retrieve every attribute on a class. The package uses IS_INSTANCEOF matching, so searching for a base attribute class will also match subclasses.
PHP Attribute Reader removes the reflection boilerplate from common attribute operations. If you're working with custom PHP attributes, it's worth a look. You can find the source code on GitHub and the full documentation on spatie.be.