Архитектурные паттерны в PHP: от хаоса к clean code

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

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

Начать курс

Хватит писать "спагетти": почему архитектурные паттерны в PHP — это маст-хэв

Вы когда-нибудь открывали старый PHP-проект и чувствовали, как по спине пробегает холодок? Когда в одном файле index.php смешаны SQL-запросы, HTML-верстка, бизнес-логика и молитвы древним богам? Если да — вы столкнулись с отсутствием архитектуры. Или, что еще хуже, с архитектурным хаосом.

Сегодня мы поговорим о том, как архитектурные паттерны в PHP превращают ваш код из груды кирпичей в стройное здание. Это не теория для сеньоров с 10-летним стажем. Это инструменты, которые спасут ваш сон и бюджет проекта. Мы разберем 4 ключевых паттерна: MVC, Active Record, Repository и Dependency Injection. Поехали!

MVC (Model-View-Controller): фундамент любого фреймворка

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

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

Вот как это выглядит в чистом PHP (без фреймворка):

<?php
// UserController.php
class UserController {
    public function show(int $id): void {
        $userModel = new UserModel();
        $user = $userModel->findById($id); // Model
        
        // View
        include 'views/user_profile.php';
    }
}

// UserModel.php
class UserModel {
    public function findById(int $id): ?array {
        $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
        $stmt->execute([$id]);
        return $stmt->fetch() ?: null;
    }
}

// views/user_profile.php
<h1><?= htmlspecialchars($user['name']) ?></h1>

Когда применять: Всегда, когда у вас больше 2 страниц. MVC — это база. Он дает разделение логики, упрощает тестирование и поддержку.

Лайфхак: Не пишите SQL прямо во View. Это убивает всю суть паттерна. Используйте подготовленные шаблоны (Twig, Blade) — они автоматически экранируют вывод, защищая от XSS.

Active Record: простота, за которую платят производительностью

Active Record — это когда объект одновременно представляет строку в БД и содержит методы для работы с ней. Классика — Eloquent в Laravel или ActiveRecord в Yii2.

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

<?php
class User extends ActiveRecord {
    protected string $table = 'users';
    
    public static function findByName(string $name): ?self {
        return self::query()
            ->where('name', $name)
            ->first();
    }
}

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

$admin = User::findByName('Админ');
$admin->role = 'superadmin';
$admin->update();

Плюсы:

  • Минимум кода для CRUD-операций.
  • Интуитивно понятно новичкам.

Минусы:

  • Нарушает Single Responsibility Principle (один объект делает всё).
  • Сложно тестировать — объект жестко привязан к БД.
  • Плохо масштабируется для сложной логики.

Вывод: Active Record — отличный выбор для прототипов, небольших проектов и админок. Но для enterprise-приложений лучше смотреть в сторону Repository.

Repository: абстракция над хранилищем данных

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

Пример на PHP:

<?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->fetch();
        
        return $data ? new User($data) : null;
    }
    
    public function save(User $user): void {
        // Логика INSERT/UPDATE
    }
}

// Использование в контроллере
class UserController {
    public function __construct(
        private UserRepositoryInterface $userRepo
    ) {}
    
    public function show(int $id): void {
        $user = $this->userRepo->findById($id);
        // ...
    }
}

Преимущества:

  • Легко заменить БД на Redis или внешнее API — просто пишете новый класс.
  • Идеально для юнит-тестов (подменяете репозиторий на mock).
  • Бизнес-логика не знает о SQL.

Когда использовать: Если ваш проект растет, вы пишете тесты или планируете менять БД. Многие современные фреймворки (Laravel, Symfony) поощряют этот подход через Service Providers.

Dependency Injection: как перестать создавать объекты вручную

Dependency Injection (DI) — это когда объект получает зависимости извне, а не создает их сам. Звучит скучно, но именно DI делает ваш код гибким и тестируемым.

Плохой пример (жесткая связка):

<?php
class ReportGenerator {
    private DatabaseLogger $logger;
    
    public function __construct() {
        // Жесткая зависимость — нельзя подменить
        $this->logger = new DatabaseLogger();
    }
}

Хороший пример (DI):

<?php
interface LoggerInterface {
    public function log(string $message): void;
}

class ReportGenerator {
    public function __construct(
        private LoggerInterface $logger  // Зависимость приходит извне
    ) {}
    
    public function generate(): void {
        $this->logger->log('Генерация отчета началась');
        // ...
    }
}

// Использование с контейнером (например, PHP-DI или Symfony DI)
$logger = new FileLogger('/var/log/app.log');
$reportGenerator = new ReportGenerator($logger);

Почему это важно для архитектур

Блоги

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