Files
michaelschiemer/docs/DefaultImplementation-Attribute.md
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- 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.
2025-10-25 19:18:37 +02:00

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)