Плохие практики в PHP-бэкэнде: примеры и советы
Так исторически сложилось, что язык программирования 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) {
}
Технически хук это конечно функция и проблема в том, что программист пытается поместить весь свой код между фигурными скобками этой функции. В итоге получается это:
Пример очень длинной функции.
Как результат мы видим очень длинную функция. Мне приходится часто наблюдать подобные хуки в пять экранов длиной и больше.
Какая в этом проблема:
Этот код трудно читать;
Трудно найти правильное место, если нужно что‑то изменить;
Реально трудно понять, что здесь происходит, потому что многие независимые части сайта изменяются этой одной функцией;
Это яркий пример «спагетти» кода;
Как это можно улучшить?
Во первых использовать более специфические хуки взамен более общих:
Слева общих хук; Справа три более специфических;
Тогда у вас будет несколько небольших узко‑специализированных функций взамен одной общей.
Если более конкретных хуков не существует, все равно разделите код на отдельные функции. Тогда главная функция станет короче и будет похожа на некий контроллер.
Слева длинная функция; Справа функция вызывающая другие функции;
Также следует использовать осмысленные имена для под‑функций, тогда даже не понадобятся дополнительные комментарии;
Вывод: хук — это не просто функция, а точка входа в вашу программу!
Не бойтесь добавлять новые функции, объявлять файлы классов в своем коде, если вам это нужно. Не пытайтесь втиснуть всю необходимую логику между фигурными скобками функции‑события.
2. Используйте силу ООП
Довольно часто бывает нужно собрать и передать некоторый набор данных, и первая идея — использовать ассоциативный массив в качестве хранилища. Подумайте об использовании объектов (классов) вместо ассоциативных массивов, когда это имеет смысл.
Слева ассоциативный массив; Справа — объект;
В чем проблема массивов?
Ключи ассоциативных массивов не подсвечиваются IDE, поэтому вы не знаете, какие ключи там ожидать;
Вы не можете ограничить тип значения элемента ассоциативного массива;
Очевидно, что вам нужно проверить, существует ли ключ, прежде чем обращаться к нему, чтобы избежать ошибки;
Преимущества использования классов:
IDE показывает назначение и описание элемента класса;
Вы можете определить типы свойств;
Вы можете проверять и фильтровать значения в одном месте (сеттеры/геттеры);
Другая ошибка: даже используя классы, некоторые программисты пишут код, подобный этому:
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;
}
}
Проблема здесь в том, что слишком много параметров нужно передавать в методы.
Как это улучшить:
Используйте свойства класса для хранения общих данных вместо передачи их в качестве параметров метода. Используйте конструктор для установки свойств класса.
Вероятно, вашим методам вообще не нужно ничего получать или возвращать, если вы работаете с классами. Смотри пример ниже.
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;
}
}
}
теперь можно просто:
$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:"
Вот и все:)
Теперь можно проверить свой код на соответствие стандартам Drupal:
phpcs --extensions=theme,module,php --standard=Drupal,DrupalPractice web/modules/custom/module_name/*
Результатом будет что то вроде этого:
Результат проверки PHP кода на соответствие стандарту.
Прелесть в том, что этот же инструмент может исправить ваш код автоматически:
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;
Проблема здесь в читаемости кода из‑за слишком большого количества вложенных условий. Представьте, если нужно еще больше ифов…
Как можно улучшить этот код?
В данном случае его можно улучшить, делая проверки на противоположные условия и останавливать выполнение программы, если условие срабатывает. Например вот так:
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 думаю что уже его умеют, но чувствуют что получается какая то фигня, и тогда предполагают, что это язык какой то не такой. Язык впорядке — совершенствуйте свои навыки, изучайте и применяйте лучшие практики и не используйте плохие.
Всем добра и надеюсь Джависты не закидают меня тапками ;)