Ports & Adapters (PHP Worked Example)
Also Known As
hexagonal architecture PHP
ports and adapters
Alistair Cockburn
TL;DR
Hexagonal Architecture in PHP — the application core exposes ports (interfaces), and adapters implement them for HTTP, CLI, databases, and external services.
Explanation
Ports & Adapters (Alistair Cockburn, 2005) defines the application as a hexagon with ports on each face. Input ports are interfaces the application offers (CreateOrderUseCase, FindUserQuery). Output ports are interfaces the application requires (OrderRepository, EmailGateway). Adapters implement ports for specific technologies: HttpAdapter drives input ports; DoctrineAdapter implements output ports. The core never depends on adapters — the dependency always points inward. This enables testing the core without any infrastructure, and swapping adapters without touching the core.
Common Misconception
✗ Ports & Adapters requires a complex folder structure — the pattern is about dependency direction, not folder layout; a simple PHP project can implement it with just interfaces and implementations in any structure.
Why It Matters
A PHP application where controllers call Eloquent models directly cannot be tested without a database — ports & adapters makes the entire application core testable with pure PHP, no infrastructure needed.
Common Mistakes
- Output port interface in the infrastructure layer — interfaces belong to the application core.
- Application core importing Doctrine or Eloquent classes — the core must not know about adapters.
- One port per use case — keep ports focused; CreateOrderPort not a generic OrderPort with 20 methods.
- Not testing through ports — tests should call use cases via ports, not internal methods.
Code Examples
✗ Vulnerable
// Core directly depends on infrastructure — tightly coupled:
class CreateOrderUseCase {
public function __construct(
private \Doctrine\ORM\EntityManager $em, // Infrastructure in core!
private \Swift_Mailer $mailer, // Infrastructure in core!
) {}
// Cannot test without Doctrine and Swift
}
✓ Fixed
// Core depends only on own interfaces (ports):
interface OrderRepository { // Output port — in core
public function save(Order $order): void;
}
interface EmailGateway { // Output port — in core
public function sendConfirmation(Order $order): void;
}
class CreateOrderUseCase { // Core — no infrastructure imports
public function __construct(
private OrderRepository $orders, // Port — not Doctrine
private EmailGateway $email, // Port — not SwiftMailer
) {}
}
// Adapters in infrastructure layer:
class DoctrineOrderRepository implements OrderRepository { /* ... */ }
class SwiftEmailGateway implements EmailGateway { /* ... */ }
// Tests inject InMemoryOrderRepository — no DB needed
Tags
🤝 Adopt this term
£79/year · your link shown here
Added
16 Mar 2026
Edited
22 Mar 2026
Views
32
🤖 AI Guestbook educational data only
|
|
Last 30 days
Agents 1
No pings yesterday
Perplexity 8
Amazonbot 7
Google 6
Ahrefs 2
Unknown AI 2
SEMrush 2
Also referenced
How they use it
crawler 25
crawler_json 2
Related categories
⚡
DEV INTEL
Tools & Severity
🔵 Info
⚙ Fix effort: High
⚡ Quick Fix
Define ports (interfaces) in your domain/application layer — adapters implement them in the infrastructure layer; the domain never imports from infrastructure, only infrastructure imports from domain
📦 Applies To
PHP 7.0+
web
cli
queue-worker
🔗 Prerequisites
🔍 Detection Hints
Domain service importing Doctrine or Guzzle directly; port interface defined in infrastructure namespace not domain; no adapter isolation from domain
Auto-detectable:
✓ Yes
phpstan
deptrac
⚠ Related Problems
🤖 AI Agent
Confidence: Low
False Positives: High
✗ Manual fix
Fix: High
Context: File
Tests: Update