Стиль оформления кода PHP

Этот пост как заметка, ради самодисциплины и порядка в коде, не полный копипаст какого-то стиля, а сборная солянка стилей оформления кода PHP из PSR-1,2, BSD и Битрикс, которая нравится и привычна лично мне, но в большей степени это стиль Алмена (Eric Allman) или стиль BSD.

Оглавление

  1. Общие рекомендации
    1. Файлы
    2. Строки
    3. Именования
  2. Процедурное программирование
    1. Основы синтаксиса
    2. Переменные
    3. Константы и глобальные переменные
    4. Массивы
    5. Функции
    6. Управляющие конструкции
      1. if, elseif, else
      2. switch, case
      3. while, do while
      4. for
      5. foreach
      6. try, catch
  3. Объектно-ориентированное программирование
    1. Пространства имён и оператор use
    2. Классы, свойства и методы
      1. Extends и implements
      2. Константы
      3. Свойства
      4. Методы
      5. Аргументы методов
      6. abstract, final и static
      7. Вызов методов
  4. Работа с БД
    1. Стиль написания запросов
  5. Формат документации
    1. Файлы
    2. Классы
    3. Функции/методы
  6. Заключение

1. Общие рекомендации

1.1. Файлы

  • В файлах использовать только теги <?php и <?=.
  • Закрывающий тэг ?> необходимо удалять из файлов, которые содержат только код PHP.
  • В файлах использовать только UTF-8 без BOM.
  • Все PHP файлы заканчивать одной пустой строкой.
  • Для подключения файлов используйте require_once().

1.2. Строки

  • В качестве отступов использовать табуляцию длиной равной 4.
  • Длина строки кода 80-120 символов, жестко ограничивать не надо.
  • Использовать пустые строки для улучшения читаемости и обозначения связанных блоков/логических сегментов кода.
  • Нельзя писать на одной строке более одной инструкции.

1.3. Именования

  • Всем файлам, переменным, константам, функциям, классам и методам давать "говорящие" названия, чтобы они отражали свою суть, а не были набором из 2-3 символов.

  • Простые переменные писать строчными (в нижнем регистре).
    Составные имена переменных писать в верблюжьей нотации,  где первое слово начинается с маленькой буквы, все остальные с большой в стиле сamelCase.
    Для явного указания типа переменной использовать префикс отражающий их тип - $dbUser, $obUser, $arUser, $strUser, $bUser... и т.д.

  • Имена констант и глобальных переменных всегда должны быть в верхнем регистре в стиле UPPER_CASE.
    Составные имена констант разделять символом подчеркивания.
    В качестве префикса в именах констант должно использоваться имя пакета/класса, в котором они используются.

  • Функции/методы класса писать в верблюжьей нотации, где первое слово начинается с маленькой буквы, все остальные с большой в стиле сamelCase().
    В функциях/методах класса использовать префиксы: is (обозначение вопроса), get (получить значение), set (установить значение).

  • Имена классов в стиле StudlyCaps.
    Каждое слово в имени класса с большой буквы и никаких символов, кроме букв латинского алфавита.

2. Процедурное программирование

2.1. Основы синтаксиса

Многострочный комментарий в стиле С (/* */) использовать либо вначале файла, для его описания, либо для временного комментирования участка кода.
Однострочный комментарий в стиле C++ (// ) использовать можно везде.
Комментарии писать грамотно с заглавной буквы и делать отступ в один пробел слева, кроме однострочных.

<?php

//==============================================================================
// CATEGORY LARGE FONT
//==============================================================================

//------------------------------------------------------------------------------
// Sub-Category Smaller Font
//------------------------------------------------------------------------------

/* Title Here Notice the First Letters are Capitalized */

/**
 * This is a detailed explanation
 * of something that should require
 * several paragraphs of information.
 */

//This is a single line quote.

?>

В качестве отступов использовать табуляцию длиной равной 4-м и не лепить код к левому краю.

<?php
    echo 'Это тест';
?>

Ключевые слова PHP true, false, null писать в нижнем регистре.

$a = true;
$b = false;
$c = null;

Операторы, ключевые слова, круглые и фигурные скобки отделять пробелами.

$x = 10;
$k = $x > 15 ? 1 : 2;

if ($k > 0)
    echo '$k=' . $k;

Нельзя писать на одной строке более одной инструкции.

//Неправильно
$a = $b; $b = $c; $c = $a;

//Правильно
$a = $b;
$b = $c;
$c = $a;

Нет необходимости ставить точку с запятой в конце последней строки блока с PHP-кодом.

<?php
    echo 'Это тест';
?>

<?= 'Это тест' ?>

<?= 'Мы опустили последний закрывающий тег';

Не использовать оператор echo для вывода html, а использовать «Изолирование от HTML»

<p>Это будет проигнорировано PHP и отображено браузером.</p>
<?= 'А это будет обработано PHP' ?>
<p>Это тоже будет проигнорировано PHP и отображено браузером.</p>

В строке html-кода подставлять переменные по возможности используя апострофы и конкатенацию, а не двойные кавычки или фигурные скобки.

//Неправильно
echo "<a href=\"$sPageHref\">$sPageName</a>";
echo "<a href='{$sPageHref}'>{$sPageName}</a>";

//Правильно
echo '<a href="'. $sPageHref .'">'. $sPageName .'</a>';

Придерживаясь ограничения длины строки 80-120 символов использовать конкатенацию и перенос строк.

echo '<offer id="' . $arItem['ARTNUMBER'] . '">'
    . '<url>' . $arItem['DETAIL_PAGE_URL'] . '</url>'
    . '<price>' . $arItem['PRICE'] . '</price>'
    . '<currencyId>' . $arItem['СURRENCY'] . '</currencyId>'
    . '<categoryId>' . $arItem['IBLOCK_SECTION_ID'] . '</categoryId>'
    . '<delivery>' . $arItem['DELIVERY'] . '</delivery>'
    . '<name>' . $arItem['NAME'] . '</name>'
    . '</offer>';
if (isset($foo) && is_int($foo) && $id
    && $a === $b && !$bar && $page > 0
    && $c == $d &&  count($var) > 4
)
{
    //тело функции
}

В тернарном операторе заключать  в скобки только условие, при сложном условии стоит заменить его на if/else. И не стесняться его использовать.

$var = (условие выполняется) ? 'делаем это' : 'иначе это';

Короткие строки писать выше длинных.

$x          = 10;
$arResult   = array();
$strSearch  = '';
$bFoundName = false;

2.2. Переменные

Простые переменные пишем строчными в нижнем регистре.

$summ = $x + $y;

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

$userName = 'Петр';

Для явного указания типа переменной использовать префикс отражающий их тип:
$bUser, $intUser, $flUser, $strUser, $dbUser, $obUser, $arUser и т.д.

//Строки с префиксом str
$strName, $strLastName

//Массивы с префиксом ar
$arResult, $arParams, $arUser

//Объекты с префиксом ob
$obElement, $obUser

//Объекты БД CDBResult с префиксом  db
$dbUser, $dbResult

При множественной инициализации переменных выравнивать значения между ними по правому краю используя доп. пространство.

$x          = 10;
$arResult   = array();
$strSearch  = '';
$bFoundName = false;

2.3. Константы и глобальные переменные

  • Имена констант и глобальных переменных всегда должны быть в верхнем регистре.
  • Составные имена констант писать с подчеркиваниями для разделения слов.
  • В качестве префикса в именах констант должно использоваться имя пакета/класса, в котором они используются.
//Константы
HOST;
SITE_ID;
CACHE_MANAGER;

//Глобальные переменные
global  $HOST, $SITE_ID, $CACHE_MANAGER;

$HOST;
$SITE_ID;
$CACHE_MANAGER;

2.4. Массивы

Массивы форматировать в таком виде.

array(
    'ID'           => GetMessage('ID'),
    'CRM_HOST'     => GetMessage('CRM_HOST'),
    'CRM_LOGIN'    => GetMessage('CRM_LOGIN'),
    'CRM_PASSWORD' => GetMessage('CRM_PASSWORD'),
    'CRM_AUTH'     => GetMessage('CRM_AUTH'),
    'CRM_PORT'     => GetMessage('CRM_PORT'),
    'CRM_PATH'     => GetMessage('CRM_PATH'),
);

2.5. Функции

  • При вызове функции пробелы между названием функции и открывающей круглой скобкой недопустимы.
  • Пробелы после открывающей круглой скобки и перед закрывающей недопустимы.
  • В списке аргументов необходим один пробел после каждой запятой и недопустимы пробелы перед запятыми.
  • Тело функции в одну строку при одном выражении писать с фигурными скобками.
//Объявление функции
function getFunction()
{
    //тело функции
}

//Вызов функции
getFunction();

//Сокращенная форма записи
function foo() { return 0; }

Большой список аргументов можно разбивать на несколько строк с одним отступом/табуляцией.
Первый аргумент также переносить на следующую строку и в каждой строке должен быть только один аргумент.

//вызов метода с аргументами
$obMyClass->getFunction(
    $arg1,
    $arg2,
    $arg3
);

2.6. Управляющие конструкции

  • Между ключевым словом и открывающей круглой скобкой один пробел;
  • Между закрывающей круглой скобкой и открывающей фигурной скобкой один пробел;
  • Тело управляющей конструкции смещать на один отступ/таб;
  • Тело каждой управляющей конструкции заключать в фигурные скобки;
  • Открывающую и закрывающую фигурные скобки с новой строки;
  • Пробелы между круглыми скобками и их содержимым в условии недопустимы;
  • Не экономить вертикальное пространоство, если это позволяет монитор.
  • Вместо else if лучше использовать слитное elseif , чтобы избежать проблем в условиях с двоеточием.

Допустимы два вида написания конструкций:

  • если тела всех инструкций состоят из одного выражения, то фигурные скобки опускаем:

    if ($expr1)
        //действие1
    elseif ($expr2)
        //действие1
    else
        //действие1
  • если тело хотя бы одной из частей состоит более чем из одного выражения, то необходимы скобки у всех инструкций:

    if ($expr1)
    {
        //действие1
        //действие2
    }
    elseif ($expr2)
    {
        //действие1
    }
    else
    {
        //действие1
    }

Открывающую фигурную скобку ставить под началом выражения и на одном уровне вертикально с закрывающей

if($strUserName)
{
    echo 'Привет ' . $sUserName;
}

Избегать лишних фигурных скобок если в условии/блоке одно выражение.

if($strUserName)
    echo 'Привет ' . $strUserName;

2.6.1. if, elseif, else

//Неправильно
if ($expr1){ /*тело if*/ }
elseif ($expr2){ /*тело elseif*/ }
else{ /*тело else;*/ }

//Правильно, но не рекомендуется :
if($a > $b):
    echo $a." больше, чем ".$b;
elseif($a == $b): // Заметьте, тут одно слово.
    echo $a." равно ".$b;
else:
    echo $a." не больше и не равно ".$b;
endif;

//Правильно
if ($expr1)
{
    //тело if
}
elseif ($expr2)
{
    //тело elseif
}
else
{
    //тело else;
}

2.6.2. switch, case

Простой switch с одним выражением пишем без фигурных скобок в case, иначе выражение в case заключаем в фигурные скобки.

Где нет break пишем комментарий вроде //no break.

switch ($expr)
{
    case 0:
        echo 'Первый case, заканчивается на break';
        break;
    case 1:
        echo 'Второй case, с умышленным проваливанием';
        //no break
    case 2:
    case 3:
    case 4:
        echo 'Третий case, завершается словом return вместо braek';
        return;
    default:
        echo 'По-умолчанию';
        break;
}

2.6.3. while, do while

while ($expr)
{
    //structure body
}

do
{
    //structure body;
}
while ($expr);

2.6.4. for

for ($i = 0; $i < 10; $i++)
{
    //тело for
}

2.6.5. foreach

foreach ($iterable as $key => $value)
{
    //тело foreach
}

2.6.6. try, catch

try
{
    //тело try
}
catch (ExceptionType $e)
{
    //тело catch
}

3. Объектно-ориентированное программирование

3.1. Пространства имён и оператор use

  • После объявления пространства имен namespace необходима одна пустая строка.
  • Оператор use должен быть после объявления простарнства имён namespace.
  • На одно объявление (импорт или создание псевдонима) использовать только один оператор use.
  • После блока операторов  use должна быть одна пустая строка.
namespace Vendor\Package;

use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;

// ... тут php-код ...

3.2. Классы, свойства и методы

  • Имена классов в стиле StudlyCaps.
  • Каждый класс должен располагаться в отдельном файле и находиться в пространстве имён как минимум первого уровня.
  • Открывающую и закрывающую фигурные скобки класса должны быть с новой строки.
namespace Vendor\Model;

class ClassName
{
    //константы, свойства, методы
}

3.2.1 Extends и implements

  • Ключевые слова extends и implements располагать на одной строке с именем класса.

  • Список implements можно разбить на несколько строк, каждая из которых с одним отступом, при этом, первый интерфейс в списке необходимо перенести на следующую строку, и в каждой строке  указать только один интерфейс.

namespace Vendor\Package;

use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;

class ClassName extends ParentClass implements
    \ArrayAccess,
    \Countable,
    \Serializable
{
    //константы, свойства, методы
}

3.2.2. Константы

  • Имя константы класса всегда должно быть в верхнем регистре.
  • Составные имена констант разделять символом подчеркивания.
  • В качестве префикса в именах констант может использоваться имя пакета/класса, в котором они используются, например: BX_FILE_PERMISSIONS, API_FILE_PERMISSIONS
namespace Vendor\Model;

class Version
{
    const VERSION = '15.0.1';
    const VERSION_DATE = '2014-11-12 09:00:00';
}

3.2.3. Свойства

  • Для всех свойств и методов класса необходимо объявлять область видимости public, static, protected, private;
  • Разделять пустой строкой константы и группы свойств по области видимости.
  • Недопустимо использовать ключевое слово var для объявления свойств.
  • Недопустимо в одном объявлении указывать более одного свойства.
  • Не следует начинать название свойства с подчёркивания для обозначения приватной или защищённой видимости.
namespace Vendor\Model;

class Foo
{
    const HTTP_1_1  = "1.1";
    const HTTP_GET  = "GET";
    const HTTP_POST = "POST";

    public $foo = null;
    public $bar = array();

    protected $proxyHost;
    protected $proxyPort;
    protected $proxyUser;
    protected $proxyPassword;
}

3.2.4. Методы

  • Имя метода пишется в верблюжьей нотации, где первое слово начинается с маленькой буквы, все остальные с большой в стиле сamelCase().
  • Для всех методов класса необходимо объявлять область видимости public, static, protected, private;
  • В методах класса использовать префиксы: is (обозначение вопроса), get (получить значение), set (установить значение).
  • Открывающая и закрывающая фигурные скобки должны быть с новой строки и на одном уровне.
  • Недопустимо объявлять методы с пробелом между названием и круглой скобкой.
  • Недопустимо добавлять пробел после открывающей круглой скобки и перед закрывающей.
  • Недопустимо начинать название метода с подчёркивания для обозначения приватной или защищённой видимости.

В примере ниже обратите внимание на расположение запятых, пробелов, круглых, квадратных и фигурных скобок.

namespace Vendor\Model;

class ClassName
{
    public function getMark($arg1, &$arg2, $arg3 = [])
    {
        //тело метода
    }
}

3.2.5. Аргументы методов

  • В списке аргументов необходим один пробел после каждой запятой и недопустимы пробелы перед запятыми.
  • Аргументы метода со значениями по-умолчанию располагать в конце списка аргументов.
namespace Vendor\Package;

class ClassName
{
    public function foo($arg1, &$arg2, $arg3 = [])
    {
        //тело метода
    }
}

  • Большое количество аргументов необходимо разбивать на несколько строк, каждая из которых с одним отступом/табуляцией слева.
  • Первый аргумент в списке также необходимо переносить на следующую строку и в каждой строке должен быть только один аргумент.
  • Когда список аргументов разбит на несколько строк, закрывающую круглую скобку и открывающую фигурную необходимо располагать на разных строках, в соответствии с общим форматирование скобок.
namespace Vendor\Package;

class ClassName
{
    public function aVeryLongMethodName(
        ClassTypeHint $arg1,
        &$arg2,
        array $arg3 = []
    )
    {
        //тело метода
    }
}

  • Стараться избегать большого количества аргументов, гораздо удобней делать так:
$http = new \Bitrix\Main\Web\HttpClient;
$http->setHeader('User-Agent', 'Test');
$http->setAuthorization('demo', 'demo');
$http->setTimeout(10);
$http->get($url);

3.2.6. abstract, final и static

  • Ключевые слова abstract и final пишутся перед модификаторам видимости.
  • Ключевое слово static пишется после модификатора видимости.
namespace Vendor\Model;

abstract class ClassName
{
    protected static $property;

    abstract protected function foo();

    final public static function bar()
    {
        //тело метода
    }
}

3.2.7. Вызов методов

  • При вызове метода пробелы между названием метода и открывающей круглой скобкой недопустимы.
  • Пробелы после открывающей круглой скобки и перед закрывающей недопустимы.
  • В списке аргументов необходим один пробел после каждой запятой и недопустимы пробелы перед запятыми.

$foo->bar($arg1);
Foo::bar($arg1, $arg2, $arg3);

  • Большой список аргументов можно разбивать на несколько строк с одним отступом/табуляцией.
  • Первый аргумент также переносить на следующую строку и в каждой строке должен быть только один аргумент.
$foo->bar(
    $longArgument,
    $longerArgument,
    $muchLongerArgument
);

4. Работа с БД

4.1. Стиль написания запросов

  • Запрос необходимо обрамлять двойными кавычками;
  • Названия таблиц, полей и алиасов в нижнем регистре (в Битрикс поля в верхнем регистре);
  • Все операторы и служебные команды БД пишем в верхнем регистре;
  • Таблицы и поля нужно обрамлять обратными кавычками, а алиасы и префиксы не надо обрамлять;
  • "Целые числа" не надо обрамлять кавычками.

Простые запросы пишем в одну строку

$sql = "SELECT `ID` FROM `b_user` WHERE `LOGIN` = '".$sqlHelper->forSql($login, 50)."'";

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

$connection = Bitrix\Main\Application::getConnection();
$sqlHelper = $connection->getSqlHelper();

$sql = "INSERT INTO `".$this->table."`
        SET `DATE`        = NOW(),
            `TITLE`       = '".$sqlHelper->forSql($_title)."',
            `KEYWORDS`    = '".$sqlHelper->forSql($keywords)."',
            `DESCRIPTION` = '".$sqlHelper->forSql($description)."',
            `DETAIL_TEXT` = '".$sqlHelper->forSql($detail_text)."',
            `ACTIVE`      = 1
        WHERE `SECTION_ID` = 1
            AND `ELEMENT_ID` = 1024
        GROUP BY `NAME`
        ORDER BY `ID`
        ";   

$result = $connection->query($sql);

5. Формат документации

  • Все комментарии/блоки документации должны быть на английском языке.
  • Все блоки документации "doc-блоки" должны быть совместимы с форматом phpDocumentor
  • Необходимо описывать в стиле PHPDoc все функции, файлы с классами, сами классы и их публичные методы.

5.1. Файлы

Каждый файл, содержащий PHP-код и класс должен иметь заголовочный doc-блок в начале файла, содержащий следующие phpDocumentor-теги.

/**
 * Short description
 *
 * Multiline detailed
 * description (if any)
 *
 * NOTE: Requires PHP version 5.3 or later
 *
 * @package      API
 * @subpackage   CAPIReviews
 * @author       Tuning-Soft (tuning-soft.ru)
 * @copyright    © 1984-2015 Tuning-Soft
 * @license     MIT License
 * @link         tuning-soft.ru
 * @version      1.0.0
 * @date         01.01.2015
 */

Минимальный doc-блок для файла.

/**
 * Created by Tuning-Soft
 *
 * @package      API
 * @subpackage   CAPIReviews
 * @copyright    © 1984-2015 Tuning-Soft
 * @version      1.0.0
 */

5.2. Классы

Каждый класс должен иметь doc-блок, содержащий следующие phpDocumentor-теги:

/**
 * Short description
 *
 * Multiline detailed
 * description (if any)
 *
 * NOTE: Requires PHP version 5.3 or later
 *
 * @package      API
 * @subpackage   CAPIReviews
 * @author       Tuning-Soft (tuning-soft.ru)
 * @copyright    © 1984-2015 Tuning-Soft
 * @license      MIT License
 * @example    tuning-soft.ru
 * @version      Release: @package_version@
 * @since        1.5.0
 * @deprecated   Class deprecated in Release 2.0.0
 */

Минимальный doc-блок для класса.

/**
 * Class HostRestriction
 *
 * @since        14.0.6
 * @version      Release: @package_version@
 * @package      Bitrix\Security
 */ 

5.3. Функции/методы

  • Каждая функция/метод класса должна иметь doc-блок, содержащий как минимум:
    • Описание функции
    • Все аргументы
    • Все возможные возвращаемые значения
  • Нет надобности использовать тег @access, т.к. область видимости уже известна из ключевых слов public, private, protected используемых при определении функции/метода.
  • Если функция/метод может выбрасывать исключение, используйте тег @throws:
    @throws exceptionclass [описание]
     

Пример doc-блока для функции/метода:

/**
 * Checking host by host restriction policy
 *
 * @param string $host Host for checking.
 *
 * @return bool Return true for valid (allowed) host.
 * @throws \Bitrix\Main\ArgumentTypeException
 */
public function isFoo($host)
{
    /* some PHP-code */
   
    return true;
}

6. Заключение

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

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

function getFunction()
{
    //тело функции
}

или так:

function getFunction() {
    //тело функции
}

- В первом случае мы расходуем вертикальное пространство, но так лучше ориентироваться новичкам по вертикальным линиям, которые подсвечивает IDE для открывающей/закрывающей фигурных скобок, и вложенность условий будет наглядна.

- Во втором случае мы экономим вертикальное пространство, это будет лучше для продвинутых, но новичок при нескольких вложенных условиях и выражениях будет путаться, сложно читать такой код, искать к какому условию относится выражение.

Имя *
Логин (мин. 3 символа)
E-mail *
*— обязательные для заполнения поля
Логин или e-mail
TUNING-SOFT.RU Разработка умных веб-сервисов