Так исторически сложилось, что язык программирования PHP порой недолюбливают. Я не встречал ещё ни одного Java-программиста, который бы не смотрел на PHP свысока или хотя бы не ронял фразы типа: «К сожалению, практически вся e-commerce написана на PHP». Наверное, это происходит из-за того, что мы видим «плохой» код на PHP, иногда вынуждены поддерживать этот код и переносим негатив на сам язык. Но тем не менее нельзя отрицать, что PHP популярен — по данным на 2024 год, PHP используется на более чем 75% всех веб-сайтов, где язык программирования известен..
Надеюсь, эта статья может быть полезна не только тем, кто не работает с PHP постоянно, а вынужден лишь иногда что-то время от времени фиксить, но и тем, для кого PHP является «родным» языком. Я собрал некоторые анти-паттерны или плохие практики, из-за которых появляется плохой код. Возможно, вы узнаете здесь свои приёмы и подходы и пересмотрите их.

1. Хук — это не просто функция
Когда мы используем фреймворки или CMS, мы пользуемся специальными hook-функциями. Они могут называться по-разному, но смысл в том, что эта функция будет вызвана при определённом событии, произошедшем в системе. Например, «пользователь добавил товар в корзину» или «пользователь зашёл на определённую страницу». Это «событийно-ориентированная модель» или «event-driven programming» — парадигма программирования, основанная на обработке событий, сигналов или сообщений, возникающих в системе. Например, в Drupal такая функция может выглядеть так:
/**
* Implements hook_form_alter().
*/
function module_name_form_alter(&$form, FormStateInterface $form_state, $form_id) {
}
Технически хук — это, конечно, функция, и проблема в том, что программист пытается поместить весь свой код между фигурными скобками этой функции. В итоге получается следующее:

Как результат мы видим очень длинную функцию. Мне приходится часто наблюдать подобные хуки, в пять экранов длиной и больше.
Какая в этом проблема:
Этот код трудно читать;
Трудно найти правильное место, если нужно что‑то изменить;
Реально трудно понять, что здесь происходит, потому что многие независимые части сайта изменяются этой одной функцией;
Это яркий пример «спагетти» кода;
Как это можно улучшить?
Необходимо использовать подход «разделяй и властвуй» (Divide and Conquer) или по другому: «модульность» (Modularity). Во‑первых использовать более специфические хуки взамен более общих:

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

Также следует использовать осмысленные имена для подфункций, тогда даже не понадобятся дополнительные комментарии;
Вывод: хук — это не просто функция, а точка входа в вашу программу!
Не бойтесь добавлять новые функции, объявлять файлы классов в своем коде, если вам это нужно. Не пытайтесь втиснуть всю необходимую логику между фигурными скобками функции‑события.
2. Используйте силу ООП
Довольно часто бывает нужно собрать и передать некоторый набор данных, и первая идея — использовать ассоциативный массив в качестве хранилища. Подумайте об использовании объектов (классов) вместо ассоциативных массивов, когда это имеет смысл.

В чем проблема массивов?
Ключи ассоциативных массивов не подсвечиваются IDE, поэтому вы не знаете, какие ключи там ожидать;
Вы не можете ограничить тип значения элемента ассоциативного массива;
Очевидно, что вам нужно проверить, существует ли ключ, прежде чем обращаться к нему, чтобы избежать ошибки;
Преимущества использования классов:
IDE показывает назначение и описание элемента класса;
Вы можете определить типы свойств;
Вы можете проверять и фильтровать значения в одном месте (сеттеры/геттеры);
Этот подход называется «Data Transfer Object (DTO)».
Другая ошибка: даже используя классы, некоторые программисты пишут код, подобный этому:
Class MyNodeProcessor {
private function processNode($nid, $external_data) {
$node_array_storage = getStorage();
$langcode = getLangcode();
$user = getCurrentUser();
$node = $this->changeTitleToExternal($node_array_storage, $nid, $langcode, $external_data->title, $user);
$node = $this->setExternalPerson($node_array_storage, $nid, $langcode, $external_data->person, $user);
$node = $this->publishIfNeeded($node_array_storage, $nid, $langcode, $external_data->person, $user);
return $node;
}
}
Проблема здесь в том, что слишком много параметров нужно передавать в методы.
Как это улучшить:
Используйте свойства класса для хранения общих данных вместо передачи их в качестве параметров метода. Используйте конструктор для установки свойств класса. Этот подход называется «Инкапсуляция» (Encapsulation).
Вероятно, вашим методам вообще не нужно ничего получать или возвращать, если вы работаете с классами. Код должен стремиться к представленному ниже.
class MyNodeProcessor {
private $node;
private $node_array_storage;
private $langcode;
private $current_user;
private function processNode($nid) {
$this->setNode($nid);
$this->changeTitleToExternal();
$this->setExternalPerson();
$this->publishIfNeeded();
}
}
3. Не упускайте новые возможности PHP 8
В последнее время язык обновляется очень интенсивно, новые версии выходят постоянно. Следите за обновлениями! Появились очень крутые и полезные инструменты. Вот некоторые из них:
Null coalescing operator
$username = $result['user'] ?? 'nobody';
// То же что и:
$username = isset($result['user']) ? $result['user'] : 'nobody';
Некоторые подходы устаревают, например динамический свойства классов:
class Post
{
public string $title;
}
// …
$post->name = 'Name';
// Dynamic properties are deprecated in PHP 8.2, and will throw an ErrorException in PHP 9.0
Вы знали, что теперь есть read-only классы?
readonly class Post
{
public function __construct(
public string $title,
public Author $author,
public string $body,
public DateTime $publishedAt,
) {}
}
$post->title = 'Other';
Error: Cannot modify readonly property Post::$title
Вместо этого кода, где мы проверяем значение на null:
$country = null;
if ($session !== null) {
$user = $session->user;
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$country = $address->country;
}
}
}
Можно использовать «оператор безопасного обращения к null» (Null‑safe operator) или Null‑coalescing chaining:
$country = $session?->user?->getAddress()?->country;

Справа - новое!
Я не могу показывать все новые функции по очевидной причине:) вам придется гуглить и читать о новых функциях PHP8 самостоятельно.
И не бойтесь — попробуйте использовать их в своем коде! И конечно избегайте использования устаревших функций. Быть в курсе последних новшеств — это хороший способ повышать свои скилы как разработчика PHP.
4. Отформатируйте свой код
К сожалению не во всех наших проектах есть git‑линтеры, которые не позволяют коммитить неформатированный код. Выберите определенные правила форматирования и придерживайтесь их.
В чем проблема?
Неформатированный код трудно читать. Я видел классы, в которых свойства были объявлены между методами. Метод конструктора был где‑то в середине файла вместо того, чтобы быть перым методом класса;
В git будет мусор: среди хаотичных изменений в отступах и пробелах трудно найти настоящие функциональные изменения;
Неформатированный код является нарушением лучших практик PHP;
Для устранения лишнего “шума” в коде вы можете самостоятельно выполнить проверку форматирования. Вкратце продемонстрирую, как установить и использовать инструмент проверки кода на примере стандарта Drupal на Mac.
Устанавливаем composer глобально
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === 'e21205b207c3ff031906575712edab6f13eb0b361f2085f1f1237b7126d785e826a450292b6cfd1d64d92e6563bbde02') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"
sudo mv composer.phar /usr/local/bin/composer
Устанавливаем через composer Drupal Coder Sniffer, тоже глобально
composer global require drupal/coder
Edit ~/.zshrc and add export PATH="$HOME/.composer/vendor/bin:<other path>"
Вот и все :)
Теперь можно проверить свой код на соответствие стандартам Drupal:
phpcs --extensions=theme,module,php --standard=Drupal,DrupalPractice web/modules/custom/module_name/*
Результатом будет что-то вроде этого:

Прелесть в том, что этот же инструмент может исправить ваш код автоматически:
phpcbf --extensions=theme,module,php --standard=Drupal,DrupalPractice web/modules/custom/module_name/*
Результатом будет:

Если вы раньше не использовали линтеры и вдруг начнете, то однозначно одним хорошим PHP программистом станет больше!
Ну и напоследок еще один антипаттерн и непрошеный совет:
Ниже будет частично псевдокод, но каждый PHP бэкендер может его узнать.
if (isset($node_ids)) {
if (is_array($node_ids)) {
foreach ($node_ids as $key => $node_id) {
$node = LoadNode($node_id);
if ($node instanceof Node) {
if ($node->hasField('name')) {
if (!$node->isEmpty('name')) {
$result[$key] = $node->get('name')->toString();
}
}
}
}
}
}
return $result;
Проблема здесь в читаемости кода из‑за слишком большого количества вложенных условий. Представьте, если нужно еще больше ифов..
Как можно улучшить этот код?
В данном случае его можно улучшить, используя «ранний возврат» (early return), то есть, делая проверки на противоположные условия и останавливая выполнение программы, если условие срабатывает. Например вот так:
if (!isset($node_ids)) {
return $result;
}
if (!is_array($node_ids)) {
return $result;
}
foreach ($node_ids as $key => $node_id) {
$node = LoadNode($node_id);
if (!$node instanceof Node) {
continue;
}
if (!$node->hasField('name')) {
continue;
}
if ($node->isEmpty('name')) {
continue;
}
$result[$key] = $node->get('name')->toString();
}
return $result;
Меньше вложенных уровней делают код более читабельным. Как видите, также можно пропустить текущую итерацию цикла вместо проверки условий перед выполнением чего‑либо внутри цикла.
Заключение
Мои советы были о том как:
Не писать длинных функций (даже если это хук);
Использовать силу ООП;
Не упускать новые возможности PHP 8;
Отформатиовать наконец свой код;
Не делать много вложенных проверок;
Эти антипаттерны — часть того, с чем я постоянно сталкиваюсь в процессе работы и то, что портит представление о PHP как о языке в целом. В этот список можно еще много чего добавить, например использование побочных эффектов — когда для приведения типа переменной к числу используют умножение на единицу. Или если не умеют пользоваться авто‑загрузчиком классов и пространствами имен.
Да, на PHP можно писать отвратительный код. Но ведь можно писать и красиво! Этот язык прост для начинающих, что обманчиво, и даже многие состоявшиеся программисты «потрогав» PHP думаю что уже его умеют, но чувствуют, что получается какая то фигня, и тогда предполагают, что это язык какой-то не такой. Язык в порядке — совершенствуйте свои навыки, изучайте и применяйте лучшие практики и не используйте плохие.
Всем добра, и надеюсь, джависты не закидают меня тапками ;-)