Архитектурные паттерны в PHP: от MVC до CQRS

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему архитектура решает всё (и ваш кофе больше не будет остывать)

Представьте, что вы строите дом. Можно навалить кирпичи как попало, скрепить их жвачкой и надеяться, что он простоит до зимы. А можно спроектировать фундамент, стены и крышу так, чтобы дом пережил и ураган, и нашествие родственников. В мире PHP всё точно так же. Архитектурные паттерны — это те самые чертежи, которые превращают ваш код из «работает — и ладно» в «работает, масштабируется и не вызывает желание выпить йод».

Когда я начинал кодить на PHP, мои проекты были монолитными монстрами. В одном файле index.php уживались SQL-запросы, HTML-верстка и бизнес-логика. Это работало ровно до тех пор, пока клиент не просил добавить новую фичу. Потом наступал ад. Сегодня я покажу вам, как этого ада избежать. Мы разберем ключевые архитектурные паттерны в PHP — от классики до современных подходов. Поехали!

MVC (Model-View-Controller): дедушка всех паттернов

Если вы писали на Laravel, Symfony или Yii2, вы уже встречались с MVC. Это базовый архитектурный паттерн PHP, который делит приложение на три части:

  • Model — данные и бизнес-логика (работа с БД, валидация, расчёты).
  • View — отображение (HTML, шаблоны, JSON).
  • Controller — связка: принимает запрос, вызывает Model и передаёт данные во View.

MVC решает главную проблему — разделение ответственности. Больше никаких SQL в HTML. Код становится читаемым, тестируемым и поддерживаемым.

Пример MVC на чистом PHP

// Model: User.php
class User {
    public function find(int $id): ?array {
        $pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
        $stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
        $stmt->execute([$id]);
        return $stmt->fetch() ?: null;
    }
}

// Controller: UserController.php
class UserController {
    public function show(int $id): void {
        $userModel = new User();
        $user = $userModel->find($id);
        if (!$user) {
            http_response_code(404);
            echo 'Пользователь не найден';
            return;
        }
        // Передаём данные во View
        require 'views/user.php';
    }
}

// View: views/user.php
<h1><?= htmlspecialchars($user['name']) ?></h1>
<p>Email: <?= htmlspecialchars($user['email']) ?></p>

Когда использовать: для веб-приложений средней сложности. Если у вас простой блог или интернет-магазин — MVC ваш выбор. Но будьте осторожны: если контроллеры становятся жирными (Fat Controllers), паттерн теряет смысл.

Active Record: простота и скорость

Паттерн Active Record — это когда объект одновременно представляет и строку в БД, и бизнес-логику. Помните Laravel Eloquent или Yii2 ActiveRecord? Это он. Каждая модель — это и запрос к базе, и правила валидации, и связи.

Пример Active Record (стиль Eloquent)

class User extends ActiveRecord {
    // Свойства соответствуют столбцам таблицы
    public int $id;
    public string $name;
    public string $email;

    public function save(): bool {
        // Логика INSERT/UPDATE
        if ($this->id) {
            // UPDATE
        } else {
            // INSERT
        }
        return true;
    }

    public static function find(int $id): ?self {
        // SELECT * FROM users WHERE id = $id
    }
}

// Использование
$user = new User();
$user->name = 'Иван';
$user->email = 'ivan@example.com';
$user->save(); // Просто и понятно

Плюсы: быстро пишется, интуитивно понятно новичкам. Минусы: нарушает принцип единой ответственности (Single Responsibility из SOLID). Модель знает и о данных, и о том, как их сохранить. Для сложных проектов это может стать проблемой.

Когда использовать: для прототипов, небольших проектов и CRUD-приложений. Если проект разрастётся — переходите на Data Mapper.

Data Mapper: чистота и гибкость

Data Mapper — это паттерн, который полностью отделяет объекты от базы данных. Модель — это просто PHP-класс с данными. За сохранение и загрузку отвечает отдельный слой — Mapper. Doctrine ORM — классический пример.

Пример Data Mapper

// Модель — просто DTO (Data Transfer Object)
class User {
    public function __construct(
        public ?int $id = null,
        public string $name = '',
        public string $email = ''
    ) {}
}

// Mapper — занимается только сохранением
class UserMapper {
    private PDO $pdo;
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    public function save(User $user): void {
        if ($user->id) {
            $stmt = $this->pdo->prepare('UPDATE users SET name=?, email=? WHERE id=?');
            $stmt->execute([$user->name, $user->email, $user->id]);
        } else {
            $stmt = $this->pdo->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
            $stmt->execute([$user->name, $user->email]);
            $user->id = (int)$this->pdo->lastInsertId();
        }
    }
}

// Использование
$mapper = new UserMapper($pdo);
$user = new User(null, 'Мария', 'maria@example.com');
$mapper->save($user);

Когда использовать: для сложных доменных моделей, когда бизнес-логика важна, а БД может меняться. Data Mapper идеально ложится на принципы SOLID и упрощает тестирование.

Repository: абстракция над источником данных

Repository — это прослойка между бизнес-логикой и хранилищем данных. Он скрывает детали: откуда берутся данные (БД, API, кэш) и как они сохраняются. Вы просто говорите: «Дай мне пользователя с ID 5», а Repository сам решает, лезть в MySQL или Redis.

Пример Repository в PHP

interface UserRepositoryInterface {
    public function findById(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function save(User $user): void;
}

class MySQLUserRepository implements UserRepositoryInterface {
    private PDO $pdo;
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    public function findById(int $id): ?User {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?');
        $stmt->execute([$id]);
        $data = $stmt                

Блоги

Рекомендаций по книгам