Плохие практики в 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 кода на соответствие стандарту.

Результат проверки 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 думаю что уже его умеют, но чувствуют что получается какая то фигня, и тогда предполагают, что это язык какой то не такой. Язык впорядке — совершенствуйте свои навыки, изучайте и применяйте лучшие практики и не используйте плохие.

Всем добра и надеюсь Джависты не закидают меня тапками ;)

© Habrahabr.ru