PHP - Hexagonal (Ports and Adapters) Architecture in PHP
Hexagonal Architecture, also known as Ports and Adapters Architecture, is a software design pattern introduced by Alistair Cockburn. It is used to build applications where the core business logic remains independent of external systems such as databases, user interfaces, APIs, messaging services, and third-party libraries. This architecture improves maintainability, flexibility, and testability by ensuring that changes in one part of the system do not affect the business logic.
The name "Hexagonal" comes from the way the architecture is commonly illustrated, with a hexagon representing the application's core. The six sides symbolize that the application can communicate with multiple external systems through well-defined interfaces rather than depending directly on them.
Why Hexagonal Architecture is Needed
In traditional PHP applications, business logic is often tightly coupled with the database, user interface, or framework. For example, a user registration function may directly interact with MySQL, send emails, and return HTML responses within the same class. This makes the code difficult to maintain because changing one component often requires modifications throughout the application.
Hexagonal Architecture solves this problem by separating business rules from infrastructure. The application communicates with external systems only through interfaces known as ports, while adapters implement these interfaces for specific technologies.
Core Components of Hexagonal Architecture
1. Domain Layer
The Domain Layer contains the core business rules of the application. It includes entities, value objects, business services, and domain logic. This layer does not know anything about databases, web frameworks, or external APIs.
Example:
class Product
{
private string $name;
private float $price;
public function __construct(string $name, float $price)
{
$this->name = $name;
$this->price = $price;
}
public function getPrice(): float
{
return $this->price;
}
}
The Product class represents business data without depending on any database or framework.
2. Ports
Ports are interfaces that define how the application communicates with external systems. They specify what actions can be performed but not how they are implemented.
Example:
interface ProductRepository
{
public function save(Product $product): void;
public function findById(int $id): ?Product;
}
This interface tells the application that products can be saved and retrieved without specifying whether the data comes from MySQL, PostgreSQL, MongoDB, or another storage system.
3. Adapters
Adapters provide concrete implementations of the ports. They connect the application to external technologies.
Example:
class MySQLProductRepository implements ProductRepository
{
public function save(Product $product): void
{
echo "Saving product to MySQL";
}
public function findById(int $id): ?Product
{
return new Product("Laptop", 65000);
}
}
Later, the application can switch to another database without changing the business logic.
class MongoProductRepository implements ProductRepository
{
public function save(Product $product): void
{
echo "Saving product to MongoDB";
}
public function findById(int $id): ?Product
{
return new Product("Laptop", 65000);
}
}
Only the adapter changes, while the rest of the application remains unchanged.
4. Application Layer
The Application Layer coordinates business operations. It uses ports to perform tasks while remaining independent of implementation details.
Example:
class ProductService
{
private ProductRepository $repository;
public function __construct(ProductRepository $repository)
{
$this->repository = $repository;
}
public function createProduct(string $name, float $price)
{
$product = new Product($name, $price);
$this->repository->save($product);
}
}
The service communicates only with the ProductRepository interface and is unaware of which database is being used.
5. Driving Adapters
Driving adapters initiate interactions with the application. These include:
-
Web controllers
-
REST API controllers
-
Command-line interfaces
-
GraphQL endpoints
-
Unit tests
Example:
$repository = new MySQLProductRepository();
$service = new ProductService($repository);
$service->createProduct("Mobile", 25000);
The controller creates the required objects and starts the business operation.
6. Driven Adapters
Driven adapters are external systems used by the application, such as:
-
Databases
-
Email services
-
Payment gateways
-
File storage
-
Cache servers
-
Message queues
These adapters implement the ports defined by the application.
Project Structure
A typical PHP project using Hexagonal Architecture may look like this:
project/
├── Domain/
│ ├── Entity/
│ ├── Repository/
│ └── Service/
│
├── Application/
│ ├── UseCase/
│ └── DTO/
│
├── Infrastructure/
│ ├── Database/
│ ├── Email/
│ ├── API/
│ └── Cache/
│
├── Presentation/
│ ├── Controller/
│ ├── CLI/
│ └── Views/
│
└── index.php
Each folder has a specific responsibility, making the application organized and easier to maintain.
Request Flow
The typical flow of a request is:
User
↓
Controller
↓
Application Service
↓
Port Interface
↓
Adapter
↓
Database
When data is returned, the flow reverses:
Database
↓
Adapter
↓
Application Service
↓
Controller
↓
User
This separation ensures that business logic never communicates directly with infrastructure components.
Advantages of Hexagonal Architecture
Improved Maintainability
Business logic is isolated from technical details, making the code easier to update and maintain.
Better Testability
Since business logic depends on interfaces rather than concrete implementations, mock objects can be used for unit testing without connecting to a real database or external service.
Easy Technology Replacement
Changing from MySQL to PostgreSQL, MongoDB, or another storage system requires only a new adapter. The domain and application layers remain unchanged.
Framework Independence
The architecture does not rely on a specific PHP framework. The same business logic can be used with Laravel, Symfony, CodeIgniter, Slim, or even a custom PHP application.
Code Reusability
The domain layer can be reused across multiple projects because it has no dependency on infrastructure.
Scalability
New adapters can be added without modifying existing business logic. For example, support for a REST API, GraphQL API, or command-line interface can be introduced while keeping the core application unchanged.
Disadvantages of Hexagonal Architecture
-
It introduces additional interfaces and classes, increasing the number of files in a project.
-
It can feel overly complex for small or simple applications.
-
Developers need a solid understanding of design patterns, dependency injection, and interface-based programming.
-
Initial development may take longer due to the architectural setup.
Real-World Applications
Hexagonal Architecture is widely used in applications that require long-term maintainability and flexibility, including:
-
Enterprise Resource Planning (ERP) systems
-
Banking and financial software
-
E-commerce platforms
-
Hospital management systems
-
Customer Relationship Management (CRM) systems
-
Inventory and warehouse management systems
-
SaaS applications
-
Large-scale RESTful APIs
-
Microservices-based systems
Best Practices
-
Keep the domain layer free from framework-specific code.
-
Define ports using interfaces to abstract external dependencies.
-
Place all infrastructure-specific code in adapters.
-
Use dependency injection to provide adapter implementations to the application.
-
Ensure that business rules never directly access databases or external APIs.
-
Write unit tests against the domain and application layers using mock implementations of ports.
-
Organize the project into clear layers with distinct responsibilities.
Conclusion
Hexagonal Architecture is a powerful architectural pattern for building scalable, maintainable, and testable PHP applications. By separating business logic from infrastructure through ports and adapters, developers can easily replace technologies, support multiple interfaces, and keep the core application independent of databases, frameworks, and external services. Although it introduces additional structure and complexity, it is highly beneficial for medium to large projects where flexibility, long-term maintenance, and clean code are important.