Repository Interfaces
Repository Interfaces define contracts for persisting and retrieving aggregate roots. Interfaces belong to the Domain layer; implementations live in Infrastructure.
Characteristics
- Domain layer location: Interfaces are ports in Domain
- Aggregate root focus: One repository per aggregate root
- Collection semantics: Repositories act like in-memory collections
- Technology agnostic: Interface knows nothing about persistence
Defining Interfaces
interface BookRepository
{
public function findAll(): BooksCollection;
public function findBy(SpecificationInterface $spec, ?array $orderBy = null, ?int $offset = null, ?int $limit = null): BooksCollection;
public function findOneBy(SpecificationInterface $spec): ?Book;
public function findById(IdInterface $id): ?Book;
public function getById(IdInterface $id): Book;
public function exists(IdInterface $id): bool;
public function save(Book $book): void;
public function delete(Book $book): int;
public function deleteById(IdInterface $id): int;
}
Method Conventions
| Method | Return | Behavior |
|---|---|---|
findById($id) | ?Entity | Returns entity or null |
getById($id) | Entity | Returns entity or throws |
findAll() | Collection | Returns all entities |
findBy($spec, ...) | Collection | Returns matching entities (supports orderBy, offset, limit) |
findOneBy($spec) | ?Entity | Returns first matching entity or null |
save($entity) | void | Insert or update |
delete($entity) | int | Affected rows count |
deleteById($id) | int | Affected rows count |
Using Specifications
$specification = new UserIdSpecification($userId);
$loans = $loanRepository->findBy($specification);
// With pagination
$books = $bookRepository->findBy(
specification: new AlwaysTrueSpecification(),
orderBy: ['title' => 'ASC'],
limit: 10
);
Container Configuration
BookRepository::class => match ($_ENV['database.type'] ?? 'Sqlite') {
'InMemory' => DI\get(InMemoryBookRepository::class),
'Sqlite' => DI\get(SqliteBookRepository::class),
'Mysql' => DI\get(MysqlBookRepository::class),
'Postgresql' => DI\get(PostgresqlBookRepository::class),
},
Best Practices
- Interface in Domain, implementation in Infrastructure
- One repository per aggregate root
- Use Specifications for complex queries
- Return typed collections, not raw arrays
- Use InMemory for fast, isolated tests
See Also
- Repository Implementations - Concrete implementations
- Unit Testing - InMemory for fast tests