Профессиональный PHP: от основ до разработки веб-приложений

Курс дает системное и практическое понимание PHP для профессиональной разработки. Вы изучите язык, работу с HTTP и базами данных, современные подходы к архитектуре, тестированию, безопасности и деплою.

1. Основы PHP и современная экосистема

Основы PHP и современная экосистема

Зачем PHP в 2026 году

PHP — это язык программирования, который чаще всего используют для серверной веб-разработки: обработки запросов, генерации HTML/JSON, работы с базами данных, очередями, кэшем и внешними API.

Сегодня PHP — это не только «скрипты в шаблонах», а зрелая платформа с:

  • современными версиями языка (PHP 8.x),
  • стандартами сообщества (PSR),
  • менеджером зависимостей Composer,
  • автозагрузкой классов, неймспейсами и объектной моделью,
  • экосистемой фреймворков (Laravel, Symfony) и инструментов качества кода.
  • В этом курсе мы будем двигаться от базового синтаксиса к профессиональной разработке веб-приложений: архитектура, тестирование, работа с окружением, безопасность, производительность.

    Как работает PHP в вебе

    PHP выполняется на сервере. Браузер отправляет HTTP-запрос, веб-сервер (например, Nginx/Apache) передаёт его PHP-интерпретатору, PHP-код формирует ответ (HTML или JSON), и сервер возвращает результат браузеру.

    !Схема показывает, как запрос проходит через веб-сервер, PHP и инфраструктуру приложения

    Важно понимать границу ответственности:

  • веб-сервер отвечает за приём соединений, TLS, статические файлы, проксирование;
  • PHP исполняет код приложения;
  • приложение управляет бизнес-логикой, доступом к данным, формированием ответа.
  • Установка и запуск PHP

    Проверка версии

    После установки PHP в терминале (командной строке) проверьте версию:

    Для профессиональной разработки ориентируйтесь на актуальную ветку PHP 8.x. Официальные сведения о релизах и поддержке смотрите в документации:

  • Документация PHP
  • Запуск скрипта

    Создайте файл hello.php:

    Запустите:

    Встроенный веб-сервер для обучения

    PHP имеет встроенный сервер, удобный для локальной разработки (не для продакшена):

    Где:

  • localhost:8000 — адрес и порт,
  • -t public — папка, которая считается корнем сайта.
  • Базовый синтаксис и типы

    Переменные и строгая типизация

    Переменные в PHP начинаются со знака name = 'Анна'; isAdmin = false; php <?php

    value); php <?php

    declare(strict_types=1);

    score >= 90) { score >= 75) { grade = 'C'; } php <?php

    for (i <= 3; i . "\n"; }

    items as item . "\n"; } php <?php

    declare(strict_types=1);

    user = [ 'id' => 1, 'name' => 'Анна', ];

    echo user['name']; // Анна php <?php

    declare(strict_types=1);

    function sum(int b): int { return b; }

    echo sum(2, 3); php <?php

    declare(strict_types=1);

    final class User { public function __construct( public int name, ) {} }

    user->name; php <?php

    declare(strict_types=1);

    namespace App\Domain;

    final class Email { public function __construct(public string user): string { if (!isset(user['name']; } bash composer init composer require monolog/monolog php <?php

    declare(strict_types=1);

    require __DIR__ . '/vendor/autoload.php'; php <?php

    declare(strict_types=1);

    require __DIR__ . '/../vendor/autoload.php';

    echo 'It works'; ``

    Что дальше по курсу

    В следующей части мы закрепим базовый синтаксис и перейдём к практическим основам разработки:

  • работа со строками и датами,
  • функции и разбиение на модули,
  • основы ООП глубже (инкапсуляция, интерфейсы, зависимости),
  • чтение конфигурации, окружение и точка входа,
  • первые шаги к «чистому» приложению.
  • 2. HTTP, формы, сессии и работа с файлами

    HTTP, формы, сессии и работа с файлами

    Как эта тема связана с предыдущей

    В предыдущей статье мы разобрали базовые элементы профессионального PHP-проекта: запуск кода, public/ как веб-корень, роль Composer, строгие типы, основы функций и ООП. Теперь закрепим ключевое: как именно PHP-приложение общается с браузером.

    Практически любое веб-приложение строится вокруг четырёх вещей:

  • HTTP-запросы и ответы
  • HTML-формы и валидация пользовательского ввода
  • Сессии и cookies для сохранения состояния
  • Работа с файлами (включая загрузку файлов)
  • HTTP: базовая модель взаимодействия

    HTTP — это протокол обмена сообщениями между клиентом (обычно браузером) и сервером. Клиент отправляет запрос, сервер возвращает ответ.

    !Диаграмма показывает состав HTTP-запроса и HTTP-ответа и типичные примеры

    Полезный обзор протокола:

  • MDN: HTTP overview
  • Метод, URL, заголовки и тело

    Запрос обычно описывается так:

  • Метод: что мы хотим сделать (например, GET, POST)
  • URL: к какому ресурсу обращаемся (например, /login)
  • Заголовки: метаданные (например, Content-Type, Accept, Cookie)
  • Тело: данные запроса (обычно в POST, PUT, PATCH)
  • Ответ обычно содержит:

  • Статус-код: результат обработки (например, 200, 404, 302)
  • Заголовки: метаданные ответа (например, Content-Type, Set-Cookie, Location)
  • Тело: HTML, JSON, файл и т.д.
  • Часто используемые методы

  • GET — получить ресурс (обычно без изменений на сервере)
  • POST — создать действие или ресурс (например, логин, отправка формы)
  • PUT — заменить ресурс целиком
  • PATCH — частично обновить ресурс
  • DELETE — удалить ресурс
  • Справочник по методам:

  • MDN: HTTP request methods
  • Статус-коды, которые нужно знать

    | Код | Смысл | Где встречается | |---:|---|---| | 200 | OK | успешная загрузка страницы/JSON | | 201 | Created | ресурс создан (часто API) | | 204 | No Content | успех без тела ответа | | 301/302 | Redirect | перенаправление после POST | | 400 | Bad Request | неверные данные запроса | | 401 | Unauthorized | нет аутентификации | | 403 | Forbidden | доступ запрещён | | 404 | Not Found | маршрут/ресурс не найден | | 422 | Unprocessable Content | валидация не прошла | | 500 | Internal Server Error | ошибка на сервере |

    Справочник по статус-кодам:

  • MDN: HTTP response status codes
  • Как PHP получает данные запроса

    PHP отдаёт данные запроса через суперглобальные массивы. Основные:

  • _POST — данные формы при application/x-www-form-urlencoded или multipart/form-data
  • _COOKIE — cookies
  • _SERVER — информация о запросе и окружении (метод, URI, заголовки в специальном виде)
  • Документация:

  • PHP Manual: Superglobals
  • Минимальная точка входа и разбор запроса

    Рекомендуемый подход для веб-приложений — единая точка входа public/index.php, которая читает запрос и решает, какой код выполнять.

    Пример учебного фронт-контроллера:

    Здесь:

  • REQUEST_METHOD определяет метод
  • REQUEST_URI содержит путь и query string
  • parse_url(..., PHP_URL_PATH) оставляет только путь, чтобы проще матчить маршруты
  • HTML-формы и обработка данных

    Как браузер отправляет форму

    Форма обычно отправляется двумя способами:

  • application/x-www-form-urlencoded — обычная форма без файлов
  • multipart/form-data — форма с файлами
  • Данные попадают в _FILES).

    Пример формы логина

    Создадим страницу GET /login и обработчик POST /login.

    И простая форма public/login-form.html:

    Важно: в этой статье мы не пишем HTML-разметку, но принцип такой: поля формы должны иметь атрибут name, совпадающий с ключами в _POST и явную валидацию, чтобы контролировать логику.

    Документация:

  • PHP Manual: filter_input
  • Кодировка и строки

    Частая проблема форм — длина строк в UTF-8. Для длины пользовательского текста используйте mb_strlen().

    Ответы сервера: заголовки, JSON и редиректы

    Заголовки и Content-Type

    Заголовки задаются функцией header().

  • Для HTML часто используют text/html; charset=utf-8
  • Для JSON используйте application/json; charset=utf-8
  • Пример JSON-ответа:

    Редирект после POST: паттерн PRG

    Если после отправки формы возвращать HTML напрямую, пользователь может обновить страницу и случайно повторить POST. Распространённый паттерн — Post/Redirect/Get:

  • Браузер отправляет POST
  • Сервер обрабатывает и отвечает 302 + Location
  • Браузер делает GET на новую страницу
  • Пример:

    Cookies и сессии: как появляется “состояние”

    HTTP сам по себе не хранит состояние: каждый запрос независим. Чтобы помнить пользователя между запросами, используют cookies и сессии.

    Cookies

    Cookie — это небольшое значение, которое сервер просит браузер сохранить. Браузер будет отправлять его обратно в следующих запросах.

    Установка cookie в PHP:

    Практические настройки:

  • secure включает передачу cookie только по HTTPS
  • httponly закрывает доступ к cookie из JavaScript
  • samesite снижает риск CSRF (обычно Lax по умолчанию подходит многим приложениям)
  • Сессии

    Сессия — это механизм, где браузер хранит только идентификатор (обычно в cookie), а данные хранятся на сервере.

    !Схема объясняет, что в cookie хранится идентификатор, а данные сессии находятся на сервере

    Старт сессии и запись данных:

    Чтение данных:

    Документация:

  • PHP Manual: Sessions
  • Важные правила безопасности для сессий

  • Всегда вызывайте session_start() до вывода тела ответа.
  • После успешной аутентификации обновляйте идентификатор сессии:
  • php <?php

    declare(strict_types=1);

    session_start();

    function setFlash(string _SESSION['flash'] = message = _SESSION['flash']); return is_string(message : null; }

    Обратите внимание на ключевые решения:

  • проверка UPLOAD_ERR_OK
  • ограничение размера
  • определение MIME через finfo по содержимому
  • генерация имени на сервере
  • сохранение в storage/, а не рядом с кодом
  • Документация по fileinfo:

  • PHP Manual: Fileinfo
  • Почему нельзя сохранять загруженные файлы “как есть”

    Риски:

  • пользователь может загрузить файл с именем ../../index.php и попытаться повлиять на пути
  • расширение может маскировать содержимое (например, “картинка”, которая на самом деле скрипт)
  • коллизии имён приводят к перезаписи
  • Профессиональная практика:

  • хранить файл под “сервисным” именем
  • сохранять исходное имя как метаданные в базе (если нужно)
  • по возможности хранить файлы вне public/
  • отдавать файлы через контролируемый обработчик (авторизация, лимиты)
  • Работа с файловой системой в PHP

    Файловая система нужна для логов, кэша, экспорта, импорта, хранения локальных файлов.

    Чтение и запись

    Защита от path traversal

    Если вы принимаете “имя файла” от пользователя, никогда не склеивайте его напрямую в путь.

    Неправильно:

    Правильнее:

  • хранить “публичный” идентификатор файла (например, UUID или хэш-имя)
  • проверять, что имя соответствует ожидаемому формату
  • формировать путь только из безопасных частей
  • Пример проверки “сервисного имени”:

    Мини-архитектура: “запрос → обработчик → ответ”

    Уже на этом этапе полезно мыслить одинаково для страниц, форм и API:

  • Получили запрос (метод, путь, данные)
  • Провалидировали вход
  • Выполнили бизнес-логику
  • Сформировали корректный HTTP-ответ
  • Типичные ошибки новичков:

  • смешивание HTML и логики в одном файле без структуры
  • отсутствие валидации и нормализации данных
  • хранение файлов в public/ без контроля
  • отсутствие session_regenerate_id() после логина
  • Что дальше по курсу

    Следующий шаг после понимания HTTP и ввода данных — систематизация кода:

  • выделение слоёв (контроллеры, сервисы, репозитории)
  • более строгая типизация и DTO
  • централизованная обработка ошибок
  • базовые меры безопасности (CSRF, XSS, безопасные заголовки)
  • Это превратит набор скриптов в приложение, которое удобно расширять и тестировать.

    3. Базы данных и доступ к данным (PDO, ORM)

    Базы данных и доступ к данным (PDO, ORM)

    Как эта тема связана с предыдущими

    В прошлой статье мы разобрали, как PHP-приложение принимает HTTP-запрос, валидирует ввод из форм, работает с сессиями и файлами и формирует корректный ответ. Теперь добавим следующий обязательный слой любого веб-приложения: хранение и извлечение данных.

    Типичный жизненный цикл запроса выглядит так:

  • Браузер отправляет запрос (например, POST /login).
  • PHP читает входные данные (dsn = 'pgsql:host=127.0.0.1;port=5432;dbname=app;';
  • pass = 'secret';

    dsn, pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); php <?php

    declare(strict_types=1);

    _POST['email'] ?? ''); email'"; // уязвимо pdo->query(email = trim((string)(stmt = stmt->execute(['email' => user = stmt = users = email = 'user@example.com'; stmt = stmt->execute(['email' => hash]);

    pdo->lastInsertId(); php <?php

    declare(strict_types=1);

    stmt = stmt->execute(['email' => hash]);

    pdo->lastInsertId();

    pdo->prepare('INSERT INTO posts (user_id, title, body) VALUES (:uid, :title, :body)'); userId, 'title' => 'Hello', 'body' => 'First post']);

    e) { e; } php <?php

    declare(strict_types=1);

    final class UserRepository { public function __construct(private PDO email): ?array { this->pdo->prepare( 'SELECT id, email, password_hash, created_at FROM users WHERE email = :email' ); email]);

    stmt->fetch(); return is_array(row : null; }

    public function add(string passwordHash): int { this->pdo->prepare( 'INSERT INTO users (email, password_hash) VALUES (:email, :hash)' ); email, 'hash' => this->pdo->lastInsertId(); } } `

    Почему это полезно:

  • проще тестировать
  • проще менять SQL и схему
  • проще перейти на другой подход (Query Builder/ORM), не переписывая весь HTTP-слой
  • ORM: что это и когда применять

    ORM (Object-Relational Mapping) — подход, при котором строки таблиц отображаются на объекты, а запросы часто строятся через API ORM.

    В PHP наиболее известны:

  • Doctrine ORM (часто в Symfony-экосистеме)
  • Eloquent ORM (часто в Laravel)
  • Официальные сайты:

  • Doctrine ORM
  • Laravel Eloquent
  • Плюсы ORM

  • меньше ручного SQL для типовых операций
  • удобные связи между сущностями (например, пользователь и посты)
  • единые паттерны: Unit of Work, Identity Map, Lazy Loading (важно понимать их последствия)
  • Минусы ORM

  • сложнее предсказать итоговый SQL и производительность
  • риск эффекта N+1 запрос при неправильной загрузке связей
  • порог входа и необходимость дисциплины в моделировании
  • Практическое правило:

  • для небольших проектов и типового CRUD ORM может ускорить разработку
  • для сложных отчётов и нестандартных выборок SQL или Query Builder часто прозрачнее
  • Query Builder и DBAL: компромисс между SQL и ORM

    Между “чистым SQL” и “полным ORM” есть промежуточный слой: DBAL/Query Builder. Он позволяет строить запросы программно, но всё ещё мыслить SQL-категориями.

    Пример продукта: Doctrine DBAL.

  • Doctrine DBAL
  • Производительность и индексы: базовая интуиция

    Когда данных становится много, скорость запроса начинает зависеть от индексов.

    Минимальные практики:

  • индексируйте поля, по которым часто ищете (email, user_id)
  • используйте уникальные индексы там, где значение должно быть уникальным (users.email)
  • проверяйте планы запросов инструментами БД (например, EXPLAIN)
  • Не нужно “индексировать всё”: индексы ускоряют чтение, но замедляют запись и занимают место.

    Миграции: как безопасно менять схему

    В реальных проектах схема БД эволюционирует. Изменения должны быть воспроизводимыми и версионируемыми.

    Для этого используют миграции — набор SQL/скриптов, которые последовательно меняют структуру.

    Популярные инструменты:

  • Phinx
  • Doctrine Migrations
  • Безопасность и эксплуатационные практики

    Ключевые правила:

  • всегда используйте prepared statements для пользовательского ввода
  • ограничивайте права пользователя БД (не давайте “всё”)
  • храните секреты вне репозитория (переменные окружения, менеджеры секретов)
  • не показывайте пользователю текст SQL-ошибок
  • логируйте ошибки на сервере, но аккуратно с персональными данными
  • Минимальный итоговый шаблон для учебного приложения

    Чтобы связать тему БД с HTTP-частью из прошлой статьи, полезно держать такую структуру:

  • public/index.php принимает запрос и маршрутизирует
  • src/Repository/*Repository.php выполняет SQL через PDO
  • src/Service/*Service.php` содержит бизнес-логику (опционально на этом этапе)
  • обработчик возвращает ответ: JSON/redirect
  • Так ваш проект не превратится в набор несвязанных скриптов.

    Что дальше по курсу

    Следующий шаг — постепенно превращать код в поддерживаемое приложение:

  • конфигурация окружения и контейнер зависимостей (DI)
  • DTO и более строгие модели данных
  • централизованная обработка ошибок
  • безопасность веб-приложений (CSRF, XSS, политики cookies)
  • тестирование слоя данных и бизнес-логики
  • 4. Архитектура приложений и фреймворки (MVC, Laravel/Symfony)

    Архитектура приложений и фреймворки (MVC, Laravel/Symfony)

    Связь с предыдущими темами курса

    Ранее мы уже собрали три ключевых слоя веб-разработки на PHP:

  • язык и экосистема (Composer, PSR, автозагрузка)
  • HTTP-взаимодействие (запрос, ответ, формы, сессии, файлы)
  • доступ к данным (SQL, PDO, репозитории, основы ORM)
  • Следующий логичный шаг — понять, как организовать код, чтобы приложение оставалось расширяемым и поддерживаемым. Для этого нужны архитектурные принципы и, в большинстве реальных проектов, фреймворк.

    Цель этой статьи: связать воедино HTTP-часть и работу с базой данных через понятную архитектуру и показать, как Laravel и Symfony реализуют эти идеи на практике.

    Зачем нужна архитектура веб-приложения

    Пока проект маленький, можно писать обработчики прямо в public/index.php. Но с ростом требований появляются типичные проблемы:

  • трудно найти, где именно реализована логика
  • повторяется код валидации, формирования ответов, работы с БД
  • становится страшно что-то менять, потому что всё связано со всем
  • тестирование почти невозможно без запуска всего приложения
  • Архитектура — это набор решений о структуре кода, границах ответственности и способах связи компонентов. В веб-приложениях ключевая идея простая: разделить код на части так, чтобы изменения в одном слое минимально затрагивали остальные.

    MVC как базовый архитектурный шаблон

    MVC (Model–View–Controller) — классический шаблон для веб-приложений. Он отвечает на вопрос: кто за что отвечает при обработке запроса.

    !Диаграмма показывает роли Model, View и Controller и поток данных между ними

    Controller (контроллер)

    Контроллер — это слой, который связывает HTTP и бизнес-логику.

    Обычно он:

  • читает входные данные запроса
  • запускает валидацию (на практике часто через отдельные классы)
  • вызывает сервисы/модели
  • формирует HTTP-ответ (HTML, JSON, redirect)
  • Важно: контроллер не должен содержать сложную бизнес-логику и тем более “тяжёлые” SQL-запросы.

    Model (модель)

    Слово “модель” в MVC трактуют по-разному. В профессиональной PHP-практике полезно мыслить так:

  • доменная модель — правила и сущности предметной области (например, Пользователь, Заказ)
  • доступ к данным — репозитории/ORM, которые читают и пишут данные
  • сервисы — сценарии использования (use cases), которые координируют операции
  • То есть “Model” — это не обязательно “один класс на таблицу”, а весь слой, где живёт логика приложения.

    View (представление)

    View отвечает за отображение данных:

  • HTML-шаблоны (страницы)
  • формирование JSON (в API это часто делает контроллер или отдельный responder)
  • В Symfony типичный шаблонизатор — Twig, его документация: Документация Twig

    В Laravel используется Blade, документация: Документация Blade

    Где MVC полезен, а где его недостаточно

    MVC хорошо объясняет “скелет” веб-приложения, но в реальных проектах его обычно расширяют:

  • добавляют слой Service (сценарии) между Controller и Model
  • добавляют слой Repository между логикой и БД
  • вводят DTO (объекты переноса данных), чтобы не таскать ассоциативные массивы
  • Практическая мысль: MVC — удобная отправная точка, но качество архитектуры определяется тем, как вы держите границы ответственности внутри “Model”.

    Слоистая архитектура: как связать HTTP и БД без хаоса

    Чтобы устойчиво расти, приложению полезно разделение на слои. Один из практичных вариантов выглядит так:

    !Схема показывает слои приложения и направление зависимостей

    Presentation слой

    Это всё, что касается доставки данных пользователю и получения данных от него:

  • HTTP-контроллеры
  • роутинг
  • middleware (прослойки запроса)
  • сериализация в JSON
  • шаблоны
  • Application слой

    Это сценарии использования (use cases): “зарегистрировать пользователя”, “создать пост”, “оплатить заказ”.

    Обычно этот слой:

  • принимает уже нормализованные данные
  • работает через интерфейсы репозиториев и сервисов
  • управляет транзакциями (либо делегирует это инфраструктуре)
  • Domain слой

    Здесь живут правила предметной области:

  • сущности и value objects
  • проверки инвариантов (например, “нельзя оплатить отменённый заказ”)
  • доменные события (если вы до них дойдёте)
  • Идеал: домен минимально зависит от фреймворка.

    Infrastructure слой

    Это конкретные реализации:

  • PDO/Doctrine/Eloquent
  • клиенты внешних API
  • очередь, кэш
  • файловая система
  • Главная идея: инфраструктуру легче менять, если она спрятана за интерфейсами и не “протекает” в бизнес-логику.

    Dependency Injection и контейнер: как компоненты связываются между собой

    Dependency Injection (DI) — это способ передавать зависимости объекту снаружи, а не создавать их внутри.

    Плохой признак:

  • класс сам создаёт new PDO(...), сам читает .env, сам решает, какой логгер использовать
  • Хороший признак:

  • класс получает зависимости через конструктор и не знает, как они устроены
  • DI-контейнер

    DI-контейнер — компонент, который:

  • создаёт объекты
  • понимает, какие зависимости нужны
  • умеет подставлять нужные реализации (например, “в проде один логгер, в тестах другой”)
  • Symfony активно использует контейнер как основу приложения: Документация Symfony Service Container

    Laravel тоже использует контейнер: Документация Laravel Service Container

    Жизненный цикл HTTP-запроса во фреймворках

    На базовом уровне фреймворки делают то, что мы уже писали вручную во фронт-контроллере, но системно и расширяемо.

    !Схема показывает этапы обработки HTTP-запроса от входа до ответа

    Типовой конвейер:

  • веб-сервер направляет запрос в public/index.php
  • фреймворк создаёт объект запроса
  • роутер сопоставляет путь и метод с обработчиком
  • middleware выполняют общие действия (аутентификация, защита, логирование)
  • контроллер вызывает нужный сценарий
  • формируется объект ответа
  • ответ отправляется клиенту
  • Middleware

    Middleware — это “прослойка” вокруг обработки запроса. Каждое middleware может:

  • остановить обработку и вернуть ответ сразу
  • изменить запрос
  • изменить ответ после выполнения контроллера
  • Symfony опирается на компонент HTTP Foundation: Документация Symfony HttpFoundation

    В более стандартизированном мире PHP часто упоминают PSR-7 и PSR-15:

  • PSR-7: HTTP message interfaces
  • PSR-15: HTTP server request handlers
  • Не все фреймворки “чисто” следуют PSR-7 внутри, но эти стандарты полезны для понимания экосистемы.

    Роутинг и контроллеры: превращаем URL в код

    Роутинг — это таблица соответствий “метод + путь” → “обработчик”.

    В Symfony роуты можно задавать по-разному, один из популярных способов — атрибуты: Документация Symfony Routing

    В Laravel — через файл маршрутов: Документация Laravel Routing

    Профессиональная практика для контроллеров:

  • контроллеры тонкие
  • валидация вынесена (Form Request в Laravel или Validator-компонент в Symfony)
  • бизнес-логика в сервисах/сценариях
  • Работа с данными во фреймворках: ORM, репозитории, миграции

    Вы уже знаете PDO и prepared statements. Во фреймворках чаще используют ORM, но принципы безопасности и контроля данных не исчезают.

    Symfony и Doctrine

    В Symfony-дефолтной экосистеме часто используют Doctrine:

  • ORM: Doctrine ORM
  • миграции: Doctrine Migrations
  • Особенности Doctrine (важные для архитектуры):

  • Unit of Work: ORM копит изменения и применяет их при flush()
  • lazy loading: связи могут догружаться “по требованию”, что может привести к N+1
  • Laravel и Eloquent

    Laravel обычно использует Eloquent ORM: Документация Laravel Eloquent

    Особенности Eloquent:

  • активная запись (Active Record): модель часто знает, как себя сохранить
  • быстрый старт для CRUD
  • важно следить за выборками и eager loading, чтобы не получить N+1
  • N+1 запрос как типичная ловушка ORM

    Проблема N+1 возникает, когда вы:

  • получили список сущностей одним запросом
  • затем для каждой сущности отдельно догрузили связанный объект
  • Итого получается запросов вместо 2–3. Решение обычно в “жадной загрузке” (eager loading) или корректном запросе.

    Конфигурация, окружение и секреты

    В профессиональном проекте конфигурация отделена от кода:

  • разные значения для dev/stage/prod
  • секреты не лежат в репозитории
  • Laravel часто использует .env и helper-функции для доступа к конфигурации: Документация Laravel Configuration

    Symfony использует переменные окружения и пакет Dotenv: Документация Symfony Dotenv

    Практическое правило:

  • храните секреты в окружении или менеджере секретов
  • не логируйте секреты
  • не возвращайте пользователю детали ошибок
  • Обработка ошибок и логирование

    Фреймворки дают инфраструктуру, которую вручную обычно пишут позже:

  • централизованный обработчик исключений
  • страницы/ответы ошибок для разных окружений
  • логирование
  • Популярный логгер в PHP — Monolog: Monolog

    Важный архитектурный принцип:

  • исключения и логи должны помогать разработчику
  • пользователь должен получать безопасное и понятное сообщение
  • Структура проекта: как выглядит “взрослый” код

    Ниже — пример фреймворк-агностичной структуры, которая хорошо маппится и на Symfony, и на Laravel:

    Смысл в том, чтобы:

  • HTTP-слой не знал деталей базы
  • домен не зависел от фреймворка
  • инфраструктура была заменяемой
  • Laravel и Symfony: как выбрать

    И Laravel, и Symfony — зрелые фреймворки. Разница чаще в подходе и “ощущениях” от разработки, чем в возможности решить задачу.

    | Критерий | Laravel | Symfony | |---|---|---| | Входной порог | Обычно ниже, быстрый старт | Выше, но более “конструктор” | | Стиль | Много готового “из коробки” | Набор компонентов, строгая конфигурация | | ORM по умолчанию | Eloquent | Doctrine (часто) | | Типичные проекты | Продукты, CRUD, быстрые итерации | Сложные системы, долгоживущие проекты, enterprise | | Ключевая сильная сторона | Скорость разработки и экосистема | Архитектурная дисциплина и компонентность |

    Полезные отправные точки:

  • Документация Laravel
  • Документация Symfony
  • Практический выбор:

  • если важно быстро собрать продукт и много типового CRUD, Laravel часто удобнее
  • если вы строите сложную систему с большим количеством модулей и долгим сроком жизни, Symfony часто проще держать в порядке
  • Как “перейти” от учебного кода к фреймворку

    Если у вас уже есть учебное приложение с public/index.php, PDO и простыми обработчиками, переход можно сделать по шагам:

  • Зафиксировать структуру проекта и автозагрузку PSR-4 в Composer.
  • Вынести работу с БД в репозитории и запретить SQL в HTTP-слое.
  • Вынести бизнес-логику в сервисы (use cases).
  • Добавить централизованную обработку ошибок и логирование.
  • Подключить роутер, контейнер, шаблоны.
  • После этого перейти на Laravel или Symfony, перенеся слои “снаружи внутрь”.
  • Главная мысль: фреймворк эффективен, когда вы понимаете архитектуру и умеете держать границы ответственности.

    Итоги

    В этой статье мы связали предыдущие темы курса в целостную картину:

  • HTTP-обработчики должны быть тонкими и предсказуемыми
  • доступ к данным должен быть вынесен и безопасен (prepared statements, репозитории, транзакции)
  • MVC помогает понять базовые роли, но реальная поддерживаемость достигается слоистой архитектурой
  • DI-контейнер и middleware делают систему расширяемой без копипаста
  • Laravel и Symfony по-разному упаковывают одни и те же принципы, и выбор зависит от контекста
  • Дальше по курсу логично углубляться в практики, которые делают архитектуру “боевой”: безопасность (CSRF, XSS), тестирование, очереди/кэш, проектирование доменной модели и качество кода.

    5. Качество, безопасность и деплой (тестирование, CI/CD)

    Качество, безопасность и деплой (тестирование, CI/CD)

    Как эта тема связана с предыдущими

    Мы уже прошли путь от синтаксиса и экосистемы к HTTP-взаимодействию, сессиям и файлам, затем к базам данных и, наконец, к архитектуре и фреймворкам (MVC, слои, DI).

    Теперь важный профессиональный шаг: сделать приложение таким, чтобы оно:

  • надёжно работало (тесты, статический анализ, предсказуемые сборки)
  • не было дырявым (типовые веб-уязвимости, секреты, безопасная конфигурация)
  • стабильно доставлялось в продакшен (деплой, миграции, CI/CD)
  • Эта статья — про инженерные практики, которые отличают код, который работает у вас на ноутбуке, от приложения, которое живёт в продакшене.

    Качество кода как система: что проверяем и зачем

    Качество кода в веб-приложении обычно обеспечивается комбинацией четырёх уровней:

  • стиль и форматирование: единообразие и читаемость
  • статический анализ: поиск ошибок без запуска приложения
  • тестирование: проверка поведения через запуск
  • ревью и автоматизация: чтобы проверки выполнялись всегда, а не «по настроению»
  • Практичный принцип: всё, что можно проверить автоматически, должно проверяться автоматически в CI.

    Тестирование в PHP: что тестировать в веб-приложении

    Основные типы тестов

  • Unit-тесты: проверяют отдельный класс/функцию без инфраструктуры (БД, сети, файлов).
  • Integration-тесты: проверяют взаимодействие компонентов (например, репозиторий + реальная БД в тестовом окружении).
  • Feature/HTTP-тесты: проверяют поведение приложения на уровне HTTP (запрос → ответ), часто через тестовый клиент фреймворка.
  • Профессиональная стратегия: больше unit-тестов, меньше «дорогих» тестов, но интеграционные тесты должны покрывать критичные места (репозитории, транзакции, безопасность).

    PHPUnit как базовый стандарт

    PHPUnit — главный инструмент модульного тестирования в PHP.

  • PHPUnit
  • Минимальный пример unit-теста для доменной логики:

    Ключевая мысль: тестируйте правила (policy, валидаторы, сервисы), а не детали реализации.

    Моки и границы: как не сделать тесты хрупкими

    Если тест «знает» слишком много о внутренностях класса, он ломается при любом рефакторинге.

    Практика:

  • мокайте внешние зависимости (HTTP-клиент, отправка писем, очередь)
  • не мокайте то, что вы хотите проверить (например, саму бизнес-логику)
  • держите явные интерфейсы (например, MailerInterface, UserRepositoryInterface)
  • Статический анализ: ловим ошибки до запуска

    Статический анализатор делает то, что компилятор делает в статически типизированных языках: находит несоответствия типов, недостижимый код, возможные null, неверные вызовы.

    Популярные инструменты:

  • PHPStan
  • Psalm
  • Что статический анализ особенно хорошо ловит:

  • обращение к несуществующим ключам массива
  • несоответствие типов аргументов и возвращаемых значений
  • потенциальные null там, где вы ожидаете объект
  • ошибки в сигнатурах, которые в рантайме проявятся не сразу
  • Пример типичной проблемы в «массивном» коде:

    php <?php

    declare(strict_types=1);

    session_start();

    function csrfToken(): string { if (!isset(_SESSION['csrf'])) { _SESSION['csrf']; }

    function assertCsrf(string expected = expected) || !hash_equals(token)) { http_response_code(419); exit('CSRF token mismatch'); } } bash composer install --no-interaction --no-dev --prefer-dist --optimize-autoloader yaml name: CI

    on: push: pull_request:

    jobs: test: runs-on: ubuntu-latest

    steps: - name: Checkout uses: actions/checkout@v4

    - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.3' coverage: none

    - name: Install dependencies run: composer install --no-interaction --prefer-dist

    - name: Lint (PHP) run: php -l public/index.php

    - name: Static analysis (PHPStan) run: vendor/bin/phpstan analyse

    - name: Unit tests (PHPUnit) run: vendor/bin/phpunit ``

    Примечания к примеру:

  • команды могут отличаться под ваш проект и фреймворк
  • для монорепо и сложных проектов добавляют кэш Composer
  • GitLab CI как альтернатива

  • GitLab CI/CD
  • Суть та же: этапы проверки качества и тестов должны быть обязательными для мержа.

    Минимальный профессиональный стандарт для учебного проекта

    Чтобы ваш проект из курса выглядел «по-взрослому», достаточно внедрить следующий набор:

  • composer.lock в репозитории и установка зависимостей через Composer
  • PHPUnit с базовым набором unit-тестов для бизнес-логики
  • PHPStan или Psalm на уровне, который вы можете поддерживать
  • автоформатирование (например, PHP CS Fixer) и проверка стиля в CI
  • базовые защиты: prepared statements, password_hash, session_regenerate_id`, CSRF-токены для форм
  • CI пайплайн, который блокирует merge при падении анализа или тестов
  • Итоги

    В профессиональной разработке PHP важен не только код, но и процесс вокруг кода:

  • качество обеспечивается тестами, статическим анализом и единым стилем
  • безопасность строится на дисциплине: валидация, SQL без инъекций, XSS/CSRF защита, правильные cookies, секреты вне репозитория
  • деплой должен быть воспроизводимым, а CI/CD — обязательным «фильтром», который не пропускает регрессии в продакшен
  • После этой темы вы готовы перейти к следующему уровню: наблюдаемость (логи, метрики, трассировка), производительность (профилирование), а также более сложные архитектурные практики (доменные модели, события, очереди и кэш).