Хватит писать "спагетти": почему архитектурные паттерны в 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);
Почему это важно для архитектур