Хорошие привычки в PHP

Как и любой другй язык программирования, пхп предоставляет возможность записать одни и те же действия по-разному. Один способ записи получается красивее, другой - не очень. Для начинающего веб-разработчика может быть не очевидно, как лучше описать какое-либо действие в PHP, поэтому некоторые из приёмов я продемонстрирую здесь.

Проход по массивам/спискам

Для прохода по массивам и спискам, где это возможно, пользуемся foreach, а не for($i=0; $i<$count; $i++):

$arr = array(1, 2, 3, 4);
foreach($arr as $number) {
  echo $number;
}

$arr2 = array('name' => 'John', 'email' => 'jon@smith');
foreach($arr2 as $key => $value) {
  echo "\n  $key = $value";
}

Не злоупотреблять префиксом @

Префикс @ перед выражением в PHP подавляет вывод предупреждений, даже если в скрипте на самом деле логическая ошибка, например обращение к объекту как к массиву:

$data = new stdClass();
$data->x = 1;
echo @$data['x'];

В этом примере на третьей строчке будет Fatal error, но сообщения о нём вы не увидите, так что найти такую ошибку в большом проекте может быть сложно. Поэтому, использовать @ стоит только там, где это действительно необходимо.

Инициализация переменных

Всегда следует инициализировать переменные. То есть, присваивать начальное значение перед тем, как считывать его из переменной.

Язык PHP родился в то время, когда о культуре программирования обычно задумывались не сильно, поэтому PHP позволяет различные вольности, вроде неинициализированных переменных. Программа, обращающаяся к неинициализированной переменно будет работать, но в ней может скрываться скрытый дефект, о котором разработчик не догадывается.

Поэтому необходимо всегда инициализировать переменные перед обращением к ним:

$users = getUsers();
$search = 'Ivan';

// Плохо:
foreach($users as $user) {
  if($user->name == $search) {
    $found = $user->id;
  }
}
if($found) {
  echo "$search = $found";
}

// Лучше:
$found = null;
foreach($users as $user) {
  if($user->name == $search) {
    $found = $user->id;
  }
}
if($found) {
  echo "$search = $found";
}

Обращение к элементам массива, которые могут отсутствовать

Обращение к отсутствующему элементу массива порождает замечание (Notice). Чтобы этого не произошло, пользуемся isset() для проверки наличия элемента в массиве или члена данных в объекте. Это предпочтительнее, чем пользоваться префиксом @, поскольку @ не предотвращает Notice, а лишь подавляет сообщение о нём, что ведёт к расходу вычисительной мощности впустую.

На машине разработчика обязательно должен быть включен вывод Notice на страницу и в лог, чтобы обращение к несуществующим переменным не осталось незамеченным.

$userId = isset($_GET['userId'])? $_GET['userId']: null;
if($userId) {
  echo 'Hello, user nr. ', $userId;
}

$email = isset($user->params->email)? $user->params->email: null;

Повторения похожего кода

Любых повторений следует избегать. Если вы смотрите на программу и видите несколько похожих строк или блоков, это почти наверняка означает избыточное копирование (копипаст), что является злейшим врагом разработчика.

В примере ниже ясно видно, что 5 раз повторяется один и тот же кусок при обращении к списку параметров. Этот кусок переписан так, чтобы не было повторяющегося фрагмента. Теперь, если придётся менять код, изменения надо будет внести только в одном месте.

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

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

// Плохо:
if (isset($search['zipcode'])) {
  $request['FindArticles']['request']['search_params']['address']['zip_code'] = $search['zipcode'];
}
if (isset($search['zipcodeRadius'])) {
  $request['FindArticles']['request']['search_params']['address']['radius'] = $search['zipcodeRadius'];
}
if (isset($search['fuel'])) {
  $request['FindArticles']['request']['search_params']['fuel_types']['fuel_type_id'] = $search['fuel'];
}
if (isset($search['minPowerAsArray'])) {
  $request['FindArticles']['request']['search_params']['kilowatt']['from'] = $search['minPowerAsArray'];
}
if (isset($search['maxPowerAsArray'])) {
  $request['FindArticles']['request']['search_params']['kilowatt']['to'] = $search['maxPowerAsArray'];
}

// Лучше:
$params = array();
if (isset($search['zipcode'])) {
  $params['address']['zip_code'] = $search['zipcode'];
}
if (isset($search['zipcodeRadius'])) {
  $params['address']['radius'] = $search['zipcodeRadius'];
}
if (isset($search['fuel'])) {
  $params['fuel_types']['fuel_type_id'] = $search['fuel'];
}
if (isset($search['minPowerAsArray'])) {
  $params['kilowatt']['from'] = $search['minPowerAsArray'];
}
if (isset($search['maxPowerAsArray'])) {
  $params['kilowatt']['to'] = $search['maxPowerAsArray'];
}
$request['FindArticles']['request']['search_params'] = $params;

Комментируй там, где надо

Комментировать код - вообще-то хорошая привычка. При работе в команде разработчиков, комментариями должны быть снабжены все внутренние API, которыми всем постоянно приходится пользоваться. Так же в обязательном порядке комментировать нужно сложные участки кода: реализации хитрых алгоритмов, и другие места, где без поллитры не разберёшься.

А вот ставить комментарий там, где всё очевидно - тратить время своё, время тех, кто будет читать этот код, и место на диске. Например, в примере ниже комментарии совершенно ни к чему, поскольку код и без них легко читается и говорит сам за себя:

// Инициализация переменных:
$totalTeethCount = 0;
$totalWeight = 0;
// Проход по пользователям:
foreach($users as $user) {
  $totalTeethCount += $user->teethCount;
  $totalWeight += $user->weight;
}

Понятные имена переменных, функций, методов

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

Вывод фрагментов HTML из PHP

Когда в PHP-скрипте надо вывести фрагмент HTML, делать это следует не через echo, а в блоках ?>....<?php. Практика показывает, что в большинстве случаев, удобнее работать с выводимым HTML, когда он находится вне PHP кода (и это правильно, так как PHP - это один язык, HTML - совсем другой). Вывод HTML через echo заставляет писать длинные цепочки из конкатенации строк через оператор ".", добавляя обратный слеш перед кавычками. Записанный таким образом HTML выглядит как месиво из кавычек, в котором сложно найти и исправить нужное место.

Сравните два фрагмента кода, в которых список объектов отображется в HTML. Какой из них понятнее? Я думаю, что второй.

// Плохо:
foreach($items as $item) {
  echo '<tr><td>' . $item->id . '</td><td><a href="item/' . $item->id . '">' . $item->title . '</a></td></tr>';
}

// Лучше:
foreach($items as $item) {
  ?>
  <tr>
    <td><?php echo $item->id ?></td>
    <td><a href="item/<?php echo $item->id ?>"><?php echo $item->title ?></a></td>
  </tr>
  <?php
}

Обработка запроса и вывод HTML

Скрипты, которые выдают HTML, могут одновременно содержать и реализацию логики приложения. Например, обработку запроса пользователя и вывод результата - HTML-страницы. В таких страницах следует четко разделить приём входных параметров, обработку запроса и вывод результата, не перемешивая между собой код обработки и вывода HTML. В больших программных проектах, как правило, это разделение ещё сильнее: за обработку и за отображение графического интерфейса отвечают разные компоненты, или классы (модель MVC, Model / View / Controller).

В маленьких скриптах создавать разные классы для обработки и отображения нет смысла, но разделить обработку и вывод необходимо хотя-бы визуально: в начале PHP-скрипта входная обработка параметров, далее - обработка собственно запроса пользователя, далее - вывод HTML. Эти части можно разделить визуально комментариями "///////....." или как нравится :).

Закрывающий маркер ?>

В конце PHP-файла, если он заканчивается PHP-кодом, а не HTML, ставить закрывающий маркер ?> не нужно. Более того: наличие этого маркера в самом конце файла может при некоторых условиях привести к ошибке, которую будет очень сложно обнаружить.

Темы: