Перейти к основному содержимому

5.07. Важные классы, интерфейсы

Разработчику Архитектору

Важные классы и интерфейсы в PHP

PHP, как язык с развитой объектно-ориентированной моделью, предоставляет обширную стандартную библиотеку классов, интерфейсов и трейтов, которые формируют основу для надёжного, расширяемого и сопровождаемого кода. Эти инструменты не являются «украшением» языка — они отвечают на фундаментальные задачи современной разработки: управление состоянием, безопасность, рефлексия, валидация, взаимодействие с внешними системами и организация кодовой базы. Ниже рассматриваются ключевые составляющие стандартной и повседневной практики, без которых невозможно говорить о профессиональном использовании языка.

SPL: библиотека стандартных шаблонов

Standard PHP Library (SPL) — это встроенная в ядро языка коллекция интерфейсов и классов, призванная стандартизировать реализацию распространённых программных паттернов и структур данных. SPL появилась в PHP 5.0 и постепенно вытеснила множество костыльных решений, основанных на процедурных функциях или кастомных реализациях. Её сила — в строгом соответствие интерфейсам, что делает код предсказуемым и совместимым с другими компонентами.

Наиболее часто используемые интерфейсы SPL:

  • Iterator — определяет контракт для объектов, по которым можно проитерироваться с помощью foreach. Реализация этого интерфейса требует наличия пяти методов: current(), key(), next(), rewind() и valid(). Когда объект реализует Iterator, он становится итерируемым объектом первого класса, что позволяет интегрировать его в цепочки обработки, совместимые с массивами. Это особенно ценно при работе с потоковыми или ленивыми структурами данных, когда полное материализование данных в памяти нежелательно.

  • ArrayAccess — позволяет объекту эмулировать поведение массива. После реализации offsetExists(), offsetGet(), offsetSet() и offsetUnset() объект допускает синтаксис $obj['key'] = $value. Это не означает, что объект становится массивом — он лишь приобретает интерфейс, понятный пользователю. Примеры применения: конфигурационные контейнеры, объекты-обёртки над JSON-документами, кэши с ключевым доступом. Критически важно помнить, что ArrayAccess не делает объект совместимым с is_array() или array_*-функциями — это чисто синтаксическое расширение.

  • Countable — обеспечивает поддержку функции count() для объекта. Для этого достаточно реализовать метод count(), возвращающий целочисленное значение. Это особенно полезно, когда класс инкапсулирует коллекцию (например, список элементов в DOM-дереве или результат выборки из базы), и разработчик ожидает, что count($collection) вернёт количество элементов без необходимости вызывать вспомогательный метод вроде getSize().

Помимо интерфейсов, SPL включает готовые реализации:

  • SplStack и SplQueue — структуры данных, реализующие соответственно стек (LIFO) и очередь (FIFO) на основе двусвязного списка (SplDoublyLinkedList). Они предоставляют методы push()/pop() и enqueue()/dequeue(), гарантируют асимптотическую эффективность операций и позволяют избежать ручного управления массивами с array_unshift()/array_pop(), что часто сопряжено с ошибками и неочевидным поведением при больших объёмах данных.

  • DirectoryIterator и RecursiveDirectoryIterator — итераторы для обхода файловой системы. В отличие от функции scandir(), они не загружают список файлов целиком, а предоставляют ленивый доступ, что критично при работе с каталогами, содержащими тысячи элементов. RecursiveDirectoryIterator, в связке с RecursiveIteratorIterator, позволяет строить рекурсивный обход без явного написания рекурсивных функций и риска переполнения стека вызовов.

  • SplObserver и SplSubject — реализация паттерна Наблюдатель на уровне интерфейсов. SplSubject объявляет методы attach(), detach() и notify(), а SplObserverupdate(). Это позволяет строить события и подписки без зависимости от сторонних библиотек. Например, объект «пользователь» может уведомлять подписчиков о смене email, а сервисы почтовой рассылки и аудита — реагировать на это независимо. SPL-реализация минимальна и не включает фильтрацию событий или асинхронные вызовы — для сложных сценариев требуются расширения.

Класс DateTime: управление временем и датой

Работа с датой и временем — одна из самых подводных областей программирования. PHP предлагает объектно-ориентированный класс DateTime, который полностью заменяет устаревшие процедурные функции (date(), mktime() и т.п.) в профессиональной разработке.

Класс DateTime инкапсулирует точку во времени с учётом временной зоны (DateTimeZone), поддерживает арифметику (сложение/вычитание интервалов через DateInterval), сравнение (>, <, ==), форматирование и парсинг строк. Конструктор принимает строку в формате, совместимом с strtotime(), или null для текущего момента:

$date = new DateTime('2025-11-18 15:30:00', new DateTimeZone('Europe/Moscow'));

Метод format() позволяет выводить дату в любом требуемом представлении, включая ISO 8601, RFC 2822 и пользовательские шаблоны. Ключевое преимущество — неизменяемость форматов: строковые шаблоны не зависят от локали системы, в отличие от strftime().

Класс DateTimeImmutable является неизменяемой альтернативой: все методы, изменяющие состояние (например, modify()), возвращают новый объект, а не модифицируют текущий. Это предпочтительный выбор в функциональном стиле и при передаче даты как параметра — исключает побочные эффекты.

Исключения: управление ошибками и аварийными ситуациями

PHP поддерживает механизм исключений (Exception и его наследники), который заменяет процедурный подход с возвратом кодов ошибок. Конструкция trycatchfinally позволяет централизовать обработку непредвиденных ситуаций и отделять логику от её защиты.

Базовый класс Exception содержит сообщение об ошибке (getMessage()), код (getCode()), трассировку стека (getTrace(), getTraceAsString()) и информацию о месте возникновения (getFile(), getLine()). Важно, что в PHP 7 появился иерархический подход: фатальные ошибки (например, вызов несуществующего метода) теперь выбрасываются как Error, который наследуется от Throwable, так же как и Exception. Это позволяет писать catch (Throwable $e) для перехвата любых аварийных ситуаций.

Класс PDOException — специализированный тип исключения для ошибок при работе с базой данных через PDO. Он содержит SQLSTATE-код ошибки ($e->getCode()), что позволяет писать точечную обработку (например, повторная попытка при таймауте соединения, логирование при нарушении уникальности).

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

Рефлексия: анализ кода во время выполнения

Класс ReflectionClass, а также ReflectionMethod, ReflectionProperty, ReflectionFunction и другие — составляют систему рефлексии в PHP. Рефлексия позволяет программно исследовать структуру классов: какие методы и свойства они содержат, какие модификаторы у них (public, private, static), какие параметры принимают методы, какие аннотации (через комментарии DocBlock) присутствуют.

Это используется в:

  • ORM и мапперах — для автоматического сопоставления свойств объекта с полями таблицы;
  • фреймворках маршрутизации — для анализа сигнатур контроллеров и извлечения параметров из URL;
  • сериализаторах — для обхода приватных свойств при преобразовании объекта в JSON;
  • тестовых фреймворках — для вызова приватных методов в unit-тестах.

Разработчик не обязан использовать рефлексию в повседневном коде, но понимание её принципов необходимо, поскольку большинство современных фреймворков и библиотек (Symfony, Laravel, Doctrine) активно полагаются на неё под капотом. Важно: рефлексия — медленная операция. В production-коде её результаты кэшируются (например, в виде массивов метаданных), чтобы не анализировать класс при каждом запросе.

PDO: единый интерфейс для работы с реляционными СУБД

PHP Data Objects (PDO) — это расширение, предоставляющее единый программный интерфейс для взаимодействия с различными реляционными базами данных: MySQL, PostgreSQL, SQLite, SQL Server, Oracle и другими. В отличие от устаревших расширений mysql_* или mysqli, PDO не привязан к конкретной СУБД и позволяет писать переносимый код, меняя только строку подключения (DSN — Data Source Name).

Ключевой принцип PDO — подготовленные запросы (prepared statements). Запрос разделяется на шаблон и параметры. Шаблон отправляется на сервер один раз и компилируется; параметры передаются отдельно при каждом выполнении. Это устраняет риск SQL-инъекций, даже если данные поступают от недоверенного источника, поскольку СУБД интерпретирует параметры строго как значения, а не как исполняемый код.

Пример подготовленного запроса с именованными параметрами:

$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email AND active = :active");
$stmt->execute(['email' => $userInputEmail, 'active' => true]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

Метод fetch() извлекает одну строку, fetchAll() — все. Константа PDO::FETCH_ASSOC указывает, что результат должен возвращаться в виде ассоциативного массива, где ключи — названия столбцов. Альтернативы: FETCH_OBJ (объект stdClass), FETCH_CLASS (инстанцирование указанного класса), FETCH_INTO (заполнение существующего объекта). Последнее особенно ценно при построении ORM.

PDO работает в режиме автоподтверждения транзакций по умолчанию. Для управления транзакциями вручную используются методы beginTransaction(), commit() и rollback(). При возникновении исключения в блоке try транзакцию следует откатывать в секции catch, а финальное подтверждение — в finally, чтобы избежать зависших транзакций.

Важно: PDO не экранирует имена таблиц и столбцов — только значения. Динамическое формирование имён полей или имён таблиц требует валидации по белому списку, иначе остаётся уязвимость.

Фильтрация и санитизация данных

Безопасность веб-приложений начинается с корректной обработки входных данных. PHP предоставляет расширение filter, реализующее стандартизированные методы валидации и санитизации через функцию filter_var() и её аналоги для массивов (filter_input(), filter_var_array()).

Валидация проверяет соответствие данных ожидаемому формату и возвращает булево значение или само значение при успехе, false — при неудаче. Например, FILTER_VALIDATE_EMAIL строго проверяет адрес по RFC 5321/5322, включая проверку домена на существование MX-записи, если включена соответствующая опция. FILTER_VALIDATE_URL учитывает схему, хост, порт, путь и корректность escape-последовательностей.

Санитизация преобразует данные в безопасную форму, удаляя или заменяя потенциально опасные символы. FILTER_SANITIZE_STRING (устаревший, начиная с PHP 8.1) заменял HTML-теги на их текстовое представление. В современных версиях рекомендовано использовать htmlspecialchars() для HTML-контекста и FILTER_SANITIZE_SPECIAL_CHARS, FILTER_SANITIZE_ENCODED или сторонние библиотеки (например, HTML Purifier) для более сложных сценариев.

Критически важно: санитизация не заменяет валидацию. Санитизированный ввод может быть безопасным, но бессмысленным (например, john..doe@example..com после очистки остаётся некорректным email’ом). Правильная последовательность: валидация → санитизация → сохранение/использование.

Расширение filter поддерживает флаги, например FILTER_FLAG_NO_ENCODE_QUOTES для htmlspecialchars()-подобного поведения, или FILTER_FLAG_PATH_REQUIRED для URL. Все операции локально независимы и не требуют подключения сторонних библиотек.

Пространства имён: логическая изоляция кода

С ростом проекта неизбежно возникает коллизия имён: два разработчика создают класс User, один — для учётной записи, другой — для профиля в соцсети. Пространства имён (namespaces) решают эту проблему, вводя иерархическую систему имён, аналогичную файловой структуре.

Объявление namespace App\Database; в начале файла означает, что все объявленные в нём классы, интерфейсы и функции будут находиться в пространстве App\Database. Полное имя класса становится App\Database\Connection, что уникально в глобальном контексте.

Директива use создаёт локальный псевдоним (по умолчанию — последний сегмент имени), позволяя писать new Connection() вместо new \App\Database\Connection(). Псевдонимы могут быть произвольными: use App\Database\Connection as DBConn.

Пространства имён не влияют на физическое размещение файлов, но PSR-4, принятый стандарт автозагрузки, устанавливает соответствие: пространство App\Database соответствует директории src/Database/, а класс Connection — файлу Connection.php. Это позволяет менеджеру зависимостей Composer автоматически генерировать автозагрузчик.

PHP не разрешает объявление нескольких пространств имён в одном файле (кроме анонимных блоков через { … }, что не рекомендуется). Глобальное пространство имён обозначается как \, например \strlen() — прямой вызов функции, избегая конфликта с App\strlen().

Трейты: горизонтальное повторное использование кода

PHP не поддерживает множественное наследование классов: класс может наследоваться только от одного родителя. Однако часто требуется повторно использовать поведение, а не интерфейс или состояние. Для этого служат трейты (traits).

Трейт — это набор методов, который может быть внедрён в любой класс с помощью use ИмяТрейта. Методы трейта становятся методами класса, имеют доступ к $this, могут вызывать приватные методы и свойства класса. Это не композиция через агрегацию — это копирование методов на этапе компиляции.

Важные особенности:

  • Если класс и трейт содержат метод с одинаковым именем, метод класса имеет приоритет.
  • Если два трейта предоставляют метод с одинаковым именем, требуется явное разрешение конфликта через insteadof или создание алиаса через as.
  • Трейты могут использовать другие трейты, наследоваться от интерфейсов (trait T implements I), но не могут быть инстанцированы.

Типичные сценарии: логирование (Loggable), временные метки (Timestampable), сериализация в массив (Arrayable), проверка доступа (Authorizable). Трейты не должны содержать состояние — только поведение. Хранить данные в свойствах трейта допустимо, но требует осторожности, так как состояние будет разделяться между всеми классами, его использующими.

Генераторы: ленивые вычисления и экономия памяти

Функция, содержащая хотя бы один оператор yield, становится генератором. В отличие от обычной функции, генератор не возвращает значение напрямую и не завершает своё выполнение после return. Вместо этого каждый yield приостанавливает выполнение и возвращает одно значение вызывающему коду. При следующей итерации выполнение возобновляется с места остановки.

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

function readLines($filename) {
$handle = fopen($filename, 'r');
while (($line = fgets($handle)) !== false) {
yield trim($line);
}
fclose($handle);
}

foreach (readLines('huge.log') as $line) {
// обработка одной строки; в памяти — только текущая строка
}

Генераторы совместимы с foreach, iterator_to_array(), и могут быть цепочечно преобразованы с помощью array_map()-подобных итераторов (например, CallbackFilterIterator). Они являются частным случаем Iterator, но требуют значительно меньше кода для реализации.

Следует избегать побочных эффектов в генераторах, так как порядок и количество вызовов yield определяется потребляющим кодом. Генераторы не могут быть клонированы, а их внутреннее состояние не подлежит сериализации.

Аутентификация: сессии и их ограничения

PHP предоставляет встроенную поддержку сессий через расширение session. Функция session_start() инициирует или возобновляет сессию, после чего переменная $_SESSION становится доступной как суперглобальный массив, сохраняющий своё состояние между запросами.

Простейшая аутентификация, как в примере, подразумевает проверку учётных данных и установку флага в $_SESSION. Однако такой подход имеет критические ограничения:

  • Хранение пароля в открытом виде ('123') недопустимо. Требуется хеширование (password_hash()) и верификация (password_verify()).
  • Отсутствие защиты от перебора (brute-force). Необходимы задержки, капчи, блокировки после N попыток.
  • Уязвимость к атакам с перехватом сессионного ID (session hijacking). Решения: session_regenerate_id() при входе, session.cookie_httponly, session.cookie_secure, session.cookie_samesite.
  • Отсутствие механизма выхода (session_destroy() + очистка куки).

Современные приложения выносят аутентификацию в отдельные компоненты (например, OAuth2-провайдеры), а сессии используют только для хранения минимального контекста (ID пользователя, роль), проверяя его на каждом запросе.

Механизмы завершения работы скрипта

PHP-скрипт может завершиться по разным причинам: нормальное окончание, явный вызов exit()/die(), фатальная ошибка, сигнал ОС (SIGTERM), истечение лимита времени выполнения. Для гарантированного выполнения кода в любом случае предусмотрены три механизма:

  1. Блок finally — выполняется после trycatch, даже если в catch вызван exit(). Подходит для локального освобождения ресурсов (файлы, сокеты, транзакции).

  2. Функция register_shutdown_function() — регистрирует callback, который будет вызван при любом завершении скрипта, включая аварийные. Вызов exit() не отменяет его выполнение. Это основной инструмент для глобального логгирования ошибок, отправки метрик, закрытия внешних соединений (например, буферизованной записи в файловую систему или очередь сообщений).

  3. Деструкторы (__destruct) — вызываются при уничтожении объекта. Однако при аварийном завершении (например, exit() в середине скрипта) сборщик мусора может не успеть вызвать деструкторы всех объектов. Поэтому __destruct не следует использовать для критически важных операций.

Важно: код в register_shutdown_function() не имеет доступа к необъявленным переменным из глобальной области видимости. Передавать данные следует через замыкание или глобальные переменные.

Магические методы: динамическое поведение объектов

Магические методы — это специальные методы с префиксом __, которые интерпретатор вызывает в ответ на определённые события. Они не являются частью публичного интерфейса класса, но позволяют эмулировать сложное поведение.

  • __get($name) и __set($name, $value) — вызываются при доступе к несуществующему или недоступному (например, приватному) свойству. Это основа для реализации геттеров/сеттеров по требованию, ленивой инициализации свойств, прокси-объектов и ORM-маппинга. Важно проверять корректность $name и не зацикливаться (например, избегать прямого $this->name = $value внутри __set, если свойство не объявлено).

  • __call($name, $arguments) и __callStatic($name, $arguments) — срабатывают при вызове несуществующего метода экземпляра или статического метода соответственно. Используются для создания fluent-интерфейсов, делегирования вызовов, эмуляции динамических методов (findByEmail(), findByLogin() без явного объявления).

  • __toString() — вызывается при попытке преобразовать объект в строку (например, в echo $obj). Обязан возвращать строку; иначе — фатальная ошибка.

  • __invoke() — позволяет вызывать объект как функцию: $obj(). Применяется для создания callable-объектов, замыканий с состоянием, миддлваров.

  • __sleep() и __wakeup() — управляют сериализацией: __sleep() возвращает список свойств для сохранения, __wakeup() восстанавливает состояние после десериализации.

Магические методы мощны, но требуют осторожности: они скрывают логику, затрудняют статический анализ (например, IDE не подскажет метод calcTotal, если он реализован через __call), и могут снижать производительность. Их следует использовать только там, где явное объявление невозможно или приведёт к избыточности.


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

Composer — это де-факто стандартный менеджер зависимостей для PHP. Он решает две ключевые задачи: управление версиями сторонних библиотек и автоматическая загрузка классов (autoloading) в соответствии со стандартами PSR-0 и PSR-4.

Файл composer.json описывает проект: его имя, версию, зависимости (в секции require), автозагрузочные правила (autoload), скрипты жизненного цикла и метаданные. При выполнении composer install или composer update Composer:

  1. Разрешает зависимости рекурсивно, строя дерево совместимых версий (с учётом ограничений в ^, ~, *);
  2. Скачивает пакеты в директорию vendor/;
  3. Генерирует единый автозагрузчик vendor/autoload.php, который регистрирует функцию spl_autoload_register() и сопоставляет пространства имён с путями к файлам.

Преимущества явного объявления зависимостей:

  • Воспроизводимость: composer.lock фиксирует точные версии всех пакетов, включая транзитивные;
  • Изоляция: каждая библиотека находится в своём подкаталоге, конфликты имён исключены;
  • Совместимость: проверка требований к PHP-версии, расширениям (ext-json, ext-mbstring и т.д.) происходит до установки.

Composer не является частью ядра PHP, но его использование стало обязательным в профессиональной разработке. Отказ от Composer в пользу ручного копирования файлов (require_once "lib/SomeClass.php") нарушает принципы сопровождаемости и масштабируемости.

Фоновые задачи: границы синхронной модели

PHP изначально проектировался как язык для обработки короткоживущих HTTP-запросов. Поэтому он является синхронным и однопоточным — один процесс обрабатывает один запрос от начала до конца. Однако реальность требует выполнения длительных операций: отправки почты, обработки медиа, синхронизации данных.

Существует несколько подходов к реализации фоновой обработки:

  1. exec(), shell_exec(), proc_open() — запуск отдельного PHP-процесса через системный shell. Конструкция

    exec("php background-task.php > /dev/null 2>&1 &");

    отправляет задачу в фон (& в конце), перенаправляет stdout и stderr в /dev/null, чтобы не блокировать родительский процесс. Это простой способ, но ненадёжный: нет контроля за состоянием задачи, отсутствует повторная попытка при падении, сложно масштабировать.

  2. Очереди сообщений (RabbitMQ, Apache Kafka, Redis Streams) — архитектурно правильное решение. Веб-запрос помещает задание в очередь (например, JSON-сообщение {"type":"send_email","to":"user@example.com"}), а отдельные worker-процессы (демоны) извлекают и выполняют их. Преимущества: отказоустойчивость, приоритизация, отложенное выполнение, мониторинг, горизонтальное масштабирование. PHP-клиенты для очередей (php-amqplib, enqueue) предоставляют объектно-ориентированный API.

  3. Cron-задачи — запуск скриптов по расписанию через системный планировщик. Подходит для периодических операций (очистка логов, расчёт статистики), но не для реактивных действий.

Важно: фоновая задача должна быть идемпотентной — повторный запуск не должен приводить к дублированию эффектов. Для этого используются уникальные ID задач (message_id), блокировки на уровне СУБД (SELECT ... FOR UPDATE) или атомарные операции (например, SETNX в Redis).

Работа с файлами: потоки и контексты

PHP предоставляет как процедурные функции (file_get_contents(), fopen()), так и объектно-ориентированные классы (SplFileObject, Phar, ZipArchive). Однако ключевая особенность — потоковая абстракция (streams).

Поток — это ресурс, через который можно читать/писать данные, независимо от их источника: локальный файл (file://), HTTP-ресурс (http://), сжатый архив (compress.zlib://), память (php://memory), временный буфер (php://temp), стандартный ввод/вывод (php://stdin, php://stdout).

Функция file_get_contents() работает с файлами и с URL (если включено allow_url_fopen):

$content = file_get_contents('https://api.example.com/data.json');

Более гибкий контроль достигается через контексты потоков (stream_context_create()). Контекст позволяет задать:

  • таймауты соединения и чтения;
  • заголовки HTTP (User-Agent, Authorization);
  • метод запроса (POST, PUT);
  • тело запроса;
  • SSL-опции (проверка сертификата, клиентские ключи).

Пример POST-запроса с JSON-телом:

$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => json_encode(['name' => 'Тест']),
'timeout' => 10
]
]);
$response = file_get_contents('https://example.com/api', false, $context);

Для обработки больших файлов рекомендуется использовать fopen() + fread()/fwrite() в цикле, чтобы избежать загрузки всего содержимого в память. Класс SplFileObject предоставляет итерабельный интерфейс для построчного чтения с поддержкой seek(), current(), key().

Типизация в PHP 7+: надёжность без потери гибкости

Начиная с PHP 7.0, язык получил строгую и слабую типизацию скалярных типов (int, string, bool, float), а с PHP 7.1 — тип void и nullable-типы (?string). PHP 7.4 добавил типизированные свойства, PHP 8.0 — union-типы (string|int), PHP 8.1 — enums и readonly-свойства, PHP 8.2 — readonly class.

Типизация — не синтаксический сахар. Она:

  • Позволяет обнаруживать ошибки на этапе выполнения (до передачи некорректных данных в СУБД или API);
  • Улучшает читаемость кода: сигнатура метода становится самодокументируемой;
  • Ускоряет выполнение: движок может оптимизировать доступ к типизированным свойствам (например, избегать проверок на isset);
  • Интегрируется со статическими анализаторами (PHPStan, Psalm), позволяя выявлять ошибки до запуска.

Пример:

class Order
{
public function __construct(
private readonly int $id,
private string $status,
private ?DateTimeImmutable $shippedAt = null
) {}

public function setStatus(string $status): self
{
$this->status = $status;
return $this;
}
}

Здесь readonly гарантирует неизменяемость после инициализации, ?DateTimeImmutable явно указывает, что свойство может быть null, а возвращаемый тип self поддерживает fluent-интерфейс.

Важно: PHP остаётся динамически типизированным языком. Даже при объявлении string $name переменная может быть приведена к строке автоматически (в слабом режиме), если передано число. Для запрета неявных приведений используется declare(strict_types=1); в начале файла — тогда передача 42 вместо '42' вызовет TypeError.

Практические рекомендации по выбору инструментов

При разработке на PHP целесообразно придерживаться следующих принципов:

  • Используйте SPL, когда задача попадает под её шаблоны. Не пишите свой стек на массивах — SplStack уже протестирован, документирован и оптимизирован. Не реализуйте Iterator вручную, если подходит ArrayIterator или CallbackFilterIterator.

  • Предпочитайте DateTimeImmutable над DateTime. Изменяемые объекты даты — частый источник скрытых багов, особенно при кэшировании или передаче по ссылке.

  • Всегда применяйте подготовленные запросы через PDO. Конкатенация строк для SQL — неприемлема в production-коде. Даже при «полной уверенности» в источнике данных — требования безопасности требуют единообразия.

  • Валидируйте до санитизации. Санитизация — последняя линия защиты, а не средство исправления невалидных данных.

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

  • Зависимости — только через Composer. Ручное управление библиотеками нарушает принцип «явное лучше неявного» и затрудняет обновление.

  • Фоновые задачи — через очереди. Даже если сейчас достаточно exec(), архитектурный долг быстро станет критическим при росте нагрузки.

  • Включайте strict_types=1 во всех новых файлах. Это инвестиция в стабильность и предсказуемость.