Port & Adapter Pattern
The Plugin system implements the Port & Adapter pattern (Hexagonal Architecture). This pattern decouples the framework core from external dependencies through clear contracts (Ports) and swappable implementations (Adapters).
Core Concept
Ports are interfaces defining contracts without implementation details. They represent what the framework needs.
Adapters are concrete implementations of Ports. Multiple adapters can implement the same port, providing different technical solutions.
// Port (interface)
interface LoggerInterface
{
public function info(string $message, array $context = []): void;
}
// Adapters (implementations)
final class FileLogger implements LoggerInterface { /* ... */ }
final class NullLogger implements LoggerInterface { /* ... */ }
Benefits
Flexibility
Replace implementations without changing business logic:
Testability
Use test-friendly implementations:
// Fast, deterministic tests
ClockInterface::class => DI\autowire(FrozenClock::class),
TransactionInterface::class => DI\autowire(InMemoryTransaction::class),
Isolation
Business logic depends on interfaces, not concrete implementations:
public function __construct(
private BookRepositoryInterface $bookRepository, // Port
private EventBusInterface $eventBus, // Port
) {}
Available Plugins
See Plugins for the complete list of available plugins with their ports and adapters.
Creating Custom Plugins
- Define Port interface in
src/Plugin/{Name}/Port/ - Implement Adapter in
src/Plugin/{Name}/Adapter/ - (Optional) Add internal utilities in
src/Plugin/{Name}/Internal/ - Register binding in
config/{app}/container.php - Application code uses only the interface
Port Types
Driving Ports (Primary)
Called by external actors to trigger application behavior:
- Controllers calling
CommandBusInterface - API endpoints calling
QueryBusInterface - CLI commands calling application services
Driven Ports (Secondary)
Called by the application to interact with external systems:
BookRepositoryInterface→ Database accessLoggerInterface→ Logging systemSessionInterface→ Session storageEventBusInterface→ Event publishing
Plugin Categories
Plugins fall into two categories based on how they're used:
Self-Contained Plugins
Provide complete, ready-to-use adapters. Applications select the appropriate adapter via DI container configuration.
| Plugin | Port | Adapters |
|---|---|---|
| Logger | LoggerInterface | MonologLogger, NullLogger |
| Session | SessionInterface | OdanSession |
| Transaction | TransactionInterface | PdoTransaction, InMemoryTransaction |
| IdGenerator | IdGeneratorInterface | TimestampIdGenerator, UuidGenerator |
| Clock | ClockInterface | SystemClock, FrozenClock |
| PasswordHasher | PasswordHasherInterface | BcryptPasswordHasher |
| Authorization | AuthorizationInterface | RbacAuthorization |
| CommandBus | CommandBusInterface | SyncCommandBus, TransactionalCommandBus |
| QueryBus | QueryBusInterface | SyncQueryBus |
| EventBus | EventBusInterface | SyncEventBus |
| SqlDriver | SqlDriverInterface | SqliteDriver, MysqlDriver, PostgresqlDriver |
Extension Plugins
Provide abstract base classes that applications extend per-entity. Concrete adapters live in the application's Infrastructure layer.
| Plugin | Abstract Base | Application Creates |
|---|---|---|
| Repository | AbstractInMemoryRepository, AbstractSqlRepository | InMemoryBookRepository, SqliteBookRepository |
Example: Repository Extension
// Application extends the abstract base
final class InMemoryBookRepository extends AbstractInMemoryRepository implements BookRepositoryInterface
{
// Inherits save(), findById(), delete() behavior
// Add entity-specific methods
}
Internal/ Directory
Some plugins contain an Internal/ directory with:
- Abstract base classes for adapter implementation
- Utility classes supporting the Port/Adapter contract
- Conventions and helpers
Important: Classes in Internal/ are implementation details. Application code should not import them directly—use only the public Port interfaces.
src/Plugin/Repository/
├── Port/
│ └── RepositoryInterface.php ← Import this
├── Adapter/
│ └── InMemory/, Sqlite/, ...
└── Internal/
└── AbstractInMemoryRepository.php ← Extend, don't import directly
See Also
- Repository Implementations - Extension plugin example