- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
448 lines
13 KiB
Markdown
448 lines
13 KiB
Markdown
# DefaultImplementation Attribute
|
|
|
|
Automatic DI Container registration for interface implementations using attributes.
|
|
|
|
## Overview
|
|
|
|
The `#[DefaultImplementation]` attribute enables automatic registration of classes as default implementations for interfaces in the DI container. This eliminates boilerplate container configuration while maintaining framework patterns (readonly, final, composition).
|
|
|
|
## Features
|
|
|
|
- **Optional Interface Parameter**: Specify interface explicitly or auto-detect
|
|
- **Auto-Detection**: Automatically uses first implemented interface if not specified
|
|
- **Validation**: Ensures class actually implements the specified/detected interface
|
|
- **Singleton Registration**: All bindings are registered as singletons
|
|
- **Framework Integration**: Fully integrated with Discovery system and caching
|
|
- **Type Safety**: Full type safety with descriptive validation exceptions
|
|
|
|
## Usage
|
|
|
|
### Explicit Interface Specification
|
|
|
|
```php
|
|
interface UserRepository
|
|
{
|
|
public function findById(string $id): ?User;
|
|
}
|
|
|
|
#[DefaultImplementation(UserRepository::class)]
|
|
final readonly class DatabaseUserRepository implements UserRepository
|
|
{
|
|
public function __construct(
|
|
private readonly DatabaseConnection $db
|
|
) {}
|
|
|
|
public function findById(string $id): ?User
|
|
{
|
|
// Implementation
|
|
}
|
|
}
|
|
|
|
// Container automatically binds UserRepository::class => DatabaseUserRepository::class
|
|
$userRepo = $container->get(UserRepository::class); // DatabaseUserRepository instance
|
|
```
|
|
|
|
### Auto-Detection (Single Interface)
|
|
|
|
```php
|
|
interface LoggerInterface
|
|
{
|
|
public function log(string $message): void;
|
|
}
|
|
|
|
#[DefaultImplementation] // No explicit interface - auto-detects LoggerInterface
|
|
final readonly class FileLogger implements LoggerInterface
|
|
{
|
|
public function log(string $message): void
|
|
{
|
|
// Implementation
|
|
}
|
|
}
|
|
|
|
// Container automatically binds LoggerInterface::class => FileLogger::class
|
|
$logger = $container->get(LoggerInterface::class); // FileLogger instance
|
|
```
|
|
|
|
### Auto-Detection (Multiple Interfaces)
|
|
|
|
When a class implements multiple interfaces, the **first interface** is used for auto-detection:
|
|
|
|
```php
|
|
#[DefaultImplementation] // Auto-detects LoggerInterface (first interface)
|
|
final readonly class AppLogger implements LoggerInterface, Stringable
|
|
{
|
|
public function log(string $message): void
|
|
{
|
|
echo "[LOG] $message\n";
|
|
}
|
|
|
|
public function __toString(): string
|
|
{
|
|
return 'AppLogger';
|
|
}
|
|
}
|
|
|
|
// Binds: LoggerInterface::class => AppLogger::class
|
|
```
|
|
|
|
## Validation
|
|
|
|
The attribute validates interface implementation at application bootstrap:
|
|
|
|
### Valid Usage
|
|
|
|
```php
|
|
// ✓ Explicit interface that class implements
|
|
#[DefaultImplementation(UserRepository::class)]
|
|
final readonly class DatabaseUserRepository implements UserRepository {}
|
|
|
|
// ✓ Auto-detect with single interface
|
|
#[DefaultImplementation]
|
|
final readonly class FileLogger implements LoggerInterface {}
|
|
|
|
// ✓ Auto-detect with multiple interfaces (uses first)
|
|
#[DefaultImplementation]
|
|
final readonly class AppLogger implements LoggerInterface, Stringable {}
|
|
```
|
|
|
|
### Invalid Usage (Throws Exceptions)
|
|
|
|
```php
|
|
// ✗ Explicit interface NOT implemented
|
|
#[DefaultImplementation(LoggerInterface::class)]
|
|
final readonly class BadRepository implements UserRepository {}
|
|
// Throws: DefaultImplementationException
|
|
// "Class 'BadRepository' has #[DefaultImplementation] for interface 'LoggerInterface'
|
|
// but does not implement that interface"
|
|
|
|
// ✗ Auto-detect with NO interfaces
|
|
#[DefaultImplementation]
|
|
final readonly class PlainClass {}
|
|
// Throws: DefaultImplementationException
|
|
// "Class 'PlainClass' has #[DefaultImplementation] without explicit interface but
|
|
// implements no interfaces. Either specify an interface explicitly or ensure the
|
|
// class implements at least one interface."
|
|
|
|
// ✗ Interface does not exist
|
|
#[DefaultImplementation(NonExistentInterface::class)]
|
|
final readonly class SomeClass implements ActualInterface {}
|
|
// Throws: DefaultImplementationException
|
|
// "Class 'SomeClass' has #[DefaultImplementation] for interface 'NonExistentInterface'
|
|
// which does not exist"
|
|
```
|
|
|
|
## How It Works
|
|
|
|
### 1. Discovery Phase
|
|
|
|
During application bootstrap, the Discovery system scans for `#[DefaultImplementation]` attributes:
|
|
|
|
```php
|
|
// Automatic discovery via UnifiedDiscoveryService
|
|
$discoveryService = $container->get(UnifiedDiscoveryService::class);
|
|
$results = $discoveryService->discover();
|
|
```
|
|
|
|
### 2. Processing Phase
|
|
|
|
The `DefaultImplementationProcessor` processes discovered attributes:
|
|
|
|
```php
|
|
// Automatically called in DiscoveryServiceBootstrapper
|
|
$processor = new DefaultImplementationProcessor($container);
|
|
$processor->process($results); // Registers all bindings
|
|
```
|
|
|
|
### 3. Container Registration
|
|
|
|
Each valid attribute creates a singleton binding:
|
|
|
|
```php
|
|
// For each DefaultImplementation attribute:
|
|
$container->singleton($interface, $className);
|
|
```
|
|
|
|
### 4. Resolution
|
|
|
|
Classes are instantiated on-demand by the container:
|
|
|
|
```php
|
|
$instance = $container->get(UserRepository::class);
|
|
// Returns: DatabaseUserRepository instance (singleton)
|
|
```
|
|
|
|
## Integration Points
|
|
|
|
### Discovery System
|
|
|
|
The attribute is automatically discovered via the framework's Discovery system:
|
|
|
|
1. **Attribute Scanning**: `UnifiedDiscoveryService` finds all `#[DefaultImplementation]` attributes
|
|
2. **Caching**: Discovered attributes are cached along with other discovery results
|
|
3. **Processor Execution**: `DefaultImplementationProcessor` runs after discovery
|
|
4. **Before Initializers**: Processed **before** `#[Initializer]` methods to ensure dependencies are available
|
|
|
|
### Bootstrap Flow
|
|
|
|
```
|
|
AppBootstrapper
|
|
└─> ContainerBootstrapper
|
|
└─> DiscoveryServiceBootstrapper
|
|
├─> UnifiedDiscoveryService::discover()
|
|
├─> DefaultImplementationProcessor::process() ← Registers bindings
|
|
└─> InitializerProcessor::processInitializers() ← Can depend on bindings
|
|
```
|
|
|
|
## Framework Compliance
|
|
|
|
The implementation follows all Custom PHP Framework principles:
|
|
|
|
✓ **No Inheritance**: `DefaultImplementationProcessor` is final and uses composition
|
|
✓ **Readonly Classes**: Attribute and processor are readonly
|
|
✓ **Value Objects**: Uses `ClassName`, `DiscoveredAttribute`, `DiscoveryRegistry`
|
|
✓ **Explicit DI**: Constructor injection throughout
|
|
✓ **Attribute-Driven**: Convention over configuration
|
|
✓ **Discovery Integration**: Automatic registration via framework's attribute system
|
|
|
|
## Testing
|
|
|
|
Comprehensive tests verify all scenarios:
|
|
|
|
```bash
|
|
# Run test suite
|
|
php tests/debug/test-default-implementation.php
|
|
```
|
|
|
|
**Test Coverage**:
|
|
- ✓ Explicit interface specification
|
|
- ✓ Auto-detect single interface
|
|
- ✓ Auto-detect multiple interfaces (uses first)
|
|
- ✓ Validation: interface not implemented
|
|
- ✓ Validation: no interfaces implemented
|
|
- ✓ Validation: interface does not exist
|
|
- ✓ Multiple bindings in one registry
|
|
- ✓ Singleton registration verification
|
|
|
|
## File Structure
|
|
|
|
```
|
|
src/Framework/DI/
|
|
├── Attributes/
|
|
│ └── DefaultImplementation.php # Attribute class
|
|
├── DefaultImplementationProcessor.php # Registration processor
|
|
└── Exceptions/
|
|
└── DefaultImplementationException.php # Validation exceptions
|
|
|
|
src/Framework/Discovery/
|
|
└── DiscoveryServiceBootstrapper.php # Integration point (modified)
|
|
|
|
tests/debug/
|
|
└── test-default-implementation.php # Comprehensive test suite
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### `#[DefaultImplementation]`
|
|
|
|
**Attribute**
|
|
|
|
```php
|
|
#[Attribute(Attribute::TARGET_CLASS)]
|
|
final readonly class DefaultImplementation
|
|
{
|
|
public function __construct(
|
|
public ?string $interface = null
|
|
) {}
|
|
}
|
|
```
|
|
|
|
**Parameters**:
|
|
- `$interface` (optional): Interface class-string. If `null`, auto-detect first interface.
|
|
|
|
**Target**: `CLASS`
|
|
|
|
### `DefaultImplementationProcessor`
|
|
|
|
**Processor Class**
|
|
|
|
```php
|
|
final readonly class DefaultImplementationProcessor
|
|
{
|
|
public function __construct(
|
|
private readonly Container $container
|
|
) {}
|
|
|
|
public function process(DiscoveryRegistry $registry): int;
|
|
}
|
|
```
|
|
|
|
**Methods**:
|
|
- `process(DiscoveryRegistry $registry): int` - Processes attributes and registers bindings. Returns count of registered bindings.
|
|
|
|
### `DefaultImplementationException`
|
|
|
|
**Exception Class**
|
|
|
|
```php
|
|
final class DefaultImplementationException extends RuntimeException
|
|
{
|
|
public static function doesNotImplementInterface(
|
|
string $className,
|
|
string $interface
|
|
): self;
|
|
|
|
public static function noInterfacesImplemented(
|
|
string $className
|
|
): self;
|
|
|
|
public static function interfaceDoesNotExist(
|
|
string $className,
|
|
string $interface
|
|
): self;
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### When to Use
|
|
|
|
✓ **Service Implementations**: Default implementations for repository, service interfaces
|
|
✓ **Infrastructure Adapters**: Database, cache, HTTP client implementations
|
|
✓ **Domain Services**: Business logic services implementing domain interfaces
|
|
✓ **Framework Extensions**: Custom implementations of framework interfaces
|
|
|
|
### When NOT to Use
|
|
|
|
✗ **Multiple Implementations**: When you need multiple implementations of same interface (use explicit binding)
|
|
✗ **Conditional Logic**: When implementation choice depends on runtime configuration (use Initializers)
|
|
✗ **Non-Singleton Scopes**: When you need per-request or transient instances (use factories)
|
|
|
|
### Combining with Initializers
|
|
|
|
For complex setup, combine with `#[Initializer]`:
|
|
|
|
```php
|
|
interface CacheService {}
|
|
|
|
#[DefaultImplementation(CacheService::class)]
|
|
final readonly class RedisCacheService implements CacheService
|
|
{
|
|
public function __construct(
|
|
private readonly RedisConnection $redis
|
|
) {}
|
|
}
|
|
|
|
final readonly class CacheServiceInitializer
|
|
{
|
|
#[Initializer]
|
|
public function initializeRedisConnection(): RedisConnection
|
|
{
|
|
// Complex Redis connection setup
|
|
return new RedisConnection(/* config */);
|
|
}
|
|
}
|
|
|
|
// DefaultImplementation registered first, then Initializer runs
|
|
// RedisCacheService can depend on RedisConnection from Initializer
|
|
```
|
|
|
|
## Performance
|
|
|
|
- **Discovery Overhead**: Negligible - scanned once during bootstrap and cached
|
|
- **Resolution Overhead**: Zero - standard container resolution (singleton pattern)
|
|
- **Memory Impact**: Minimal - only stores bindings in container registry
|
|
- **Cache Integration**: Full cache support - discovered once, cached for subsequent requests
|
|
|
|
## Troubleshooting
|
|
|
|
### "Class does not implement interface"
|
|
|
|
**Problem**: Class marked with explicit interface but doesn't actually implement it.
|
|
|
|
**Solution**: Either implement the interface or remove the explicit parameter to auto-detect.
|
|
|
|
### "Must implement at least one interface"
|
|
|
|
**Problem**: Class marked with `#[DefaultImplementation]` but implements no interfaces.
|
|
|
|
**Solution**: Either implement an interface or remove the attribute.
|
|
|
|
### "Interface does not exist"
|
|
|
|
**Problem**: Explicit interface parameter references non-existent interface.
|
|
|
|
**Solution**: Fix the interface class-string or ensure the interface is loaded.
|
|
|
|
### "Multiple implementations conflict"
|
|
|
|
**Problem**: Multiple classes have `#[DefaultImplementation]` for same interface.
|
|
|
|
**Behavior**: Last processed binding wins (discovery order dependent).
|
|
|
|
**Solution**: Remove attribute from all but one implementation, or use explicit container binding.
|
|
|
|
## Migration Guide
|
|
|
|
### From Manual Container Binding
|
|
|
|
**Before**:
|
|
```php
|
|
// In Initializer or bootstrap
|
|
$container->singleton(UserRepository::class, DatabaseUserRepository::class);
|
|
$container->singleton(LoggerInterface::class, FileLogger::class);
|
|
```
|
|
|
|
**After**:
|
|
```php
|
|
// Add attributes to implementation classes
|
|
#[DefaultImplementation(UserRepository::class)]
|
|
final readonly class DatabaseUserRepository implements UserRepository {}
|
|
|
|
#[DefaultImplementation(LoggerInterface::class)]
|
|
final readonly class FileLogger implements LoggerInterface {}
|
|
|
|
// Bindings happen automatically - remove manual registration
|
|
```
|
|
|
|
### From Service Provider Pattern
|
|
|
|
**Before**:
|
|
```php
|
|
final readonly class ServiceProvider
|
|
{
|
|
public function register(Container $container): void
|
|
{
|
|
$container->singleton(UserRepository::class, DatabaseUserRepository::class);
|
|
$container->singleton(LoggerInterface::class, FileLogger::class);
|
|
}
|
|
}
|
|
```
|
|
|
|
**After**:
|
|
```php
|
|
// Add attributes to implementation classes
|
|
#[DefaultImplementation(UserRepository::class)]
|
|
final readonly class DatabaseUserRepository implements UserRepository {}
|
|
|
|
#[DefaultImplementation] // Auto-detect
|
|
final readonly class FileLogger implements LoggerInterface {}
|
|
|
|
// Remove ServiceProvider - attribute handles registration
|
|
```
|
|
|
|
## Future Enhancements
|
|
|
|
Potential future improvements:
|
|
|
|
- **Conditional Registration**: `#[DefaultImplementation(when: 'production')]`
|
|
- **Priority System**: `#[DefaultImplementation(priority: 10)]` for multiple implementations
|
|
- **Scope Control**: `#[DefaultImplementation(scope: 'transient')]` for non-singleton instances
|
|
- **Tag System**: `#[DefaultImplementation(tags: ['database', 'read-only'])]` for grouped registration
|
|
|
|
## Related Documentation
|
|
|
|
- [Framework Architecture](architecture.md)
|
|
- [Dependency Injection](../Framework/DI/README.md)
|
|
- [Discovery System](../Framework/Discovery/README.md)
|
|
- [Attribute Patterns](naming-conventions.md#attributes)
|