-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Description
Description
Reference Implementation: cakephp-attribute-registry
Abstract
This RFC proposes integrating a centralized attribute discovery and management system into CakePHP core. The system provides automatic scanning, caching, and querying of PHP 8+ attributes across application and plugin codebases, enabling attribute-driven architecture patterns within the framework.
This attribute registry represents the foundational first step toward attribute support in CakePHP, establishing the infrastructure necessary for future attribute-based features across routing, validation, ORM, events, and other framework components.
Motivation
PHP 8 attributes provide native metadata capabilities that enable cleaner, more declarative code. Modern frameworks increasingly adopt attribute-based patterns for routing, validation, ORM mapping, and dependency injection. CakePHP currently lacks a standardized mechanism for discovering and managing attributes across applications and plugins.
Current Pain Points:
- No unified approach for attribute-based features across core components
- Each feature requiring attributes must implement its own discovery logic
- No caching strategy for attribute reflection, leading to performance overhead
- Plugin attributes cannot be easily discovered or queried
- Developers must manually scan and parse attributes for custom features
Proposed Solution Benefits:
- Standardized attribute discovery across framework and applications
- Performance optimization through compiled caching
- Foundation for attribute-driven features (routing, validation, events, etc.)
- Consistent API for attribute queries and filtering
- Plugin ecosystem support out of the box
Terminology
- Attribute: PHP 8+ native metadata attached to code elements via
#[Attribute]syntax - Target: The code element an attribute is attached to (class, method, property, parameter, constant)
- Scanner: Service responsible for discovering PHP files and extracting attributes
- Parser: Service that uses reflection to extract attributes from code elements
- Registry: Central facade providing high-level API for attribute operations
- AttributeInfo: Value object encapsulating complete attribute metadata
- AttributeCollection: Fluent interface for filtering and querying discovered attributes
Architecture
Core Components
1. AttributeRegistry (Facade)
Central access point providing high-level API for attribute operations:
AttributeRegistry
├── discover(): AttributeCollection
├── findByAttribute(string): AttributeCollection
├── findByClass(string): AttributeCollection
├── findByTargetType(AttributeTargetType): AttributeCollection
├── warmCache(): int
└── clearCache(): bool
Responsibilities:
- Coordinate attribute discovery and caching
- Provide unified query interface
- Manage lifecycle events
- Support both singleton and dependency injection access patterns
2. Service Layer
AttributeScanner
- Discovers PHP files using configurable glob patterns
- Resolves paths for application and plugins
- Filters files based on include/exclude patterns
- Yields file paths for processing
AttributeParser
- Uses reflection to extract attributes from code elements
- Supports all PHP attribute targets except standalone functions
- Instantiates attribute classes with proper arguments
- Generates AttributeInfo value objects
CompiledCache
- Serializes discovered attributes to compiled PHP files
- Validates cache integrity via file modification times
- Handles cache invalidation and rebuilding
- Thread-safe cache operations
PathResolver
- Resolves glob patterns to absolute file paths
- Integrates with PluginLocator for plugin path discovery
- Normalizes paths across platforms
PluginLocator
- Discovers loaded CakePHP plugins
- Provides plugin base paths for scanning
- Integrates with CakePHP plugin system
3. Data Layer
AttributeInfo (Value Object)
AttributeInfo
├── name: string (attribute FQCN)
├── target: AttributeTarget
├── instance: object (attribute instance)
├── reflectionTarget: Reflector
├── fileTime: int (file modification timestamp)
└── methods: getArguments(), getClassName(), etc.
AttributeTarget (Value Object)
AttributeTarget
├── type: AttributeTargetType (enum)
├── className: string
├── name: string (method/property/constant/parameter name)
└── filePath: string
AttributeCollection
- Immutable collection of AttributeInfo objects
- Fluent filtering API (byAttribute, byClass, byTargetType)
- Chainable query methods
- Implements Countable and IteratorAggregate
4. Supporting Components
AttributeTargetType (Enum)
enum AttributeTargetType: string {
case CLASS = 'class';
case METHOD = 'method';
case PROPERTY = 'property';
case PARAMETER = 'parameter';
case CLASS_CONSTANT = 'class_constant';
}Events
AttributeRegistry.beforeDiscover: Triggered before attribute discoveryAttributeRegistry.afterDiscover: Triggered after discovery with resultsAttributeRegistry.cacheWarmed: Triggered after cache compilationAttributeRegistry.cacheCleared: Triggered after cache invalidation
Configuration
Framework configuration namespace AttributeRegistry:
[
'cache' => [
'enabled' => true, // Enable compiled caching
'validateFiles' => false, // Validate files on load
'path' => null, // Cache directory path
],
'scanner' => [
'paths' => ['src/**/*.php'], // Glob patterns to scan
'exclude_paths' => [ // Patterns to exclude
'vendor/**',
'tmp/**'
],
],
]Dependency Injection
Services registered via ServiceProvider pattern:
AttributeRegistry::class- Singleton instance- CLI commands registered in console command collection
CLI Integration
Console commands for cache management and inspection:
cake attributes cache # Warm attribute cache
cake attributes list # List all discovered attributes
cake attributes inspect <name> # Inspect specific attributeDevelopment Workflow
The attribute registry supports multiple operational modes to balance performance and developer experience based on environment needs.
Cache Modes
Production Mode (cache = true, validateFiles = false)
'cache' => [
'enabled' => true,
'validateFiles' => false,
]- Attributes discovered on first request and compiled to cache file
- Subsequent requests load from compiled cache instantly
- Cache persists until explicitly cleared via CLI or programmatically
- Optimal performance with zero overhead after initial discovery
- Requires manual cache refresh:
bin/cake attributes cache
Development Mode (cache = true, validateFiles = true)
'cache' => [
'enabled' => true,
'validateFiles' => true, // Recommended for development
]- Validates cache freshness by comparing file modification times (
filemtime) - Automatically refreshes cache when attributes change in existing files
- Detects modifications without manual intervention
- Limitation: New files with attributes require manual cache refresh
- Balanced approach: automatic refresh for edits, minimal performance impact
No Cache Mode (cache = false)
'cache' => [
'enabled' => false,
]- Performs full attribute discovery on every request
- No caching overhead or stale data concerns
- Automatically detects all changes including new files
- Viable for small applications or development environments with fast disk I/O
- Performance impact: ~50-100ms per request depending on codebase size
Recommended Configuration by Environment
// config/app.php or config/attribute_registry.php
return [
'AttributeRegistry' => [
'cache' => [
'enabled' => env('CACHE_ATTRIBUTES', true),
'validateFiles' => env('DEBUG', false), // Validate in debug mode
'path' => CACHE . 'attributes' . DS,
],
],
];This configuration automatically:
- Enables validation during development (
DEBUG = true) - Disables validation in production for maximum performance
- Allows environment-specific overrides via environment variables
Workflow Examples
Adding Attributes to Existing Files (Development Mode)
// Edit existing controller method
class UsersController extends AppController
{
#[Route('/users', methods: ['GET'])] // Added or modified attribute
public function index()
{
// ...
}
}- With
validateFiles = true: Automatically detected on next request - With
validateFiles = false: Runbin/cake attributes cacheto refresh
Adding New Files with Attributes
// Create new controller file
class PostsController extends AppController
{
#[Route('/posts')]
public function index() {}
}- All cache modes: Requires manual refresh
bin/cake attributes cache - Cache validation only detects changes to existing cached files, not new files
Development Best Practices
- Use
validateFiles = trueduring active development - Run
bin/cake attributes cacheafter adding new files with attributes - Disable validation in CI/production environments
- Consider
cache = falsefor small projects or during heavy attribute development
Usage Patterns
Basic Discovery
$registry = AttributeRegistry::getInstance();
$attributes = $registry->discover();Filtered Queries
// Find all route attributes
$routes = $registry->findByAttribute(Route::class);
// Find all attributes on a specific class
$controllerAttrs = $registry->findByClass(UsersController::class);
// Find all method attributes
$methodAttrs = $registry->findByTargetType(AttributeTargetType::METHOD);Fluent Filtering
$registry->discover()
->byAttribute(Route::class)
->byTargetType(AttributeTargetType::METHOD)Dependency Injection
class MyService {
public function __construct(
private AttributeRegistry $registry
) {}
}Integration Points
Potential Core Applications
-
Routing System
- Attribute-based route definitions on controller methods
- Automatic route discovery and registration
- Middleware and prefix configuration via attributes
-
Validation
- Attribute-driven validation rules on entity properties
- Custom validator attributes
- Nested validation configuration
-
ORM/Table
- Column metadata and associations via attributes
- Behavior configuration
- Finder and query scope definitions
-
Events
- Event listener registration via attributes
- Priority and condition configuration
-
Dependency Injection
- Service configuration and autowiring hints
- Factory method designation
Performance Considerations
Cache Strategy:
- Compiled PHP cache for optimal deserialization speed
- File modification time validation for automatic cache refresh in development
- Three operational modes: production (static cache), development (validated cache), no cache (discovery per request)
- Lazy loading - attributes discovered only when needed
- Cache invalidation via CLI, programmatically, or automatically when
validateFiles = true
Memory Usage:
- AttributeInfo objects are lightweight value objects
- Collection uses generators where possible
- No memory overhead when cache disabled
Benchmark Data (Reference Implementation):
- Cache warm: ~100-200ms for medium application
- Cache load: <5ms with serialized cache
- Discovery without cache: ~50-100ms per request
Compiled Cache Format
The cache is stored as a compiled PHP file returning an array of AttributeInfo value objects. This format provides optimal performance through PHP's native opcache:
<?php
// phpcs:ignoreFile
/**
* Pre-compiled Attribute Registry Cache
*
* Generated: 2025-12-25 11:38:03
* Attributes: 16
*
* DO NOT EDIT THIS FILE MANUALLY
* Regenerate with: bin/cake attributes cache
*/
declare(strict_types=1);
return [
new \Cake\Core\AttributeRegistry\ValueObject\AttributeInfo(
className: 'App\\Controller\\UsersController',
attributeName: 'Cake\\Routing\\Attribute\\Route',
arguments: [
'path' => '/users',
'methods' => ['GET'],
],
filePath: '/var/www/html/src/Controller/UsersController.php',
lineNumber: 19,
target: new \Cake\Core\AttributeRegistry\ValueObject\AttributeTarget(
type: \Cake\Core\AttributeRegistry\Enum\AttributeTargetType::METHOD,
targetName: 'index',
parentClass: 'UsersController',
),
fileTime: 1735125483,
pluginName: null,
),
// ... additional entries
];Benefits:
- Near-instant loading via PHP opcache
- Type-safe with full object instantiation
- File modification time validation for cache invalidation (using native
filemtime) - Plugin-aware with source tracking
Backward Compatibility
This is a new feature with no breaking changes:
- Opt-in functionality requiring explicit usage
- No modifications to existing core components
- Compatible with PHP 8.2+ requirement
- Does not affect applications not using attributes
Security Considerations
- No code execution during attribute discovery (reflection only)
- Cache files use PHP serialization format with integrity checks
- File paths validated and normalized
- No external input processed during discovery
- Cache directory should be outside webroot
Testing Strategy
- Unit tests for all service components
- Integration tests for discovery and caching
- Performance benchmarks for cache operations
- Plugin integration testing
- Edge cases: malformed attributes, missing classes, circular references
Next Steps
Before submitting a formal pull request to CakePHP core based on my plugin, this RFC seeks community feedback and discussion on:
- Architecture and design decisions
- Integration approach with existing core components
- Naming conventions and API design
- Potential use cases and adoption patterns
- Migration strategy and backward compatibility concerns
Community input is essential to ensure this functionality aligns with CakePHP's philosophy and meets the needs of the broader ecosystem.
Conclusion
Integrating an attribute registry into CakePHP core provides a foundation for modern, declarative programming patterns. The proposed architecture offers a clean separation of concerns, high performance through caching, and extensibility for future attribute-driven features. The reference implementation demonstrates production-readiness with testing and documentation.
CakePHP Version
6.0
RFC changelog
- 01-08-26 Added Development Workflow
- 01-08-26 Changed
fileHash=>fileTime