Практический пример: Гексагональная, Луковая и Чистая архитектуры на PHP
Эти архитектуры преследуют общую цель: изолировать бизнес-логику («Ядро» или «Core») от внешних деталей реализации, таких как базы данных, фреймворки и сторонние библиотеки.
Давайте реализуем классический юзкейс (сценарий использования) «Регистрация пользователя», чтобы увидеть эти принципы в действии.
1. Слой предметной области (Domain Layer / Core)
Это сердце приложения. Здесь сосредоточены бизнес-правила и доменные сущности. Данный слой не имеет внешних зависимостей.
namespace App\Domain\Entity;
class User {
private string $id;
private string $email;
private string $password;
public function __construct(string $id, string $email, string $password) {
$this->id = $id;
$this->email = $email;
$this->password = $password;
}
// Доменные методы (бизнес-логика)
public function changePassword(string $newPassword): void {
if (strlen($newPassword) < 8) {
throw new \InvalidArgumentException("Пароль слишком короткий");
}
$this->password = $newPassword;
}
public function getEmail(): string { return $this->email; }
public function getId(): string { return $this->id; }
}
2. Порты (Interfaces / Driven Ports)
Ядро определяет свои потребности через интерфейсы. В гексагональной архитектуре они называются Ведомыми портами (Driven Ports).
namespace App\Domain\Repository;
use App\Domain\Entity\User;
interface UserRepositoryInterface {
public function save(User $user): void;
public function findByEmail(string $email): ?User;
}
3. Слой приложения (Application Layer / Use Cases)
Этот слой координирует работу доменных объектов для выполнения конкретной задачи. Он зависит только от Domain-слоя.
namespace App\Application\UseCase;
use App\Domain\Entity\User;
use App\Domain\Repository\UserRepositoryInterface;
class RegisterUserUseCase {
private UserRepositoryInterface $repository;
public function __construct(UserRepositoryInterface $repository) {
$this->repository = $repository;
}
public function execute(string $email, string $password): void {
// Проверка бизнес-правила через репозиторий
if ($this->repository->findByEmail($email)) {
throw new \Exception("Пользователь уже существует");
}
$user = new User(uniqid(), $email, password_hash($password, PASSWORD_BCRYPT));
$this->repository->save($user);
}
}
4. Слой инфраструктуры (Adapters / Driven Adapters)
Содержит конкретные реализации (Адаптеры) для работы с внешними системами (БД, почтовые сервисы, внешние API).
namespace App\Infrastructure\Persistence;
use App\Domain\Entity\User;
use App\Domain\Repository\UserRepositoryInterface;
use PDO;
class SqlUserRepository implements UserRepositoryInterface {
private PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function save(User $user): void {
$stmt = $this->pdo->prepare("INSERT INTO users (id, email, password) VALUES (?, ?, ?)");
$stmt->execute([$user->getId(), $user->getEmail(), '...']);
}
public function findByEmail(string $email): ?User {
// Логика SQL-запроса...
return null; // Упрощённый пример (Mock)
}
}
5. Слой представления (Driving Adapters)
Точки входа в приложение (контроллеры HTTP, консольные команды CLI).
namespace App\Presentation\Http;
use App\Application\UseCase\RegisterUserUseCase;
class UserController {
private RegisterUserUseCase $useCase;
public function __construct(RegisterUserUseCase $useCase) {
$this->useCase = $useCase;
}
public function register(array $requestData): string {
try {
$this->useCase->execute($requestData['email'], $requestData['password']);
return "Пользователь успешно зарегистрирован";
} catch (\Exception $e) {
return "Ошибка: " . $e->getMessage();
}
}
}
Соответствие архитектурным паттернам:
1. Hexagonal Architecture (Ports & Adapters)
- Порты (Ports):
UserRepositoryInterface. - Адаптеры (Adapters):
SqlUserRepository(ведомый) иUserController(ведущий). - Суть: Мы можем заменить
SqlUserRepositoryнаRedisUserRepository, не меняя ни строчки в бизнес-логике.
2. Onion Architecture (Луковая архитектура)
- Domain Model (Ядро):
App\Domain\Entity. - Domain Services/Interfaces:
App\Domain\Repository. - Application Services:
App\Application\UseCase. - Outer Layer:
App\InfrastructureиApp\Presentation. - Суть: Направление зависимостей всегда строго внутрь: Инфраструктура -> Приложение -> Домен.
3. Clean Architecture (Чистая архитектура)
- Entities (Сущности):
User. - Use Cases (Юзкейсы):
RegisterUserUseCase. - Interface Adapters:
SqlUserRepository,UserController. - Frameworks & Drivers: Сама БД (
PDO), веб-фреймворк (Laravel, Symfony).
Dependency Injection (Внедрение зависимостей) — Сборка
В реальном приложении за сборку отвечает DI-контейнер. Пример «ручной» сборки в Composition Root:
// Точка композиции (Composition Root)
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$repository = new SqlUserRepository($pdo); // Конкретный адаптер инфраструктуры
$useCase = new RegisterUserUseCase($repository); // Наш юзкейс
$controller = new UserController($useCase); // Контроллер презентационного слоя
// Обработка входящего запроса