Рекомендации по разработке на PHP
Рекомендации по разработке на PHP
1. Общие принципы разработки
Разработка программного обеспечения требует не только технических навыков, но и грамотной организации, планирования и коммуникации. В современной разработке применяются различные подходы, принципы, паттерны и методологии. При этом в большинстве организаций придерживаются определённой стратегии развития, особенно в сфере корпоративного ПО.
1.1. Понимание задачи
Любая разработка начинается с задачи. Это может быть запрос от заказчика, идея собственного проекта или поручение внутри команды. Первый и один из самых важных шагов — правильное понимание требований:
- Уточняйте требования, задавайте вопросы, исключайте двусмысленность
- Определяйте цели — чего хочет заказчик, какова основная функция
- Собирайте контекст — узнайте о существующих ограничениях
Не стесняйтесь спрашивать у коллег, особенно у аналитиков задачи. Иногда задание может быть расплывчатым или противоречивым. В таких случаях необходим предварительный анализ и согласование условий до начала проектирования и реализации.
1.2. Стратегия разработки и оценка затрат
После понимания задачи наступает время стратегического планирования. Этот этап определяет дальнейший ход действий и помогает ответить на ключевые вопросы:
- Сколько времени потребуется на реализацию
- Какие ресурсы будут задействованы
- Какой бюджет необходим
Для ответа на эти вопросы используют методы оценки трудозатрат, анализ рисков и учитывают выбранную модель жизненного цикла разработки. Точная оценка на ранних этапах позволяет избежать недоразумений и перерасхода ресурсов позже.
1.3. Предварительное планирование
Организация процесса — залог успешной реализации любого проекта. Даже самые талантливые разработчики могут потерпеть неудачу, если работа плохо спланирована или не контролируется.
Перед началом кодирования выполните предварительное проектирование. Определите данные, логику, интерфейс, способы обмена данными, поток движения информации внутри системы. Напишите крупные этапы работы, разбейте их на задачи, декомпозируйте алгоритмически. Определите имена сущностей, элементов, переменных.
Документация поможет не забыть о внесённых изменениях, какие были названия у сущностей и элементов, и позволит избежать проблем в будущем.
1.4. Процесс написания кода
Это центральный этап разработки. Код должен быть:
- Читаемым — другие разработчики должны понимать вашу логику
- Поддерживаемым — добавление новых функций не должно вызывать сложностей
- Тестируемым — код должен быть разделён на модули, готовые к проверке
Соблюдение стандартов кодирования, написание комментариев и документирование функций значительно облегчает жизнь всем участникам проекта.
Перед началом работы проверьте готовность инструментов и окружения — текстовый редактор, системы сборки и зависимости, доступ к серверам, базам данных, внешним API. Правильно настроенная рабочая среда экономит время и снижает количество проблем.
1.5. Сдача разработки
Когда план реализован и разработка завершена, важно учесть дополнительные моменты — время и ресурсы включают не только разработку. Сюда входит предварительное тестирование силами самого разработчика, подготовка и проверка тестов, рефакторинг, отладка, исправление найденных ошибок.
Перепроверьте код. Прочитайте его, убедитесь в корректности, аккуратности и соответствии регламентам. Проверьте, работает ли он, протестируйте. И только когда вы убедились, что всё работает как надо и готово, сдавайте задачу.
2. Требования по именованию
2.1. Нотация для элементов языка
| Элемент языка | Нотация | Пример |
|---|---|---|
| Класс, интерфейс | PascalCase | AppDomain |
| Перечисление (тип) | PascalCase | ErrorLevel |
| Перечисление (значение) | PascalCase | FatalError |
| Константа класса | PascalCase | MAX_ITEMS |
| Приватное свойство | camelCase | _listItem |
| Защищённое свойство | camelCase | mainPanel |
| Публичное свойство | camelCase | backgroundColor |
| Переменная | camelCase | listOfValues |
| Метод | camelCase | toString |
| Пространство имён | PascalCase | App\Services |
| Параметр | camelCase | typeName |
| Свойство | camelCase | backgroundColor |
2.2. Избегайте коротких имен
Несмотря на то, что с технической точки зрения короткие имена могут выглядеть корректно, они легко могут ввести в заблуждение:
$boolValue = ($lo == $l0) ? ($I1 == $11) : ($lOl != $101);
Разбор:
- В примере переменные отличаются только похожими символами (
l,I,1,0,O), из-за чего чтение становится рискованным. - Тернарный оператор технически корректен, но не раскрывает смысл предметной логики.
- Ревьюеру сложно понять, где опечатка, а где намеренное имя, поэтому вероятность дефекта резко растёт.
- Главный вывод: синтаксис может быть валидным, но плохие имена делают код почти непроверяемым.
Лучше давать осмысленные имена переменным — код выглядит чуть более громоздким, зато читаемость повышается значительно:
$isPublished = ($positionModificationCode == PositionDecision::PURCHASE_CANCELED)
? ($commonIKZ == $checkCommonIKZ)
: ($commonPositionNumber !== null);
Разбор:
$isPublishedсразу указывает, что выражение вычисляет булево состояние публикации.PositionDecision::PURCHASE_CANCELEDзадаёт понятный бизнес-контекст вместо "магического" значения.- В ветке
?идёт проверка согласованности кодов, в ветке:— строгая проверка на наличие номера. - Код остаётся компактным, но уже читается как правило предметной области.
2.3. Названия свойств
- Именуйте свойства с использованием существительных или словосочетаний с существительными
- Называйте свойство логического типа с использованием утвердительных фраз. Например,
canSeekвместоcantSeek - В наименованиях свойств логического типа используйте приставки
is,has,can,allowsилиsupports
2.4. Названия методов
Называйте методы с использованием связки глагол-существительное. Хороший пример — showDialog.
Хорошее имя должно давать подсказку, что делает этот метод и, если возможно, почему. Не используйте слово And в названии метода.
2.4.1. Асинхронные методы
Добавляйте суффикс Async к названиям асинхронных методов. Общие требования для методов, которые возвращают Promise — добавлять к ним суффикс Async.
2.5. Цепочки методов
Если подряд вызывается больше одного метода, выносите вызов каждого на отдельную строку. Точка ставится перед методом:
public function getNames(): array
{
return $this->dataContext
->contacts()
->where(fn($contact) => $contact->name->startsWith('Super'))
->loadWith(fn($contact) => $contact->contactCommunication)
->toArray();
}
Разбор:
- Возвращаемый тип
: arrayфиксирует контракт метода и уменьшает неоднозначность. - Цепочка вызовов по строкам показывает pipeline: источник данных → фильтрация → подгрузка связей → финальный массив.
fn($contact) => ...использует стрелочную функцию для лаконичного условия.toArray()материализует коллекцию, чтобы вызывающий код получил готовый массив.
2.6. Тернарные выражения
Записывайте тернарное выражение в три строки. Если они вложены, отделяйте табуляцией:
public function getValidationMessage(string $name): string
{
return empty($name)
? 'Empty name'
: strlen($name) < 3
? 'Name too short'
: 'Ok';
}
Разбор:
- Метод получает
stringи всегда возвращаетstring, поэтому поведение полностью определено сигнатурой. - Первый тернарный уровень обрабатывает пустое имя через
empty($name). - Второй уровень проверяет минимальную длину (
strlen($name) < 3). - Последняя ветка
'Ok'возвращается только если обе проверки пройдены.
2.7. Избегайте остроумия
Если имена будут излишне остроумными, их смысл будет понятен только людям, разделяющим чувство юмора автора — и только если они помнят шутку.
3. Требования по оформлению
3.1. Общие правила оформления
- В качестве отступов используйте 4 пробела
- Вставляйте один пробел между выражениями и ключевыми словами
- Не используйте пробелы после
(и перед) - Добавляйте пробел перед и после операторов (
+,-,==) - Открывайте и закрывайте парные скобки всегда в новой строке
- Добавляйте пустую строку между многострочными выражениями, членами класса, после закрытия парных скобок
3.2. Стиль Олмана
Используйте стиль Олмана при расставлении фигурных скобок:
- Открывающая фигурная скобка располагается на новой строке с тем же отступом, что и выражение на предшествующей строке
- Первое выражение внутри фигурных скобок располагается на новой строке с отступом 4 пробела
- Последующие выражения внутри фигурных скобок располагаются с тем же отступом
- Закрывающая фигурная скобка располагается с отступом, равным отступу соответствующей открывающей фигурной скобке
Пример кода, отформатированного в стиле Олмана:
if ($condition) {
$body;
}
Разбор:
- Пример демонстрирует единый стиль скобок для управляющих конструкций.
- Даже короткий
ifоформлен блоком, что снижает риск ошибок при будущих правках. - В командной разработке такой формат упрощает форматирование и диффы.
- Чтение кода ускоряется, потому что визуальная структура предсказуема.
4. Требования по проектированию классов
4.1. Принцип единственной ответственности
Класс или интерфейс должен иметь единственное предназначение в рамках системы. Как правило, класс служит одной из целей — описывает тип, представляет абстракцию бизнес-логики, описывает структуру данных или отвечает за взаимодействие между другими классами.
Класс со словом And в названии — это явное нарушение данного правила.
Для взаимодействия между классами используйте паттерны проектирования. Если не удаётся применить ни один из паттернов к классу, возможно, он берёт на себя слишком большую ответственность.
4.2. Создание экземпляров класса
Создавайте новые экземпляры класса с помощью конструктора таким образом, чтобы в результате получать полностью готовый к использованию объект. Созданный объект не должен нуждаться в установке дополнительных свойств перед использованием.
4.3. Использование интерфейсов
Используйте интерфейс, а не базовый класс, чтобы поддерживать несколько реализаций. Если вы хотите выставить точку расширения класса, выставляйте её в качестве интерфейса, а не базового класса.
Для удобства можно создать реализацию по умолчанию (абстрактный класс), которая может служить в качестве отправной точки.
4.4. Слабая связанность
Используйте интерфейсы для реализации слабой связанности между классами:
- Они помогают избежать двунаправленной связанности
- Они упрощают замену одной реализации другой
- Они позволяют заменить недоступный внешний сервис временной заглушкой
- Они позволяют заменить текущую реализацию фиктивной при модульном тестировании
- Используя фреймворк для внедрения зависимостей, можно собрать в одном месте логику выбора класса
4.5. Избегайте статических классов
За исключением статических классов, которые используются для создания методов расширения, статические классы очень часто приводят к плохому коду. Их очень сложно, если вообще возможно, тестировать в изоляции.
4.6. Не скрывайте унаследованные элементы
Не скрывайте унаследованные элементы за ключевым словом new. Это противоречит полиморфизму — одному из самых важных принципов объектно-ориентированного программирования, и делает дочерние классы трудными для понимания.
4.7. Не ссылайтесь на производные классы из базового
Наличие зависимостей в родительском классе от его дочерних классов нарушает принципы объектно-ориентированного программирования и не даёт возможности другим разработчикам наследоваться от базового класса.
4.8. Принципы SOLID
При проектировании классов придерживайтесь принципов SOLID:
- S — Принцип единой ответственности (SRP)
- O — Принцип открытости/закрытости (OCP)
- L — Принцип подстановки Лисков (LSP)
- I — Принцип разделения интерфейсов (ISP)
- D — Принцип инверсии зависимостей (DIP)
4.9. Избегайте двунаправленной зависимости
Двунаправленная зависимость означает, что два класса знают о публичных методах друг друга или зависят от внутреннего поведения друг друга. Рефакторинг или замена одного из этих двух классов требует изменений в обоих классах.
Наиболее очевидное решение — создание интерфейса для одного из этих классов и использование внедрения зависимостей.
5. Требования по проектированию членов класса
5.1. Свойства класса
Свойства класса должны иметь возможность быть установленными в любом порядке. Свойства не должны зависеть от других свойств. Не должно быть разницы в том, какое свойство устанавливается в первую очередь.
5.2. Методы вместо свойств
Используйте метод вместо свойства если:
- Производится более дорогостоящая работа, чем настройка значения поля
- Свойство представляет собой конвертацию. Например, метод
toString - Свойство возвращает различные значения для каждого вызова, даже если аргументы при этом не изменяются
- Использование свойства вызывает побочный эффект
5.3. Возвращаемые значения
Методы никогда не должны возвращать null в случае, если метод возвращает тип, производный от iterable. Всегда возвращайте пустую коллекцию или пустую строку вместо нулевой ссылки.
6. Требования по проектированию методов
6.1. Проверка аргументов
Проверяйте аргументы конструкторов и публичных методов. Проверка аргументов для конструкторов обязательна всегда, кроме случаев, когда аргумент прокидывается в базовый конструктор.
public class A
{
private string $_nameA;
public function __construct(string $nameA)
{
if (empty($nameA)) {
throw new InvalidArgumentException('Name cannot be empty');
}
$this->_nameA = $nameA;
}
}
Разбор:
- Конструктор валидирует вход до записи в состояние объекта.
empty($nameA)отсекает недопустимое значение на ранней стадии.throw new InvalidArgumentException(...)явно сообщает о нарушении контракта.- После проверки поле инициализируется, и объект остаётся в корректном состоянии.
Исключение при проверке аргументов публичных методов: аргументы можно не проверять, если единственное назначение метода — вызвать перегруженный метод с дефолтным параметром.
Не забывайте, что проверка аргументов — это не только проверка на null, но ещё и на недопустимые значения.
6.2. Генерация исключений
Генерируйте исключение вместо возвращения статусного сообщения. Кодовая база, которая использует возвращаемое статусное сообщение для определения завершилась ли операция успешно, зачастую имеет вложенные условия, разбросанные по всему коду. Зачастую пользователи забывают проверить возвращаемое значение.
Структурированная обработка исключений позволяет генерировать исключения и отлавливать или заменять их на более высоком уровне. В большинстве систем является распространённой практикой генерировать исключения всякий раз, когда происходит неожиданная ситуация.
6.3. Сообщения об исключениях
Обеспечьте полное и осмысленное сообщение об исключении. Если выбрасываете исключение и заполняете сообщение, указывайте осмысленное сообщение и постарайтесь указать в нём все значения входных параметров/локальных переменных, чтобы по логам можно было понять, почему исключение возникло.
6.4. Обработка общих исключений
Не игнорируйте ошибку путём обработки общих исключений с пустым catch. Только обработчик ошибок самого верхнего уровня должен отлавливать общие исключения с целью логирования и корректного завершения работы приложения.
6.5. Логирование исключений
При логировании исключения всегда используйте полную информацию. Для этого используйте метод __toString() у класса исключения или неявное приведение к типу string.
try {
// ...
} catch (Exception $ex) {
// Правильно
$logger->error("Unable to touch the stars: " . $ex->__toString());
// Тоже правильно
$logger->error("Unable to touch the stars: " . $ex);
// Неправильно
// $logger->error("Unable to touch the stars: " . $ex->getMessage());
}
Разбор:
- Блок
try/catchперехватывает исключение и отправляет его в лог. $ex->__toString()и неявное приведение$exвключают стек вызовов и контекст.- Вариант только с
getMessage()теряет важные детали расследования. - Такой подход повышает диагностируемость ошибок в production.
6.6. Проверка делегатов событий
Перед вызовом события убедитесь, что список делегатов, представляющих это событие, не равен null. Чтобы избежать конфликтов при изменении из параллельных потоков, используйте временную переменную.
6.7. Возвращаемые типы коллекций
Метод не должен возвращать iterable. Потому что вызывающий код может очень легко нарваться на ошибку возможного многократного перечисления.
Если метод должен вернуть коллекцию, выбирайте из следующих вариантов:
- Неизменяемые типы — массивы,
ArrayObject - Изменяемые типы —
ArrayList
Исключение: если в методе используется конструкция yield. В этом случае допускается использовать iterable в качестве возвращаемого значения, но название метода должно начинаться с enumerate:
public function enumerateSuitableContracts(): iterable
{
$recentContracts = $this->dataContext
->contracts()
->where(fn($contract) => $contract->createdOn >= (new DateTime())->modify('-7 days'));
foreach ($recentContracts as $contract) {
$isSuitable = $this->getIsContractSuitable($contract);
if ($isSuitable) {
yield $contract;
}
}
}
Разбор:
iterableздесь оправдан, потому что метод используетyieldи возвращает генератор.- Сначала формируется выборка за последние 7 дней, затем каждый элемент проходит доменную проверку.
yield $contractотдаёт элементы лениво, без загрузки всей коллекции в память.- Префикс
enumerateв названии подсказывает вызывающему коду характер результата.
7. Требования по улучшению сопровождаемости кода
7.1. Магические числа
Не используйте литеральные значения, числа или строки в коде ни для чего другого, кроме как для объявления констант.
class Whatever
{
public const MAX_NUMBER_OF_WHEELS = 18;
public const COLOR_PAPAYA_WHIP = '#FFEFD5';
}
Разбор:
- Константы выносят фиксированные значения из рабочей логики.
- Имена констант объясняют смысл лучше, чем "голые" литералы в коде.
public constделает значения доступными без создания экземпляра класса.- Подход упрощает поддержку и централизует изменение параметров.
Строки, предназначенные для логирования или трассировки, являются исключением из этого правила. Литеральные значения допускается использовать только тогда, когда их смысл ясен из контекста и их не планируется изменять.
Если значение одной константы зависит от значения другой, укажите это в коде:
class SomeSpecialContainer
{
public const MAX_ITEMS = 32;
public const HIGH_WATER_MARK = self::MAX_ITEMS * 3 / 4; // 75%
}
Разбор:
HIGH_WATER_MARKвычисляется отMAX_ITEMS, поэтому связь между лимитами формализована.- Выражение
self::MAX_ITEMS * 3 / 4задаёт порог заполнения как 75%. - При изменении базового лимита зависимая константа обновляется автоматически.
- Комментарий
// 75%помогает быстро сверить математику при чтении.
7.2. Присваивание значений переменным
Присваивайте значение каждой переменной в отдельном объявлении. Никогда не делайте так:
$result = $someField = $this->getSomeMethod();
Разбор:
- Это цепочное присваивание записывает одно значение в две переменные сразу.
- Конструкция сокращает количество строк, но усложняет отладку и рефакторинг.
- При чтении неочевидно, какая переменная является основной, а какая производной.
- Отдельные присваивания обычно читаются и поддерживаются лучше.
7.3. Сравнение логических значений
Не производите явного сравнения с true или false. Сравнение логического значения с true или false — это плохой стиль программирования.
while ($condition == false) // неправильно
while ($condition != true) // тоже неправильно
while ($condition) // правильно
Разбор:
- Первые две строки делают избыточные сравнения булевого выражения с литералами.
- В условии цикла PHP и так ожидает boolean-контекст.
- Короткая форма
while ($condition)передаёт намерение напрямую. - Меньше синтаксического шума — быстрее ревью и ниже риск ошибок.
7.4. Фигурные скобки в управляющих конструкциях
Всегда используйте конструкции if, else, while, for, foreach и case с фигурными скобками.
if ($b1) {
if ($b2) {
foo();
} else {
bar();
}
}
7.5. Блок default в switch
Если блок default будет пуст, добавьте поясняющий комментарий. Если этот блок не должен быть достижимым, сгенерируйте при его вызове LogicException, чтобы обнаружить будущие изменения.
Код ITЗагрузка примера кода…
Разбор:
switchобрабатывает фиксированный набор значений входного параметра.defaultне оставлен пустым: неожиданный вход сразу приводит к исключению.LogicExceptionпомогает поймать нарушение инвариантов на раннем этапе.- Сообщение включает
$answer, чтобы ускорить диагностику.
7.6. Завершение блока if-else-if
Заканчивайте каждый блок if-else-if объявлением else:
function foo(string $answer): void
{
if ($answer == 'no') {
echo 'Вы ответили Нет';
} else if ($answer == 'yes') {
echo 'Вы ответили Да';
} else {
// Что должно случиться, когда этот блок выполнится?
// Если нет, то сгенерировать исключение LogicException.
}
}
Разбор:
- Ветка
elseзакрывает все неучтённые сценарии, а не оставляет их "молчаливыми". - Такая структура полезна в бизнес-правилах, где важно явно фиксировать неожиданные входы.
- Комментарий в примере подводит к идее выбрасывать
LogicException. - Код с полным ветвлением проще тестировать по сценарию "известные/неизвестные значения".
7.7. Условное присваивание
Не используйте блок if-else вместо простого (условного) присваивания. Выражайте свои намерения прямо:
// Вместо этого:
$pos = false;
if ($val > 0) {
$pos = true;
}
// Делайте так:
$pos = ($val > 0);
Разбор:
- Первый вариант разносит простое булево вычисление на несколько строк.
- Во втором варианте условие сразу присваивается переменной.
($val > 0)уже возвращаетtrueилиfalse, поэтому дополнительныйifне нужен.- Итог — меньше кода при том же поведении.
7.8. Инкапсуляция повторяющегося кода
Следуйте принципу Don't Repeat Yourself. Если код повторяется 2 и более раз, выносите его в отдельный метод.
7.9. Настройки приложения
Не хардкодьте настройки приложения — строки подключения, адреса серверов и т.д. Используйте файлы конфигурации, переменные окружения или специальные классы для хранения настроек.
7.10. Выражения запросов
Не используйте операторы запросов кроме случаев, когда выражение можно записать только с их помощью. Вместо:
$query = from($items)->where(fn($item) => $item->length > 0);
Лучше воспользоваться методами расширений:
$query = $items->where(fn($i) => $i->length > 0);
7.11. Лямбда-выражения
Используйте лямбда-выражения вместо анонимных функций. Лямбда-выражения служат более красивой альтернативой анонимным функциям.
7.12. Ключевое слово mixed
Используйте тип mixed только при работе с динамическими данными. Их использование создает серьёзный затор производительности, поскольку компилятор вынужден сгенерировать некоторое количество дополнительного кода.
Используйте mixed только при обращении к членам динамически созданных экземпляров класса или при работе с данными из внешних источников. Обязательно снабдите код с типом данных mixed подробным комментарием с обоснованием его использования.
8. Требования к методам
8.1. Количество параметров
В методе не должно быть более 7 входящих значений. Метод, который включает в себя более 7 входящих значений, скорее всего делает слишком много или берёт на себя слишком большую ответственность.
Разделите метод на несколько маленьких, имеющих чёткое предназначение, и дайте им имена, которые будут точно указывать на то, что они делают.
8.2. Двойное отрицание
Избегайте двойного отрицания. Двойное отрицание более сложно для понимания, чем простое выражение.
8.3. Параметры byRef
Не используйте передачу параметров по ссылке в параметрах. Они делают код менее понятным и создают предпосылки для ошибок. Вместо этого возвращайте составные объекты в качестве результата выполнения функции.
8.4. Порядок членов класса
Располагайте члены класса в строго определённом порядке:
- Приватные свойства, константы и приватные методы
- Публичные константы
- Публичные статические свойства
- Фабричные методы
- Конструкторы
- События
- Публичные свойства
- Прочие методы
Сохранение общего порядка позволяет другим членам команды легче ориентироваться в коде.
8.5. Инкапсуляция сложных выражений
Инкапсулируйте сложное выражение в методе или свойстве:
Код ITЗагрузка примера кода…
9. Требования по написанию комментариев
9.1. Документирование кода
Добавляйте комментарии, которые документируют код. Используйте встроенные в PHP средства для генерации документации на основании комментариев. В таких комментариях можно размещать дескрипторы, содержащие документацию по типам и членам типов, используемым в коде.
Код ITЗагрузка примера кода…
Такие комментарии отображаются в подсказках при написании кода.
9.2. Комментирование публичных методов и свойств
Снабжайте комментариями открытые методы и свойства классов, а так же сами классы и интерфейсы. Все открытые методы, а так же свойства необходимо сопровождать комментариями, даже если название метода описывает его функционал.
То же самое касается и классов — их описывайте в любом случае, открытые они или закрытые. В случае, если класс реализует интерфейс, дублировать описание реализуемых методов опционально.
9.3. Отслеживание работы
Не используйте комментарии для отслеживания работы, которая должна быть сделана позднее. Добавление к блоку кода комментария ToDo или какого-либо другого для отслеживания работы, которая должна быть сделана, может показаться хорошим решением. Но на самом деле такие комментарии никому не нужны. Используйте систему трекинга задач, такую как Jira, чтобы отслеживать недоработки.
9.4. Закомментированный код
Не оставляйте закомментированные участки кода. Никогда не отправляйте в репозиторий закомментированный код. Вместо этого используйте систему трекинга задач, чтобы следить за тем, какая работа должна быть сделана. Никто впоследствии не догадается, для чего предназначен тот или иной блок закомментированного кода.
10. Структура проекта и организация файлов
10.1. Стандартная структура
project/
├── app/
│ ├── Controllers/
│ ├── Models/
│ ├── Services/
│ ├── Repositories/
│ └── Middleware/
├── config/
├── database/
│ ├── migrations/
│ └── seeds/
├── public/
│ └── index.php
├── resources/
│ ├── views/
│ └── assets/
├── routes/
├── tests/
├── vendor/
├── .env
├── .env.example
├── composer.json
└── README.md
10.2. Пространства имён
Используйте пространства имён для организации кода. Пространства имён должны соответствовать структуре папок.
namespace App\Controllers;
namespace App\Models;
namespace App\Services;
10.3. Автозагрузка
Используйте Composer для автозагрузки классов. Настройте composer.json для соответствия структуре проекта:
{
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
}
11. Работа с базами данных
11.1. Использование ORM
Предпочитайте использование ORM вместо прямых SQL-запросов. ORM обеспечивает типобезопасность, защиту от SQL-инъекций и упрощает работу с данными.
11.2. Параметризованные запросы
Если используете прямые SQL-запросы, всегда применяйте параметризованные запросы для защиты от SQL-инъекций:
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
$user = $stmt->fetch();
Разбор:
prepare(...)создаёт параметризованный запрос с именованным плейсхолдером:email.execute(['email' => $email])безопасно подставляет значение и защищает от SQL-инъекций.fetch()получает первую найденную запись и возвращаетfalse, если результат пустой.- Шаблон подходит для типичного поиска пользователя по уникальному полю.
11.3. Валидация данных
Всегда валидируйте данные перед сохранением в базу данных. Используйте встроенные средства валидации или специальные библиотеки.
12. Безопасность
12.1. Валидация входных данных
Добавляйте валидацию для всех значений, которые поступают извне — интеграционное получение данных, ввод пользователя.
12.2. Экранирование вывода
Всегда экранируйте вывод данных для защиты от XSS-атак:
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
Разбор:
htmlspecialcharsпреобразует спецсимволы в HTML-сущности перед выводом в страницу.- Флаг
ENT_QUOTESэкранирует и двойные, и одинарные кавычки. UTF-8фиксирует кодировку преобразования и предотвращает ошибки интерпретации.- Такой вывод снижает риск XSS при отображении пользовательского ввода.
12.3. Защита от CSRF
Используйте токены для защиты от CSRF-атак в формах:
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
Разбор:
- Скрытое поле передаёт CSRF-токен вместе с формой.
- Значение берётся из сессии, где сервер хранит эталонный токен для пользователя.
- На обработчике формы токен сравнивается с сессионным значением.
- При несовпадении запрос отклоняется как потенциально поддельный.
13. Тестирование
13.1. Модульные тесты
Критически важные компоненты должны быть покрыты модульными и интеграционными тестами. Используйте подходы TDD/BDD по уместности.
13.2. Тестирование исключений
Исключения — для исключительных ситуаций, а не для управления потоком выполнения. Не используйте try-catch вместо if. Не стоит оборачивать каждый блок кода в try-catch — это снижает читаемость, скрывает реальные проблемы и усложняет отладку.
13.3. Избегание предотвратимых исключений
Избегайте исключений, которые можно предотвратить проверкой:
// Вместо этого:
try {
$value = $array[$index];
} catch (OutOfBoundsException $e) {
$value = null;
}
// Делайте так:
$value = $index < count($array) ? $array[$index] : null;
Разбор:
- Блок
try/catchв примере сверху использует исключение как обычную ветку логики. - Во втором варианте индекс проверяется до чтения массива.
- Тернарный оператор возвращает элемент, если индекс в диапазоне, иначе
null. - Такой код выполняется быстрее и понятнее при повседневной поддержке.