PHP для уровня Middle: уверенная разработка и архитектура

Курс систематизирует знания PHP до уровня middle-разработчика: современные возможности языка, качественная архитектура и практики разработки. Упор на проектирование, работу с базами данных, тестирование, безопасность и производительность в реальных веб-приложениях.

1. Современный PHP: типы, ООП, исключения и стандарты

Современный PHP: типы, ООП, исключения и стандарты

Эта статья открывает курс PHP для уровня Middle: уверенная разработка и архитектура. Мы зафиксируем базовые ожидания от современного PHP-разработчика: уверенная работа с типами, чистое ООП, корректные исключения и следование стандартам экосистемы.

К концу статьи у вас должно сложиться понимание:

  • как типы в PHP реально помогают поддерживать код
  • как писать ООП так, чтобы код расширялся без переписывания
  • как проектировать и обрабатывать исключения
  • какие стандарты (PSR) де-факто обязательны в командах
  • Современный PHP и версия языка

    Под современным PHP обычно подразумевают подход к разработке, который опирается на:

  • строгую типизацию там, где это оправдано
  • автозагрузку и управление зависимостями через Composer
  • стандарты PHP-FIG (PSR)
  • статический анализ, форматирование и тестирование
  • Рекомендуемая база для практики по курсу:

  • PHP 8.1+ (в идеале 8.2+)
  • Официальная документация по языку: PHP Manual.

    Типы в PHP: зачем они нужны middle-разработчику

    PHP исторически был динамическим языком: тип значения определяется во время выполнения. Современный PHP добавил много инструментов, чтобы ошибки ловились раньше, а IDE и анализаторы лучше понимали ваш код.

    Декларация строгих типов

    В начале файла можно включить строгий режим для скалярных типов:

    Важно понимать границы:

  • strict_types действует на уровне файла, где объявлена функция, а не где она вызвана
  • строгий режим касается в первую очередь скалярных типов (например, int, string, float, bool)
  • Справка: declare(strict_types=1).

    Типы параметров и возвращаемого значения

    Типы можно указывать для:

  • аргументов функций и методов
  • возвращаемого значения
  • свойств класса
  • Справка по типам: Type Declarations.

    Nullable-типы и значения по умолчанию

    Если значение может быть null, используйте ?Type:

    Правило для читаемости:

  • если null является ожидаемым состоянием, ?Type уместен
  • если null означает ошибку или неожиданность, лучше исключение или отдельный результат
  • Union-типы и intersection-типы

    Union-тип означает одно из нескольких:

    Intersection-тип означает одновременно несколько интерфейсов (полезно в инфраструктурном коде и адаптерах):

    Справка: Attributes.

    Enums: когда нужны перечисления

    Enum позволяет выразить ограниченный набор допустимых значений без “магических строк”.

    Справка: Enumerations.

    !Диаграмма показывает, как интерфейс отделяет контракт от реализаций и упрощает подмену зависимостей

    Исключения: корректная обработка ошибок в PHP

    Исключения нужны, чтобы отделять нормальный поток выполнения от ошибочных ситуаций и не размазывать проверки по всему коду.

    Throwable, Exception и Error

    В PHP 7+ почти всё, что можно “поймать”, реализует интерфейс Throwable.

  • Exception — традиционные исключения уровня приложения
  • Error — ошибки движка и критические ошибки (например, TypeError)
  • Обычно прикладной код ловит Exception (или более конкретные исключения). Ловить Throwable уместно на самом верхнем уровне приложения, чтобы гарантированно залогировать фатальную ситуацию.

    Справка: Exceptions.

    Базовые конструкции: try, catch, finally

    Третий аргумент ($previous) сохраняет первопричину, что критично для диагностики.

    !Блок-схема показывает разделение нормального выполнения и обработки исключений

    Стандарты и экосистема: PSR и Composer

    Middle-разработчик обычно работает в команде и в проекте, который живёт годами. Поэтому стандарты важны не меньше синтаксиса.

    Composer: зависимости и автозагрузка

    Composer решает две ключевые задачи:

  • устанавливает зависимости (пакеты)
  • генерирует автозагрузчик классов
  • Документация: Composer Documentation.

    Базовые файлы:

  • composer.json — что нужно проекту
  • composer.lock — зафиксированные версии, которые реально поставлены
  • vendor/autoload.php — вход в автозагрузку
  • PSR: что это и какие нужны чаще всего

    PSR — набор рекомендаций от PHP-FIG (PHP Framework Interop Group), чтобы библиотеки и приложения были совместимее.

    Ключевые PSR для middle-уровня:

  • PSR-12: стиль кода (PSR-12)
  • PSR-4: автозагрузка классов по namespace (PSR-4)
  • PSR-3: интерфейс логгера (PSR-3)
  • PSR-11: контейнер зависимостей (PSR-11)
  • PSR-7: HTTP-сообщения (Request/Response) (PSR-7)
  • PSR-15: HTTP middleware (PSR-15)
  • PSR-18: HTTP client (PSR-18)
  • Практическая польза:

  • вы можете заменить библиотеку на другую, если обе следуют PSR
  • код становится более переносимым между фреймворками
  • PSR-4 на практике: как думать о структуре

    Идея PSR-4: namespace соответствует директории.

    Пример концепции:

  • App\Domain\User\User находится в src/Domain/User/User.php
  • Это упрощает навигацию, автозагрузку и поддержку проекта.

    Инструменты качества кода

    Минимальный набор, который часто ожидают от middle:

  • автоформатирование по стандарту (например, PSR-12)
  • статический анализ типов и потенциальных ошибок
  • Популярные инструменты:

  • PHP-CS-Fixer: PHP-CS-Fixer
  • PHP_CodeSniffer: PHP_CodeSniffer
  • PHPStan: PHPStan
  • Psalm: Psalm
  • Минимальный чеклист после статьи

    Если вы хотите соответствовать ожиданиям middle-уровня, проверьте себя:

  • вы используете типы аргументов, возвращаемых значений и свойств
  • вы понимаете разницу между ?Type, TypeA|TypeB, mixed, void, never
  • вы предпочитаете композицию, интерфейсы и внедрение зависимостей
  • вы проектируете исключения осмысленно и не теряете первопричину
  • вы понимаете, что такое Composer, PSR-4 и PSR-12, и зачем это в команде
  • В следующих материалах курса мы будем наращивать эти основы: проектировать слои приложения, вводить архитектурные границы и закреплять практики через задачи и мини-проект.

    2. Инструменты и экосистема: Composer, автозагрузка, PSR, окружение

    Инструменты и экосистема: Composer, автозагрузка, PSR, окружение

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

    К концу статьи вы должны уверенно понимать:

  • как устроены composer.json и composer.lock, и почему они важны
  • как работает автозагрузка и как правильно раскладывать код по namespace
  • какие PSR чаще всего встречаются в проектах и как их применять
  • как собирать воспроизводимое окружение локально и в CI
  • Composer как фундамент проекта

    Composer в PHP — это стандартный менеджер зависимостей и генератор автозагрузчика. Практически любой современный проект начинается с composer.json.

    Официальные источники:

  • Composer
  • Документация Composer
  • Packagist
  • Главные файлы проекта

    | Файл | Зачем нужен | Почему важно middle-уровню | |---|---|---| | composer.json | Описывает зависимости, автозагрузку, скрипты | Это контракт проекта: что ему нужно и как он собирается | | composer.lock | Фиксирует точные версии зависимостей | Гарантирует одинаковую сборку у всей команды и в CI | | vendor/ | Установленные пакеты + автозагрузчик | Не редактируется руками, генерируется Composer | | vendor/autoload.php | Точка входа автозагрузки | Подключается в приложении и тестах |

    > Практика команды: composer.json и composer.lock коммитятся в репозиторий, папка vendor/ обычно не коммитится.

    Установка и обновление зависимостей

    Базовые команды:

    Ключевое отличие:

  • composer install ставит версии из composer.lock и обеспечивает воспроизводимость
  • composer update пересчитывает зависимости и обновляет версии, после чего меняется composer.lock
  • Middle-практика:

  • в CI и на проде почти всегда выполняют composer install --no-dev
  • composer update выполняют осознанно, обычно отдельным PR, чтобы контролировать риск обновлений
  • Ограничения версий и semver на пальцах

    Большинство библиотек живут по правилам семантического версионирования: MAJOR.MINOR.PATCH.

  • PATCH — исправления багов без ломания API
  • MINOR — новые возможности без ломания API
  • MAJOR — изменения, которые могут ломать API
  • Частая запись в Composer — ^1.2.

  • ^1.2 обычно означает: можно обновляться в пределах 1.x, но не до 2.0
  • это баланс между получением исправлений и защитой от больших несовместимых изменений
  • Документация по ограничениям версий: Composer: Versions and constraints.

    require и require-dev

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

    Composer scripts как единая точка запуска рутины

    Composer умеет запускать скрипты, чтобы команда работала одинаково на всех машинах.

    Пример фрагмента composer.json:

    Запуск:

    Безопасность зависимостей

    Composer умеет проверять известные уязвимости:

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

    !Как Composer превращает описание зависимостей в установленные пакеты и автозагрузку

    Автозагрузка и PSR-4 без магии

    Автозагрузка решает проблему ручных require_once. Вы пишете код с namespace, а Composer сам находит файл класса.

    Стандарт, который почти везде используется: PSR-4.

    Как работает PSR-4 в идее

  • namespace отражает структуру директорий
  • Composer настраивает соответствие префикса namespace и папки
  • когда PHP впервые встречает неизвестный класс, автозагрузчик вычисляет путь к файлу и подключает его
  • Настройка PSR-4 в composer.json

    Пример:

    Теперь класс App\Domain\User\User ожидается в файле:

  • src/Domain/User/User.php
  • Содержимое файла:

    PSR-11: контейнер зависимостей

    PSR-11 — минимальный интерфейс контейнера, который умеет отдавать сервисы по идентификатору.

    Зачем это нужно:

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

    PSR-7, PSR-15, PSR-18: HTTP-уровень

    Эти стандарты чаще встречаются в микросервисах, API и middleware-архитектуре:

  • PSR-7 — HTTP Request/Response как объекты
  • PSR-15 — middleware интерфейсы
  • PSR-18 — HTTP client интерфейс
  • Практическая выгода:

  • легче менять HTTP-клиент или компоненты пайплайна
  • проще тестировать, потому что контракты стабилизированы интерфейсами
  • Окружение: чтобы код работал одинаково у всех

    Даже хороший код ломается, если окружение непредсказуемо. Middle-разработчик должен уметь настроить проект так, чтобы:

  • локально было удобно разрабатывать
  • в CI было повторяемо
  • в продакшене было безопасно и стабильно
  • Версия PHP и расширения

    Что стоит фиксировать и проверять:

  • версию PHP, например 8.2
  • ключевые расширения, например pdo, json, mbstring, openssl
  • режимы ошибок и настройки php.ini
  • Практика в composer.json:

    Это не устанавливает PHP автоматически, но защищает от случайного запуска проекта на неподходящей версии.

    Документация: Composer platform packages.

    Конфигурация через переменные окружения

    Распространённый подход:

  • секреты и параметры окружения (DSN базы, ключи, режим debug) не хранят в коде
  • приложение читает значения из переменных окружения
  • Минимальные правила:

  • не коммитить секреты в репозиторий
  • иметь шаблон файла, например .env.example, без секретов
  • в продакшене задавать переменные средствами инфраструктуры
  • Если проект использует библиотеку для загрузки .env, часто встречается phpdotenv. Но важно помнить: в продакшене обычно удобнее использовать настоящие переменные окружения, а не файлы.

    Docker как способ стандартизировать окружение

    Docker не обязателен, но часто снимает проблемы вида у меня работает, у тебя нет.

    Типичный набор сервисов:

  • php-fpm или php-cli для выполнения кода
  • nginx как веб-сервер
  • postgres или mysql как база данных
  • redis при необходимости
  • !Понимание, из каких компонентов обычно состоит окружение приложения

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

    Отладка и диагностика

    Инструменты, которые часто используются в проектах:

  • Xdebug для пошаговой отладки и покрытия кода: Xdebug
  • логи приложений по PSR-3
  • метрики и трассировка зависят от стека, но принцип один: ошибки должны быть наблюдаемыми
  • Важно различать режимы:

  • локально можно включать подробные ошибки
  • в продакшене ошибки показывать пользователю нельзя, но нужно логировать с контекстом
  • Набор инструментов качества кода для middle-уровня

    Ниже — практичный минимум, который часто ожидают в командах.

    Тестирование

    Наиболее распространённый фреймворк тестирования: PHPUnit.

    Установка в dev-зависимости:

    Статический анализ

    Статанализ ловит ошибки типов и контрактов ещё до запуска.

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

  • PHPStan
  • Psalm
  • Важно: статанализ особенно хорошо сочетается с темами прошлой статьи — типами, readonly, аккуратными контрактами и осмысленными исключениями.

    Форматирование и стиль

    Чтобы PSR-12 соблюдался автоматически:

  • PHP-CS-Fixer
  • PHP_CodeSniffer
  • Пример require-dev для типового проекта

    Минимальный чеклист после статьи

  • вы понимаете разницу между composer install и composer update
  • вы знаете роль composer.lock и почему он критичен для CI и продакшена
  • вы умеете настроить PSR-4 автозагрузку и понимаете связь namespace и пути файла
  • вы понимаете, зачем нужны PSR-3 и PSR-11, и почему лучше зависеть от интерфейсов
  • вы можете описать, как обеспечить воспроизводимость окружения (версия PHP, расширения, переменные окружения, Docker при необходимости)
  • В следующих материалах курса мы будем использовать этот фундамент как базовую инфраструктуру: соберём каркас приложения, выделим слои, настроим проверки качества и закрепим архитектурные границы на практике.

    3. Работа с данными: SQL, транзакции, ORM, миграции, кеширование

    Работа с данными: SQL, транзакции, ORM, миграции, кеширование

    В прошлых статьях мы зафиксировали основы современного PHP (типы, ООП, исключения) и собрали рабочую экосистему (Composer, PSR, окружение). Теперь перейдём к тому, что почти всегда является ядром серверного приложения: работа с данными.

    Middle-разработчик должен не просто “уметь сделать запрос”, а понимать:

  • как писать SQL так, чтобы он был предсказуемым и быстрым
  • как безопасно работать с базой через PDO и подготовленные выражения
  • когда использовать транзакции и как не получить “случайные” баги данных
  • что ORM даёт, где мешает и как его правильно ограничивать
  • как и зачем делать миграции схемы
  • как использовать кеширование и не сломать актуальность данных
  • SQL как контракт с базой данных

    SQL — это язык, на котором приложение “договаривается” с базой данных. Даже если вы используете ORM, в критичных местах вы всё равно упрётесь в понимание SQL.

    Что важно в запросах на практике

  • Запрашивайте только нужные поля вместо SELECT *.
  • Фильтруйте и сортируйте предсказуемо: где возможно, используйте индексы.
  • Всегда фиксируйте ожидаемый результат запроса: одна строка, список, агрегат.
  • Индексы простыми словами

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

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

  • Индексируйте колонки, по которым часто делаете WHERE, JOIN, ORDER BY.
  • Не делайте индексы “на всё подряд”: это раздувает размер и ухудшает запись.
  • Проверяйте запросы через EXPLAIN (поддерживается большинством СУБД) и сравнивайте планы выполнения.
  • SQL-инъекции и подготовленные выражения

    Главное правило безопасности: никогда не склеивайте SQL из строк с пользовательским вводом.

    Плохо:

    Хорошо: подготовленное выражение (prepared statement) с параметрами.

    Подготовленные выражения важны потому что:

  • защищают от SQL-инъекций
  • делают код читаемее и поддерживаемее
  • дают базе шанс эффективнее переиспользовать план запроса (зависит от СУБД и драйвера)
  • Документация: PDO::prepare.

    Доступ к данным в PHP: PDO и слой хранения

    PDO — стандартный способ работы с SQL-базами в PHP через единый API.

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

    Базовая настройка PDO, которую ожидают в проектах

    Рекомендация: включайте исключения для ошибок базы и отключайте “тихие” фейлы.

    Что здесь важно:

  • PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION превращает ошибки SQL в исключения, которые можно корректно обработать
  • PDO::ATTR_DEFAULT_FETCH_MODE задаёт предсказуемый формат выборки
  • PDO::ATTR_EMULATE_PREPARES => false по возможности включает настоящие prepared statements на стороне СУБД
  • Почему нужен слой хранения, а не SQL “везде”

    Если SQL размазан по контроллерам и сервисам, у вас появляются проблемы:

  • Сложно менять схему и запросы: их нужно искать по всему проекту.
  • Нельзя изолированно тестировать бизнес-логику.
  • Транзакции становятся хаотичными: непонятно, кто и где должен их открывать.
  • Обычно вводят слой хранения (часто его называют репозиторий): класс, который отвечает за загрузку и сохранение данных.

    Пример простого репозитория на PDO:

    Важно: репозиторий не обязан быть “идеальным DDD”. Его цель на middle-уровне — локализовать работу с базой и сделать контракты предсказуемыми.

    !Схема показывает, где обычно размещают SQL и где проходит граница транзакции

    Транзакции: как сохранить целостность данных

    Транзакция — это группа операций, которая должна выполниться целиком или не выполниться вообще.

    Что такое ACID без усложнений

    ACID — свойства транзакций:

  • Atomicity: всё или ничего
  • Consistency: данные не переходят в “некорректное” состояние с точки зрения ограничений
  • Isolation: параллельные транзакции не мешают друг другу неожиданным образом
  • Durability: после подтверждения результат не теряется при сбое
  • Middle-ожидание: понимать это не как теорию, а как причины реальных багов.

    Транзакции в PDO

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

    Типовой шаблон:

    Почему так:

  • commit() подтверждает изменения
  • rollBack() гарантирует откат, если что-то пошло не так
  • проверка inTransaction() защищает от ситуации, когда исключение было брошено до старта транзакции
  • Изоляция транзакций: какие проблемы она решает

    Уровень изоляции определяет, что транзакция может “увидеть” из работы других транзакций.

    Проблемы, которые встречаются в реальных проектах:

  • грязное чтение: вы прочитали данные, которые другая транзакция ещё не подтвердила
  • неповторяемое чтение: вы дважды прочитали одну строку в рамках транзакции и получили разные значения
  • фантомы: вы повторили запрос по условию и получили другой набор строк
  • Детали зависят от СУБД (PostgreSQL, MySQL) и конкретного уровня изоляции. На middle-уровне важно практическое правило:

  • чем выше изоляция, тем меньше “странных” эффектов, но тем выше риск блокировок и падения производительности
  • Практические правила работы с транзакциями

  • Делайте транзакции короткими: не ходите в сеть, не вызывайте внешние API внутри транзакции.
  • Группируйте операции по смыслу: если данные должны измениться согласованно, они должны быть в одной транзакции.
  • Думайте о конкуренции: если два пользователя меняют один и тот же ресурс, вам нужны либо блокировки, либо стратегия повторов.
  • ORM: когда полезно и когда опасно

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

    Самый распространённый ORM в PHP-экосистеме: Doctrine.

  • Документация: Doctrine ORM
  • Что ORM даёт

  • меньше ручного SQL в типовых CRUD-операциях
  • единый стиль доступа к данным
  • удобные связи между сущностями (например, “пользователь и его заказы”)
  • Где ORM создаёт проблемы

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

    Ключевые понятия:

  • Entity (сущность): класс, который соответствует строке таблицы
  • EntityManager: основной объект для работы с сущностями (загрузка, сохранение)
  • Unit of Work: механизм, который отслеживает изменения сущностей и применяет их при flush()
  • Lazy loading: “ленивая загрузка” связей, которая часто и создаёт N+1
  • Пример маппинга через атрибуты:

    Здесь важно:

  • ключ должен быть стабильным и уникальным
  • TTL задаёт срок жизни (в примере 300 секунд)
  • при изменении email нужно либо удалить ключ, либо принять окно устаревания
  • Инвалидация кеша: как не сломать актуальность

    Инвалидация — это удаление или обновление кеша, когда исходные данные изменились.

    Практические подходы:

  • Удалять ключи при записи (например, после обновления пользователя удалять user.email.{id}).
  • Использовать TTL как страховку, даже если удаляете ключи.
  • Группировать ключи по префиксам и соглашениям, чтобы их можно было предсказуемо чистить.
  • Проблема “cache stampede”

    Если ключ истёк, и много запросов одновременно пытаются заполнить кеш, база может получить всплеск нагрузки.

    Что делают в проектах:

  • добавляют блокировку на заполнение (lock)
  • используют библиотечные механизмы защиты от stampede
  • Пример из экосистемы: компонент Symfony Cache содержит механизмы, упрощающие защиту от stampede и работу с адаптерами под Redis/APCu и другие хранилища.

  • Документация: Symfony Cache
  • Минимальный чеклист после статьи

  • вы используете подготовленные выражения и не собираете SQL строками
  • вы понимаете, где должна начинаться транзакция и почему она должна быть короткой
  • вы знаете основные риски ORM: N+1, скрытые запросы, сложность оптимизации
  • вы понимаете правила миграций: коммит в репозиторий, не редактировать применённые, воспроизводимость
  • вы умеете объяснить cache-aside, TTL и инвалидацию, и знаете, почему кеш может “сломать” актуальность данных
  • В следующих материалах курса мы будем опираться на этот фундамент, чтобы проектировать границы модулей, собирать прикладные сервисы вокруг транзакций и выстраивать наблюдаемость: логирование, метрики и безопасную обработку ошибок при работе с внешними зависимостями.

    4. Веб-разработка: HTTP, REST, middleware, аутентификация и безопасность

    Веб-разработка: HTTP, REST, middleware, аутентификация и безопасность

    В предыдущих статьях курса мы закрепили современный PHP (типы, ООП, исключения, PSR), настроили инструменты (Composer, автозагрузка, окружение) и разобрали работу с данными (SQL, транзакции, ORM, миграции, кеш). Теперь соберём всё это вокруг типичной задачи middle-разработчика: создание и сопровождение веб-приложения и API.

    К концу статьи вы должны понимать:

  • как устроены HTTP-запрос и HTTP-ответ, и какие свойства методов важны для архитектуры
  • как проектировать API в стиле REST так, чтобы оно было предсказуемым
  • как работает middleware-пайплайн и какие стандарты PSR используются
  • чем отличается аутентификация от авторизации и какие подходы применяются в PHP
  • какие базовые уязвимости встречаются чаще всего и какие практики защищают проект
  • HTTP как протокол: что реально важно разработчику

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

    !Базовая модель HTTP-запроса и ответа

    Анатомия запроса и ответа

    Запрос обычно состоит из:

  • метод (например, GET, POST)
  • путь и query string (например, /users?limit=20)
  • заголовки (например, Accept, Authorization)
  • тело (body), если оно нужно (например, JSON)
  • Ответ обычно состоит из:

  • статус-код (например, 200, 404)
  • заголовки (например, Content-Type, Cache-Control)
  • тело ответа
  • Хороший практический источник по HTTP: MDN: HTTP.

    Методы HTTP: семантика важнее привычки

    На уровне middle важно знать не только каким методом отправить запрос, но и какие гарантии вы обещаете клиенту.

    | Метод | Типичное назначение | Тело запроса | Важные свойства | |---|---|---|---| | GET | Получить представление ресурса | обычно нет | безопасный, идемпотентный | | POST | Создать ресурс или выполнить команду | обычно да | не идемпотентный | | PUT | Полностью заменить ресурс | часто да | идемпотентный | | PATCH | Частично изменить ресурс | часто да | обычно не гарантирует идемпотентность | | DELETE | Удалить ресурс | обычно нет | идемпотентный |

    Пояснения без сложной теории:

  • Безопасный метод — сервер не должен менять состояние данных из-за вызова этого метода. Обычно это GET.
  • Идемпотентный метод — повтор одного и того же запроса несколько раз даёт тот же конечный результат. Обычно это PUT и DELETE.
  • Практическая польза:

  • Ретраи в сетевых сбоях безопаснее делать для идемпотентных операций.
  • Кеширование чаще применимо к GET.
  • Статус-коды: договор о результате

    Статус-код — это короткий сигнал клиенту, что произошло. Важно использовать коды предсказуемо.

    | Код | Смысл | Когда использовать | |---|---|---| | 200 OK | Успех | Успешный GET, успешный ответ без создания | | 201 Created | Создано | POST, который создал ресурс (часто с Location) | | 204 No Content | Успех без тела | DELETE, PUT, когда тело ответа не нужно | | 400 Bad Request | Некорректный ввод | Ошибка формата, валидации, отсутствует поле | | 401 Unauthorized | Нет аутентификации | Нет/невалидный токен, нет логина | | 403 Forbidden | Нет прав | Пользователь известен, но доступ запрещён | | 404 Not Found | Не найдено | Ресурс отсутствует | | 409 Conflict | Конфликт | Нарушение уникальности, конфликт версий | | 422 Unprocessable Content | Ошибка семантики | Данные валидны по формату, но не проходят бизнес-правила | | 500 Internal Server Error | Ошибка сервера | Неожиданное падение приложения |

    Справочник: MDN: HTTP response status codes.

    Заголовки, которые вы будете видеть постоянно

    | Заголовок | Где | Зачем | |---|---|---| | Content-Type | запрос/ответ | Формат тела, например application/json | | Accept | запрос | Какие форматы клиент готов принять | | Authorization | запрос | Учетные данные, часто Bearer <token> | | Cookie | запрос | Сессионные данные браузера | | Set-Cookie | ответ | Установка cookie | | Cache-Control | ответ | Правила кеширования | | ETag | ответ | Версия представления ресурса | | If-None-Match | запрос | Проверка актуальности по ETag |

    HTTP и PHP: как это выглядит в коде

    На практике в современных приложениях запрос и ответ часто представлены объектами по стандартам:

  • PSR-7: HTTP message interfaces
  • Идея PSR-7:

  • запрос и ответ — это объекты
  • они обычно иммутабельны: методы withHeader, withBody возвращают новый объект
  • Это напрямую связано с темами из первой статьи курса: типы и контракты дают предсказуемость на границах системы.

    REST: как проектировать API, чтобы его понимали

    REST в прикладном смысле — это набор практик, чтобы API было:

  • предсказуемым по URL и методам
  • удобным для интеграций
  • устойчивым к изменениям
  • Сильная сторона REST не в модности, а в снижении случайной сложности.

    Ресурсы и URL

    Думайте о URL как об именах ресурсов (сущностей), а методы — как об операциях над ними.

    Пример для пользователей:

    | Действие | Метод и путь | Комментарий | |---|---|---| | Список пользователей | GET /users | Можно добавить фильтры в query string | | Получить пользователя | GET /users/{id} | Один ресурс | | Создать пользователя | POST /users | Создание, обычно 201 | | Обновить пользователя целиком | PUT /users/{id} | Идемпотентно | | Частично обновить | PATCH /users/{id} | Только изменяемые поля | | Удалить пользователя | DELETE /users/{id} | Обычно 204 |

    Практическое правило: избегайте URL вида /createUser, если это не вынужденная команда. REST-стиль проще поддерживать и документировать.

    Пагинация, сортировка и фильтрация

    Если список может стать большим, пагинация обязательна.

    Пример договорённости:

  • GET /users?limit=20&offset=40
  • Или вариант со страницами:

  • GET /users?page=3&perPage=20
  • Важно, чтобы договорённость была единой по проекту.

    Формат ошибок: стабильность для клиента

    Клиенту нужно уметь отличать:

  • ошибку формата/валидации (не так передали данные)
  • ошибку доступа (не аутентифицирован/нет прав)
  • ошибку сервера (временная проблема)
  • Один из практичных форматов ответа об ошибке для JSON API:

    Практика middle-уровня:

  • Не возвращайте HTML-страницы ошибок из JSON API.
  • Не светите пользователю детали исключений и SQL.
  • Корреляцию с логами делайте через идентификатор запроса.
  • Middleware: как устроен современный HTTP-конвейер

    Middleware — это слой, который оборачивает обработку запроса. Он может:

  • прочитать запрос
  • принять решение (например, запретить доступ)
  • изменить запрос или ответ
  • передать управление дальше
  • !Понимание “обёртки” middleware вокруг обработчика

    PSR-15: стандарт middleware

    В PHP чаще всего встречается стандарт:

  • PSR-15: HTTP Server Request Handlers
  • Ключевые интерфейсы:

  • Psr\Http\Server\MiddlewareInterface
  • Psr\Http\Server\RequestHandlerInterface
  • Типовая сигнатура middleware:

    Связка с темой исключений из первой статьи:

  • middleware бросает доменно понятное исключение (UnauthorizedException)
  • верхний уровень (error handler middleware) превращает его в корректный ответ 401 без утечки деталей
  • Пароли: хранение и проверка только через встроенные функции

    Нельзя хранить пароли в открытом виде и нельзя “хешировать md5”. В PHP есть стандартный безопасный путь:

  • PHP Manual: password_hash
  • PHP Manual: password_verify
  • Минимальная практика:

  • хранить password_hash(...) в базе
  • сравнивать через password_verify(...)
  • Безопасность: минимальный набор, который ожидают от middle

    Хорошая отправная точка для общей картины угроз:

  • OWASP Top 10
  • Ниже — самые частые классы проблем и что с ними делать.

    Инъекции: SQL уже закрыли, но есть и другие

    Из прошлой статьи вы уже должны уверенно делать:

  • подготовленные выражения вместо склейки строк
  • Но инъекции бывают и не только SQL:

  • инъекции в командную строку
  • инъекции в шаблоны
  • инъекции в LDAP (реже)
  • Правило одно: данные пользователя не должны становиться “кодом” для интерпретатора.

    XSS: защита через экранирование вывода

    XSS происходит, когда вы отдаёте в HTML небезопасный пользовательский ввод.

    Базовая практика:

  • Экранировать вывод в HTML.
  • Не вставлять пользовательские данные в script и “сырые” атрибуты без строгой необходимости.
  • Добавить защитные заголовки, в том числе Content-Security-Policy.
  • Справка: MDN: Cross-Site Scripting (XSS).

    CSRF: актуально для cookie-based аутентификации

    CSRF — это атака, когда браузер пользователя отправляет запрос на ваш сайт с его cookies, но по инициативе злоумышленника.

    Справка: MDN: CSRF.

    Практические защиты:

  • SameSite для cookie.
  • CSRF-токен для опасных операций (POST, PUT, DELETE).
  • CORS: это не “защита API от всех”

    CORS — механизм браузера, который ограничивает запросы со страниц на других доменах. Это не заменяет аутентификацию.

    Справка: MDN: CORS.

    Практика:

  • настройка CORS должна быть явной: какие origins разрешены, какие заголовки и методы
  • Security headers: дешёвая и полезная страховка

    На уровне HTTP-ответов часто добавляют заголовки:

  • Strict-Transport-Security чтобы браузер предпочитал HTTPS
  • Content-Security-Policy для снижения риска XSS
  • X-Content-Type-Options: nosniff чтобы браузер не “угадывал” тип
  • X-Frame-Options или frame-ancestors в CSP чтобы снизить риск clickjacking
  • Справка:

  • MDN: HTTP response headers
  • Валидация данных: где делать и зачем

    Валидация нужна на границе системы:

  • Сначала проверьте формат и обязательность полей на HTTP-уровне.
  • Затем примените бизнес-правила на прикладном уровне.
  • Связка с темой типов:

  • после валидации удобно переводить входные данные в DTO или value object, чтобы дальше код работал с типизированной моделью
  • Обработка ошибок: не утекать наружу

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

  • Пользователь не должен видеть stack trace.
  • В логах должен быть контекст: request_id, идентификатор пользователя, ключевые параметры.
  • Ошибки аутентификации и авторизации должны быть различимы по статус-кодам 401 и 403.
  • Безопасность зависимостей: не забывайте про Composer

    Из статьи про инструменты:

  • composer.lock фиксирует версии
  • composer audit помогает обнаруживать известные уязвимости
  • Справка: Composer audit.

    Минимальный чеклист после статьи

  • вы различаете HTTP методы по смыслу, а не по привычке
  • вы используете корректные статус-коды и стабильный формат ошибок
  • вы понимаете, как PSR-7 и PSR-15 описывают запрос, ответ и middleware
  • вы различаете аутентификацию и авторизацию и можете объяснить выбор сессий или токенов
  • вы знаете базовые меры против XSS, CSRF и утечек ошибок, и умеете объяснить, зачем нужны security headers
  • 5. Качество и эксплуатация: тестирование, профилирование, CI/CD и деплой

    Качество и эксплуатация: тестирование, профилирование, CI/CD и деплой

    В предыдущих статьях курса мы построили основу: современный PHP (типы, ООП, исключения), рабочие инструменты (Composer, PSR, окружение), корректную работу с данными (SQL, транзакции, миграции, кеширование) и веб-слой (HTTP, middleware, безопасность). Эта статья переводит всё это в режим надёжной эксплуатации: как проверять код автоматически, как находить узкие места производительности, как собирать CI/CD и как деплоить без сюрпризов.

    К концу статьи вы должны уверенно понимать:

  • что именно тестировать и на каких уровнях
  • как использовать PHPUnit, чтобы тесты были предсказуемыми и полезными
  • что такое профилирование и чем оно отличается от «оптимизации на глаз»
  • как устроен CI (Continuous Integration) и CD (Continuous Delivery/Deployment)
  • какие практики деплоя и проверок после деплоя считаются базовыми на уровне middle
  • Качество как система, а не как «настроение команды»

    Качество в разработке — это не только «код красивый». Это способность системы:

  • правильно работать (корректность)
  • безопасно меняться (поддерживаемость)
  • выдерживать нагрузку (производительность)
  • быть наблюдаемой в продакшене (эксплуатация)
  • На уровне middle важно мыслить качеством как набором автоматических проверок и процессов, а не как разовыми усилиями.

    Типичная цель процесса качества:

  • локально вы быстро ловите ошибки типов и контрактов (то, что мы закрепляли в статьях про типы и PSR)
  • в CI вы гарантируете, что PR не ломает проект
  • в продакшене вы быстро диагностируете проблемы (логирование, метрики, ошибки)
  • Тестирование: что это такое и какие уровни нужны

    Тест — это код, который проверяет, что другая часть кода ведёт себя ожидаемо.

    Важно не пытаться «покрыть тестами всё подряд», а выбрать уровни тестов, которые дают максимальную пользу.

    Уровни тестирования

    | Уровень | Что проверяем | Скорость | Что чаще всего ломается | Где особенно полезно в нашем курсе | |---|---|---|---|---| | Unit-тесты (модульные) | один класс/функция | быстро | бизнес-правила, ветвления | доменная логика, сервисы без I/O | | Integration-тесты (интеграционные) | взаимодействие с БД/очередью/файлами | медленнее | SQL, транзакции, конфигурация | репозитории на PDO/ORM, миграции | | API/HTTP-тесты | поведение API как системы | средне | маршруты, middleware, статусы | REST-эндпоинты, авторизация |

    Практика middle-уровня:

  • unit-тестами покрывают правила и преобразования данных
  • интеграционными тестами закрывают самые рискованные места I/O (БД, транзакции)
  • API-тесты фиксируют контракт HTTP (коды, формат ошибок, доступ)
  • !Пирамида показывает, почему быстрых unit-тестов обычно больше, чем медленных end-to-end

    Тестовый дубль: простыми словами

    В тестах часто заменяют реальную зависимость на упрощённую.

  • Mock — объект-заглушка, который ещё и проверяет, как его вызывали
  • Stub — объект, который возвращает заранее заданные данные
  • Зачем это нужно:

  • чтобы не ходить в реальную базу/сеть
  • чтобы тест был быстрым и детерминированным (то есть всегда давал одинаковый результат)
  • PHPUnit: базовый инструмент тестирования в PHP

    Самый распространённый тестовый фреймворк для PHP — PHPUnit.

  • PHPUnit
  • Минимальная структура теста

    Пример unit-теста для доменной логики (без базы и сети):

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

  • ожидайте конкретное исключение, а не общий Exception
  • Интеграционные тесты с базой

    Интеграционный тест — это проверка, что ваш код реально работает с инфраструктурой (например, с PostgreSQL).

    Ключевая идея:

  • unit-тесты не должны требовать БД
  • интеграционные тесты должны поднимать предсказуемую БД (часто через Docker) и прогонять миграции
  • Связь с предыдущими статьями:

  • из статьи про данные вы уже знаете транзакции и миграции
  • из статьи про окружение вы знаете, как делать воспроизводимость
  • Минимальные правила для интеграционных тестов:

  • каждый тест должен начинаться с чистого состояния (часто через транзакцию с откатом или пересоздание схемы)
  • тесты не должны зависеть от порядка запуска
  • Покрытие кода: что измеряет и чего не обещает

    Покрытие кода показывает, какие строки/ветки выполнялись во время тестов.

    Это полезно, но не является доказательством качества.

    Практические выводы:

  • 100% coverage не гарантирует отсутствие багов
  • низкое coverage часто означает, что в проекте легко сломать критичные пути незаметно
  • Инструменты покрытия в PHP обычно используют Xdebug:

  • Xdebug
  • Статический анализ и стиль: ловим проблемы до запуска

    В предыдущих статьях мы опирались на типы и PSR. Статический анализ делает это практикой: проверяет код без выполнения.

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

  • PHPStan
  • Psalm
  • Что они ловят чаще всего:

  • несоответствие типов аргументов и возвращаемых значений
  • возможные null там, где вы не ожидаете null
  • недостижимый код, неверные PHPDoc-обобщения для массивов
  • Для стиля кода часто используют:

  • PHP-CS-Fixer
  • PHP_CodeSniffer
  • Единая точка запуска через Composer scripts

    Чтобы команда и CI запускали проверки одинаково, фиксируйте команды в composer.json.

    Польза:

  • разработчик запускает composer ci локально
  • CI запускает ровно то же самое
  • Профилирование: как ускорять код без самообмана

    Профилирование — это измерение того, где программа тратит время и память.

    Важно отличать:

  • логирование (что происходило)
  • метрики (сколько раз и сколько заняло)
  • профилирование (какие функции и вызовы съели время)
  • Когда профилирование действительно нужно

    Профилирование включают, когда есть симптом:

  • медленный эндпоинт
  • рост времени ответа при увеличении нагрузки
  • подозрение на N+1 запросы (связь со статьёй про ORM)
  • Практический подход:

  • Сначала зафиксируйте проблему числом: время ответа, количество запросов, потребление памяти.
  • Потом профилируйте и находите узкие места.
  • Оптимизируйте и снова измеряйте.
  • Инструменты профилирования в PHP

  • Xdebug умеет профилирование и трассировку: Xdebug
  • Blackfire — коммерческий профайлер и мониторинг производительности: Blackfire
  • Tideways — коммерческий APM и профилирование: Tideways
  • Если вы используете Symfony-компоненты, удобно измерять небольшие участки кода через Stopwatch:

  • Symfony Stopwatch
  • Частые причины медленной работы, которые видит middle

    | Причина | Как проявляется | Как диагностировать | Типичный фикс | |---|---|---|---| | N+1 запросы | много запросов на один экран | лог запросов + профайлер | eager loading, переписать выборку | | Нет индекса | медленные WHERE/JOIN | EXPLAIN в БД | добавить индекс | | Слишком много сериализации JSON | CPU на кодирование/декодирование | профайлер | уменьшить объём ответа, кешировать | | Внешний HTTP вызов внутри критичного пути | скачки по времени | тайминги + логирование | таймауты, ретраи, очередь | | Неправильный кеш | промахи и «шторм» | метрики cache hit ratio | TTL, инвалидация, защита от stampede |

    !Схема показывает, где обычно измеряют производительность и где чаще всего возникают узкие места

    CI: Continuous Integration на практике

    CI (Continuous Integration) — это процесс, при котором каждый коммит/PR автоматически проходит проверки.

    Зачем это нужно:

  • код не попадает в main «вслепую»
  • качество проверяется одинаково у всех
  • проблемы ловятся раньше и дешевле
  • Популярные системы:

  • GitHub Actions
  • GitLab CI/CD
  • Типовой пайплайн CI для PHP-проекта

    Обычно этапы такие:

  • установка зависимостей через composer install
  • линт и формат (например, PHP-CS-Fixer)
  • статический анализ (PHPStan/Psalm)
  • тесты (PHPUnit)
  • сборка артефакта (если вы деплоите собранный пакет)
  • !Диаграмма помогает увидеть, какие проверки выполняются до мержа и релиза

    Пример GitHub Actions workflow

    Что важно понимать:

  • версия PHP фиксируется (связь со статьёй про окружение)
  • команды запускаются через Composer scripts (единый источник правды)
  • Практика: разделяйте быстрые и медленные проверки

    Чтобы CI не стал «вечно красным и медленным», часто разделяют:

  • быстрые проверки на каждый PR
  • более тяжёлые проверки по расписанию или перед релизом (например, полный набор интеграционных тестов)
  • CD и деплой: как выпускать изменения безопасно

    Термины простыми словами:

  • Continuous Delivery — каждый коммит потенциально готов к релизу, деплой делается кнопкой
  • Continuous Deployment — деплой происходит автоматически после прохождения пайплайна
  • В обоих случаях цель одна: маленькие предсказуемые релизы вместо редких опасных.

    Что такое «артефакт» и зачем он нужен

    Артефакт — это собранный результат (например, архив приложения или Docker-образ), который вы деплоите.

    Зачем:

  • вы деплоите ровно то, что прошло проверки
  • вы уменьшаете шанс «в CI одно, на сервере другое»
  • Конфигурация и секреты

    Базовая практика эксплуатации:

  • конфигурация (DSN, ключи, режимы) задаётся переменными окружения
  • секреты не хранятся в репозитории
  • отличия окружений (dev/stage/prod) не требуют изменения кода
  • Это напрямую продолжает идеи из статьи про окружение.

    Миграции на деплое: самая частая зона риска

    Миграции из статьи про данные становятся частью деплоя. Ошибки здесь приводят к простоям.

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

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

  • Добавить новую колонку, не удаляя старую.
  • Обновить код так, чтобы он писал в обе колонки или умел читать из обеих.
  • Перенести данные фоном.
  • Удалить старую колонку отдельной миграцией позже.
  • Стратегии деплоя, которые стоит знать middle

    | Стратегия | Идея | Плюсы | Минусы | |---|---|---|---| | Rolling update | обновляем серверы по очереди | меньше простоя | нужно поддерживать совместимость версий | | Blue/Green | два окружения, переключение трафика | быстрый откат | дороже по ресурсам | | Canary | малой доле пользователей новая версия | снижает риск | сложнее маршрутизация и наблюдаемость |

    Проверки после деплоя

    Чтобы «деплой прошёл» означало «система работает», нужны проверки.

    Минимальный набор:

  • healthcheck-эндпоинт (например, GET /health), который проверяет базовые зависимости
  • проверка логов на всплеск ошибок
  • метрики времени ответа и ошибок
  • Инструменты наблюдаемости зависят от стека, но источники для понимания:

  • ошибки и алерты часто заводят через Sentry
  • метрики часто собирают через Prometheus
  • Откат (rollback): что это и почему нужно готовить заранее

    Rollback — возврат на предыдущую версию, если новая ведёт себя плохо.

    Middle-практика:

  • откат должен быть быстрым и технически простым
  • если миграция ломает обратную совместимость, откат усложняется, это нужно учитывать заранее
  • Практический чеклист после статьи

  • вы можете объяснить разницу между unit- и интеграционными тестами и выбрать уровень проверки под задачу
  • вы умеете запускать тесты и статический анализ одинаково локально и в CI через Composer scripts
  • вы понимаете, что профилирование начинается с измерений, а не с «перепишем на более быстрый код»
  • вы можете набросать минимальный CI пайплайн для PHP-проекта
  • вы понимаете риски миграций на деплое и базовые стратегии безопасного релиза
  • Дальше в курсе эту тему удобно закреплять мини-проектом: собрать небольшой сервис с API, репозиториями, миграциями и кешем, подключить CI, добавить тесты и провести пробный деплой на staging с измерениями и логами.