Плохие практики в 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) {  }

Технически хук — это, конечно, функция, и проблема в том, что программист пытается поместить весь свой код между фигурными скобками этой функции. В итоге получается следующее:

Пример очень длинной функции.

Пример очень длинной функции.

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

Какая в этом проблема:

  • Этот код трудно читать;

  • Трудно найти правильное место, если нужно что‑то изменить;

  • Реально трудно понять, что здесь происходит, потому что многие независимые части сайта изменяются этой одной функцией;

  • Это яркий пример «спагетти» кода;

Как это можно улучшить?

Необходимо использовать подход «разделяй и властвуй» (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/*

Результатом будет что-то вроде этого:

Результат проверки 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; 

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

Как можно улучшить этот код?

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

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


ссылка на оригинал статьи https://habr.com/ru/articles/871426/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *