Hexagonal Architecture in PHP: Clean Code Without Pain

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Your Code is a Fortress, Not Spaghetti

\n\n

Have you ever rewritten a project from Laravel to Symfony or from MySQL to PostgreSQL? If so, you know the pain: controllers soaked in SQL queries, models that know everything about HTTP requests, and business logic that dies when you change the framework. Sound familiar?

\n\n

Welcome to the world of Hexagonal Architecture in PHP (also known as Ports & Adapters). It's not a trendy buzzword, but a lifeline for projects that want to live long and prosper. In this article, we'll break down how to build a PHP application that isn't afraid of change: switching databases, moving to a different cache, or a new API — all of this becomes a routine adapter swap, not a rewrite of half the code.

\n\n

What is Hexagonal Architecture Really?

\n\n

Imagine your application is a core that doesn't know where data comes from: the web, a console, a Telegram bot, or tests. Around this core are ports (interfaces), and on the outside are adapters (implementations). Hexagonal architecture in PHP allows you to separate business logic from infrastructure code. You write the rules once and use them everywhere.

\n\n

Key principles:

\n
    \n
  • Dependency Inversion — high-level code does not depend on low-level code. It's the other way around.
  • \n
  • Business Logic is Isolated — no framework penetrates your Use Cases.
  • \n
  • Testability — you can test the core without a database or HTTP.
  • \n
\n\n

Unlike Robert Martin's Clean Architecture, Hexagonal is more flexible: it doesn't dictate the number of layers, but simply says: "Inside is the domain, outside is infrastructure, connect them through interfaces."

\n\n

Project Structure: From Chaos to Order

\n\n

Let's get straight to the point. Here is a typical folder structure for Hexagonal architecture in PHP:

\n\n
src/\n├── Domain/\n│   ├── Entity/\n│   │   └── Order.php\n│   ├── Repository/\n│   │   └── OrderRepositoryInterface.php\n│   └── Service/\n│       └── OrderService.php\n├── Application/\n│   ├── UseCase/\n│   │   └── CreateOrderUseCase.php\n│   └── Port/\n│       ├── Input/\n│       │   └── CreateOrderInputPort.php\n│       └── Output/\n│           └── OrderOutputPort.php\n├── Infrastructure/\n│   ├── Persistence/\n│   │   └── DoctrineOrderRepository.php\n│   └── Http/\n│       └── OrderController.php\n└── Framework/\n    └── Symfony/\n        └── Kernel.php
\n\n

Notice? Domain and Application know nothing about Doctrine, Symfony, or MySQL. They only work with interfaces. Adapters live in Infrastructure and are connected via Dependency Injection.

\n\n

Step 1: Define the Core — Domain

\n\n

Let's start with an order entity. No ORM annotations, no setters that break invariants:

\n\n
<?php\n// src/Domain/Entity/Order.php\nnamespace App\\Domain\\Entity;\n\nclass Order\n{\n    public function __construct(\n        private readonly string $id,\n        private readonly string $productName,\n        private float $totalAmount,\n        private string $status = 'pending'\n    ) {}\n\n    public function approve(): void\n    {\n        if ($this->status !== 'pending') {\n            throw new \\DomainException('Only pending orders can be approved.');\n        }\n        $this->status = 'approved';\n    }\n\n    public function getId(): string { return $this->id; }\n    public function getProductName(): string { return $this->productName; }\n    public function getTotalAmount(): float { return $this->totalAmount; }\n    public function getStatus(): string { return $this->status; }\n}
\n\n

And here is the port (interface) for the repository. Note: it knows nothing about Doctrine or PDO:

\n\n
<?php\n// src/Domain/Repository/OrderRepositoryInterface.php\nnamespace App\\Domain\\Repository;\n\nuse App\\                

Blogs

Book Recommendations