This document explains the intended use of the three core folders introduced:
src/Domainsrc/Applicationsrc/Infrastructure
This structure supports a clean separation of concerns while still working with Cycle ORM and Psalm.
This architecture is a layered architecture with DDD-inspired boundaries:
- Domain = Business rules
- Application = Use cases
- Infrastructure = External systems (DB, ORM, APIs)
The Domain layer contains the core business logic of the system.
It is:
- framework-independent
- ORM-independent
- persistence-ignorant
Client
Invoice
ProductClientId
Money
EmailClient cannot have empty name
Invoice total must be >= 0InvoiceCalculator
PricingService- ❌ NO Cycle ORM annotations
- ❌ NO database logic
- ❌ NO HTTP logic
- ✔ Pure PHP logic only
The Application layer contains use cases (business actions).
It coordinates domain objects but does NOT contain business rules itself.
CreateClient
DeleteInvoice
UpdateClientAddress- calls domain objects
- calls repositories via interfaces
- manages workflow
final class DeleteClient
{
public function __invoke(int $clientId): void
{
$client = $this->clientRepository->get($clientId);
$client->markAsDeleted();
$this->clientRepository->save($client);
}
}- ✔ Can depend on Domain
- ✔ Can use repository interfaces
- ❌ No ORM logic
- ❌ No SQL / DB code
The Infrastructure layer contains all external implementations.
This is where Cycle ORM lives.
App\Infrastructure\Persistence\Cycle\Entity\ClientRecordClientRepository (Cycle-based)
InvoiceRepository#[Entity]
#[Column]- email providers
- payment gateways
- file storage
- ✔ Can depend on external libraries
- ✔ Can depend on Cycle ORM
- ❌ No business rules
- ❌ No domain logic
Application Layer (Use Case)
↓
Domain Layer (Business logic)
↓
Infrastructure Layer (Cycle ORM / DB)
Cycle ORM entities live in:
Infrastructure Layer ONLY
They are NOT domain entities.
Instead:
- Cycle entity = persistence model
- Domain entity = business model
Mapping happens between them.
DeleteClient::execute($clientId);Client::markAsDeleted();ClientRecord (Cycle ORM entity)
ClientRepository persists changesIt must never depend on anything else.
It tells the system WHAT to do.
It handles HOW things are stored or communicated.
- Clear separation of responsibilities
- Easier testing
- Reduced coupling to Cycle ORM
- Psalm-friendly type boundaries
- Gradual migration to DDD possible
This is a gradual architecture, not a full rewrite.
You can:
- keep current Cycle entities initially
- migrate slowly into Domain models
- evolve boundaries over time
| Layer | Responsibility |
|---|---|
| Domain | Business rules |
| Application | Use cases |
| Infrastructure | Cycle ORM + external systems |
This structure enables a controlled evolution from ORM-driven design to domain-driven design without a full rewrite.