Repository Implementations
Phexium provides four repository implementations for each aggregate, allowing database switching without changing application code.
Available Implementations
| Implementation | Use Case | Persistence |
|---|---|---|
InMemory | Testing, prototyping | In-memory array |
Sqlite | Development, small apps | SQLite file |
Mysql | Production | MySQL server |
Postgresql | Production | PostgreSQL server |
Usage
All implementations implement the same domain interface:
interface BookRepository
{
public function findById(IdInterface $id): ?Book;
public function save(Book $book): void;
public function findAll(): BooksCollection;
}
InMemory Implementation
Fast testing without database dependencies:
final readonly class InMemoryBookRepository implements BookRepository
{
public function findById(IdInterface $id): ?Book
{
$row = $this->driver->findById(self::TABLE, $id);
return $row !== null ? $this->rowToBook($row) : null;
}
public function save(Book $book): void
{
$this->driver->save(self::TABLE, $this->bookToRow($book));
}
}
Switching Implementations
Configure via DI container:
BookRepository::class => match ($_ENV['DATABASE_TYPE']) {
'InMemory' => DI\get(InMemoryBookRepository::class),
'Sqlite' => DI\get(SqliteBookRepository::class),
'Mysql' => DI\get(MysqlBookRepository::class),
'Postgresql' => DI\get(PostgresqlBookRepository::class),
},
Database Initialization
task dev:database:init # SQLite development
task tests:acceptance:mysql # MySQL for tests
task tests:acceptance:postgresql # PostgreSQL for tests
Mapping Traits
Repository implementations share data mapping logic through traits. Each entity has a dedicated mapping trait that converts between domain objects and database rows.
Structure
trait BookRepositoryMappingTrait
{
protected function rowToBook(array $row): Book
{
return new Book(
$this->idGenerator->from($row['id']),
Title::fromString($row['title']),
Author::fromString($row['author']),
ISBN::fromString($row['isbn']),
BookStatus::from($row['status'])
);
}
protected function bookToRow(Book $book): array
{
return [
'id' => $book->getId()->getValue(),
'title' => $book->getTitle()->getValue(),
'author' => $book->getAuthor()->getValue(),
'isbn' => $book->getIsbn()->getValue(),
'status' => $book->getStatus()->value,
];
}
}
Usage in Repositories
All four implementations use the same trait:
final readonly class InMemoryBookRepository implements BookRepository
{
use BookRepositoryMappingTrait;
// ...
}
final readonly class SqliteBookRepository implements BookRepository
{
use BookRepositoryMappingTrait;
// ...
}
This approach ensures consistent entity reconstruction across all implementations and avoids code duplication.
Available Traits
BookRepositoryMappingTrait- Book entity mappingUserRepositoryMappingTrait- User entity mappingLoanRepositoryMappingTrait- Loan entity mapping
Source Files
app/demo/Library/Infrastructure/Trait/BookRepositoryMappingTrait.phpapp/demo/User/Infrastructure/Trait/UserRepositoryMappingTrait.phpapp/demo/Loan/Infrastructure/Trait/LoanRepositoryMappingTrait.php
Best Practices
- Interface in Domain, implementation in Infrastructure
- Use InMemory for unit tests (fast, no database)
- Use real implementations for integration tests
- Keep mapping logic in traits for reuse across implementations
See Also
- Repository Interfaces - Domain layer port definitions
- Port & Adapter Pattern - Repositories as adapters
- SQL Driver - Database driver plugin
- Specifications - Query criteria pattern
- Integration Testing - Repository testing patterns
- Unit Testing - InMemory for fast tests