dentelis / php7-attribute-reader
Library for reading php8 attributes from legacy php7 code
Requires
- php: ~7.2
Requires (Dev)
- phpunit/phpunit: ^8.5
This package is auto-updated.
Last update: 2026-03-10 15:11:31 UTC
README
A fast and simple library that brings PHP 8 attribute syntax to PHP 7.2+. Read and parse PHP 8 style attributes from comments in legacy PHP codebases.
๐ฏ Why This Library?
PHP 8 introduced native attributes (also known as annotations in other languages), but many projects are still running on PHP 7.x. This library allows you to:
- Write modern PHP 8 attribute syntax that works in PHP 7.2+
- Future-proof your codebase - attributes written with this library work as comments in PHP 7 and as native attributes in PHP 8
- Gradually migrate from PHP 7 to PHP 8 without rewriting your metadata
- Use attributes for frameworks that don't support PHP 8 yet
๐ฆ Installation
composer require dentelis/php7-attribute-reader
๐ Quick Start
Basic Usage with Plain Arrays
use AttributeReader\AttributeReader; class UserController { #[Route('/api/users', 'GET')] #[Auth('admin')] public function getUsers() { // Your code here } } // Read attributes as plain arrays $attributes = AttributeReader::getPlainAttributes( UserController::class, 'getUsers' ); // Result: // [ // ['name' => 'Route', 'arguments' => ['/api/users', 'GET']], // ['name' => 'Auth', 'arguments' => ['admin']] // ]
Advanced Usage with DTO Classes (Recommended)
Define your attribute classes:
namespace App\Attributes; class Route { public $path; public $method; public function __construct(string $path, string $method = 'GET') { $this->path = $path; $this->method = $method; } } class Auth { public $role; public function __construct(string $role) { $this->role = $role; } }
Use them in your code:
class UserController { #[Route('/api/users', 'POST')] #[Auth('admin')] public function createUser($data) { // Your code here } } // Create reader with attribute mapping $reader = new AttributeReader([ 'Route' => \App\Attributes\Route::class, 'Auth' => \App\Attributes\Auth::class, ]); // Get attribute instances $attributes = $reader->getAttributes(UserController::class, 'createUser'); // Work with strongly-typed objects foreach ($attributes as $attribute) { if ($attribute instanceof \App\Attributes\Route) { echo "Path: {$attribute->path}, Method: {$attribute->method}\n"; // Output: Path: /api/users, Method: POST } if ($attribute instanceof \App\Attributes\Auth) { echo "Required role: {$attribute->role}\n"; // Output: Required role: admin } }
๐จ Features
Named Arguments Support
Use PHP 8 named arguments syntax:
class ProductController { #[Route(path: '/products', method: 'GET')] #[Cache(ttl: 3600, enabled: true)] public function list() { // Your code here } } $reader = new AttributeReader([ 'Route' => \App\Attributes\Route::class, 'Cache' => \App\Attributes\Cache::class, ]); $attributes = $reader->getAttributes(ProductController::class, 'list');
Mixed Positional and Named Arguments
#[Route('/api/users', method: 'POST')] // First arg positional, second named #[Cache(ttl: 3600, maxSize: 100)] // All named #[Auth('admin')] // Positional
Multiple Attributes
You can use multiple attributes per method:
// Separate lines #[Route('/admin')] #[Auth('admin')] #[RateLimit(100)] public function adminDashboard() {} // Or comma-separated in one line #[Route('/admin'), Auth('admin'), RateLimit(100)] public function adminDashboard() {}
Unmapped Attribute Handling
Control what happens when an attribute isn't mapped to a class:
// Ignore unmapped attributes (default) $reader = new AttributeReader($map, AttributeReader::UNMAPPED_IGNORE); // Throw exception for unmapped attributes $reader = new AttributeReader($map, AttributeReader::UNMAPPED_THROW);
๐ Complete Example: Building a Router
Here's a practical example of building a simple router using attributes:
<?php namespace App\Attributes; class Route { public $path; public $method; public $middleware; public function __construct( string $path, string $method = 'GET', array $middleware = [] ) { $this->path = $path; $this->method = $method; $this->middleware = $middleware; } } class Middleware { public $name; public function __construct(string $name) { $this->name = $name; } } // Your controller class UserController { #[Route('/api/users', 'GET')] #[Middleware('auth')] public function list() { return ['users' => []]; } #[Route(path: '/api/users', method: 'POST')] #[Middleware('auth')] #[Middleware('admin')] public function create($data) { return ['created' => true]; } #[Route('/api/users/{id}', 'DELETE')] #[Middleware('auth')] public function delete($id) { return ['deleted' => true]; } } // Router implementation class Router { private $reader; private $routes = []; public function __construct() { $this->reader = new \AttributeReader\AttributeReader([ 'Route' => Route::class, 'Middleware' => Middleware::class, ]); } public function registerController(string $controllerClass) { $reflection = new \ReflectionClass($controllerClass); foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { $attributes = $this->reader->getAttributes( $controllerClass, $method->getName() ); $route = null; $middleware = []; foreach ($attributes as $attribute) { if ($attribute instanceof Route) { $route = $attribute; } if ($attribute instanceof Middleware) { $middleware[] = $attribute->name; } } if ($route !== null) { $this->routes[] = [ 'path' => $route->path, 'method' => $route->method, 'handler' => [$controllerClass, $method->getName()], 'middleware' => $middleware, ]; } } } public function getRoutes() { return $this->routes; } } // Usage $router = new Router(); $router->registerController(UserController::class); print_r($router->getRoutes()); // Output: // Array // ( // [0] => Array // ( // [path] => /api/users // [method] => GET // [handler] => Array // ( // [0] => UserController // [1] => list // ) // [middleware] => Array // ( // [0] => auth // ) // ) // [1] => Array // ( // [path] => /api/users // [method] => POST // [handler] => Array // ( // [0] => UserController // [1] => create // ) // [middleware] => Array // ( // [0] => auth // [1] => admin // ) // ) // ... // )
๐ง API Reference
Static Method: getPlainAttributes
Returns attributes as plain arrays without any class instantiation.
public static function getPlainAttributes( string $className, string $functionName ): array
Returns: Array of associative arrays with name and arguments keys.
[
['name' => 'Route', 'arguments' => ['/api/users', 'GET']],
['name' => 'Cache', 'arguments' => ['ttl' => 3600]]
]
Instance Method: getAttributes
Returns attribute instances based on provided mapping.
public function getAttributes( string $className, string $functionName ): array
Returns: Array of instantiated attribute objects.
Constructor
public function __construct( array $attributeMap = [], string $unmappedStrategy = AttributeReader::UNMAPPED_IGNORE )
Parameters:
$attributeMap- Associative array mapping attribute names to fully qualified class names$unmappedStrategy- Strategy for handling unmapped attributes:AttributeReader::UNMAPPED_IGNORE(default) - Skip unmapped attributesAttributeReader::UNMAPPED_THROW- ThrowRuntimeExceptionfor unmapped attributes
๐งช Testing
composer test
๐ Requirements
- PHP 7.2 or higher
- Composer
๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
๐ License
This project is licensed under the MIT License.
๐ Credits
Developed by Dim Entelis
๐ก Use Cases
- Legacy Framework Migration - Gradually introduce modern attribute syntax
- Custom Framework Development - Build attribute-based routing, validation, caching
- API Documentation - Generate API docs from attributes
- Access Control - Define permissions using attributes
- Event Listeners - Register event handlers via attributes
- Validation Rules - Define validation schemas with attributes
- Caching Policies - Configure caching behavior through attributes
- Dependency Injection - Mark injectable services
๐ฎ Future PHP 8 Compatibility
When you migrate to PHP 8, the syntax remains the same! Your attributes will work as native PHP 8 attributes without any code changes:
// This works in both PHP 7 (via this library) and PHP 8 (natively) #[Route('/api/users')] public function getUsers() {}