PHP
PHP The Right Way — обязательно к прочтению перед работой.
Правило бойскаута: оставлять место после себя чище чем, оно было до твоего визита.
Примечание
Переписывайте код, который не соответствует стандартам и правилам хорошего тона.
Стандарты
Общий стиль
- Полное соблюдение стандартов PSR-1 и PSR-2.
- Для автоматического приведения кода к требуемым стандартам существует инструмент PHP Coding Standards Fixer.
Именование переменных, ключей в массивах и свойств классов
Имена переменных обязательно должны быть в нижнем регистре, в формате snake_case (например,
$cart_content
).Обязательно осмысленное и информативное именование.
Правильно:
$counter, $user_id, $product, $is_valid
Неправильно:
$с, $uid, $obj, $flag
Переменные, хранящие список однотипных объектов, желательно именовать с суффиксом
_list
, например:$products_list
,$cart_applied_promotions_list
. Этот подход позволяет визуально быстрее отделять название переменной, хранящей список, от названия переменной, хранящей один из элементов списка. Например, при итерации массива в циклеforeach
:Правильно:
foreach ($applied_promotion_list as $applied_promotion) { // чёткое визуально разделение }
Неправильно:
foreach ($applied_promotions as $applied_promotion) { // $applied_promotions и $applied_promotion легко перепутать при беглом взгляде }
Переменные, хранящие булевы значения, желательно именовать с префиксом
is_
,has_
или любым другим подходящим глаголом.Правильно:
$is_valid, $has_rendered, $has_children, $use_cache
Неправильно:
$valid, $render_flag, $parentness_status, $cache
Имена переменных нежелательно начинать со знака нижнего подчёркивания. Бывали случаи, когда в теле одной функции были переменные с именами
$cache
,$_cache
и$__cache
.
Именование и объявление констант
Обязательно полностью в верхнем регистре, разделитель — нижнее подчёркивание (
_
):SORT_ORDER_ASC
,COLOR_GREEN
.Желательный порядок слов в названиях однотипных констант — сначала повторяющаяся часть, потом различающаяся:
Правильно:
COLOR_GREEN, COLOR_RED, SORTING_ASC, SORTING_DESC
Неправильно:
GREEN_COLOR, RED_COLOR, ASC_SORTING, DESC_SORTING
Семантика именования — такая же, как и у переменных.
Строковые литералы
- При обращении к элементу массива по ключу заключать имя ключа в одинарные кавычки:
$product['price'];
. - Все строковые переменные, не содержащие в себе других переменных, заключать в одинарные кавычки:
$foo = 'bar';
. - Если в строку необходимо включить значение переменной, то строка берётся в двойные кавычки, а название переменной обрамляется в фигурные скобки:
$greeting_text = "Hello, {$username}!";
.
Магические значения прямо посреди кода
В коде не должно быть числовых значений и строковых литералов, значение которых неочевидно:
$product->tracking = 'O'; // Что значит 'O'? ... $order_status = 'Y'; // "Y" == "Yes"? "Yellow"?
Такие вещи следует переносить в константы с осмысленными именами, и обращаться в коде к константам.
Если имеем дело с группой значений (например, возможные варианты значения поля в таблице БД), то константы этих значений нужно выделить в отдельный класс в пространстве имён
Tygh\Enum
. Пример такого класса -Tygh\Enum\ProductTracking
. Выглядит это так:$product->tracking = Tygh\Enum\ProductTracking::TRACK_WITH_OPTIONS;
Комментарии
- Комментарии пишутся только на английском языке. Для комментирования кода кода внутри функции/в контроллере использовать двойной слеш
//
. - Использование perl style(#) не допускается.
- Не пишите комментарий, который дублирует то, что и так выражено кодом. Лучше код без комментариев, чем код с ложными и неактуальными комментариями.
- Будьте точны и кратки.
PHPDoc
Желательно соблюдение черновика стандарта PSR-5. Как только стандарт будет принят, он станет обязательным.
Обязательно используйте блок с комментарием и описанием аргументов при объявлении всех функций, методов, свойств классов и самих классов.
Если функция не возвращает значение:
- запрещено писать
@return
; - можно оформлять как
@return void;
- запрещено писать
Обязательно выравнивайте на один уровень комментарии к тегам, названия параметров и свойств.
Обязательно оставляйте одну пустую строку перед первым тегом.
Обязательно оставляйте пустую строку перед и после группы последовательно идущих тегов
@param
.Запрещено оставлять более одной пустой строки подряд.
Обязательно разбивайте длинный комментарий на несколько строк, а строки выравнивайте на один уровень.
Пример правильного форматирования:
/** * Generates date-time intervals of a given period for sales reports * * @param int $interval_id Sales reports interval ID * @param int $timestamp_from Timestamp of report period beginning date * @param int $timestamp_to Timestamp of report period end date * @param int $limit Maximal number of the generated intervals. Also, this string * is used to illustrate the wrapped and aligned long comment. * * @return array * @author John Doe */
Быстродействие
Желательно не использовать внутри тела циклов вызов Registry::get()
. Эта операция очень ресурсоёмкая, и обращение к хранилищу значительно снижает производительность. Чтобы избежать циклических вызовов, необходимо перед циклом присвоить переменной значение из Registry
, а уже внутри цикла использовать переменную.
Функции
Именование
Обязательно называйте функции полностью в нижнем регистре и начинайте имена либо с префикса fn_
, либо с db_
:
fn_get_addon_option_variants
Аргументы
Если у нескольких аргументов есть стандартные значения, либо аргументы по смыслу не являются основными, то объединяйте их в один аргумент $params
. Таким образом, в функцию будут передаваться только основные аргументы и массив $params
.
Пример такой трансформации:
// до
function fn_get_product_data($product_id, &$auth, $lang_code = CART_LANGUAGE, $field_list = '', $get_add_pairs = true, $get_main_pair = true, $get_taxes = true, $get_qty_discounts = false, $preview = false, $features = true, $skip_company_condition = false)
// после
function fn_get_product_data($product_id, &$auth, $params)
{
$default_params = array(
'lang_code' => CART_LANGUAGE,
'field_list' => '',
'get_add_pairs' => true,
'get_main_pair' => true
'get_taxes' => true,
'get_qty_discounts' = false,
'preview' = false,
'get_features' = true
)
$params = fn_array_merge($default_params, $params);
DRY - Don’t repeat yourself
Если какой-либо кусок кода встречается в двух и более местах в контроллере/функции, обязательно выносите код в отдельную функцию.
Возвращать значение - это хорошо
Кроме функций-обработчиков хуков, желательно избегать передачи переменных в функцию по ссылке, не возвращая функцией ничего, и модифицируя значение исходной переменной. Это может приводить к необъяснимым и неочевидным модификациям значений переменных — сэкономьте своим коллегам и самому себе время, которое вы будете проводить за отладкой кода. Если передача по ссылке делается с целью уменьшить потребление памяти, то спешу вас расстроить: PHP сам делает нужные оптимизации даже при передаче переменной по значению.
Точка выхода
Желательно, чтобы функция имела только одну точку выхода. Использование двух и более точек выхода допускается лишь в случаях, если этим достигается:
- низкое ветвление кода (лучше множественный
return
, чем 5 вложенныхif
); - значительная экономия ресурсов (например, функция
fn_apply_exceptions_rules
в fn.catalog.php).
Комментарии для удаленных функций
Этот комментарий добавляется к устаревшим функциям, содержимое которых заменено на вывод нотиса:
<?php
/**
* This function is deprecated and no longer used.
* Its reference is kept to avoid fatal error occurances.
*
* @deprecated deprecated since version 3.0
*/
?>
Пример:
<?php
/**
* This function is deprecated and no longer used.
* Its reference is kept to avoid fatal error occurances.
*
* @deprecated deprecated since version 3.0
*/
function fn_get_setting_description($object_id, $object_type = 'S', $lang_code = CART_LANGUAGE)
{
fn_generate_deprecated_function_notice('fn_get_setting_description()', 'Settings::get_description($name, $lang_code)');
return false;
}
?>
Комментарии для часто встречающихся параметров
Это утвержденные комментарии для описания переменных в коде. Если они вам встречаются при определении хука, используйте их, пока смысл соответствует:
$auth - Array of user authentication data (e.g. uid, usergroup_ids, etc.)
$cart - Array of the cart contents and user information necessary for purchase
$lang_code - 2-letter language code (e.g. 'en', 'ru', etc.)
$product_id - Product identifier
$category_id - Category identifier
$params - Array of various parameters used for element selection
$field_list - String of comma-separated SQL fields to be selected in an SQL-query
$join - String with the complete JOIN information (JOIN type, tables and fields) for an SQL-query
$condition - String containing SQL-query condition possibly prepended with a logical operator (AND or OR)
$group_by - String containing the SQL-query GROUP BY field
Объектно-ориентированное программирование
Именование сущностей
Обязательно называйте классы, интерфейсы и трейты с заглавной буквы в формате UpperCamelCase.
Названия абстрактных классов обязательно должны иметь префикс
A
, например:ABackend
,ADatabaseConnection
.Имена интерфейсов обязательно должны иметь префикс
I
, например:ICountable
,IFilesystemDriver
.Если имя класса, интерфейса, трейта или метода должно содержать аббревиатуру наподобие URL, API, REST и т.п., то аббревиатура обязательно должна подчиняться правилам CamelCase.
Правильно:
$a->getApiUrl(), $a = new Rest();, class ApiTest
Неправильно:
$a->getAPIURL(), $a = new REST();, class APITest
Константы
Правила именования такие же, как и для констант вне классов. Пример:
class Api
{
/**
* Default HTTP request format mime type
*
* @const DEFAULT_REQUEST_FORMAT
*/
const DEFAULT_REQUEST_FORMAT = 'text/plain';
Свойства
- Правила именования такие же, как и для обычных переменных.
- Названия private- и protected- свойств запрещено начинать со знака нижнего подчёркивания (
_
).
Пример:
class Api
{
/**
* Current request data
*
* @var Request $request
*/
private $request = null;
/**
* Sample var
*
* @var array $sample_var
*/
private $sample_var = array();
Методы
В отличие от функций, названия методов обязательно должны начинаться со строчной буквы, формат именования — lowerСamelCase.
Названия private- и protected- методов запрещено начинать со знака нижнего подчёркивания (
_
).В общем случае, методы в классе желательно группировать по типу области видимости:
public -> protected -> private
.Пример:
class ClassLoader { /** * Creates a new ClassLoader that loads classes of the * specified namespace. * * @param string $include_path Path to namespace */ public function __construct($include_path = null) { // ... } /** * Gets request method name (GET|POST|PUT|DELETE) from current http request * * @return string Request method name */ private function getMethodFromRequestHeaders() { // ... }
Пространства имён
Tygh
— название пространства имён, в котором находятся все пространства имён и классы ядра CS-Cart.
Все классы, интерфейсы и трейты ядра и аддонов обязательно должны принадлежать этому пространству имён.
Если несколько классов, интерфейсов или трейтов относятся по смыслу к одному функционалу, то обязательно выделяйте их в общее подпространство, например, как классы менеджера блоков (
Tyqh\BlockManager
) и REST API (Tyqh\Api
).В каждом файле, в котором используются классы, интерфейсы либо трейты, обязательно используйте в начале файла директиву
use
, которая определяет, какие пространства имён используются в файле. В случае совпадения названий двух и более классов из разных пространств имён, обязательно описывайте алиасы для имён конфликтующих классов (use \Tygh\BlockManager\RenderManager as BlockRenderer
).Любая сущность (класс, интерфейс или трейт) обязательно должна находиться в своем отдельном файле. Наиболее часто это правило нарушается, когда разработчик в одном файл объявляет и класс, и исключение.
Желательно, чтобы аддоны добавлял свои классы, интерфейсы и трейты только в свое пространство имен
\Tygh\Addons\AddonName
. Например, для аддона form_builder разрешённое пространство имен —\Tygh\Addons\FormBuilder
.Исключением этому правилу служит:
- добавление новых сущностей API (следует добавлять класс в пространство имен
\Tygh\Api\Entities
), - добавление новых коннекторов для центра обновлений (следует добавлять класс в неймспейс TyghUpgradeCenterConnectors).
- добавление новых сущностей API (следует добавлять класс в пространство имен
Следует помнить, что корневая директория каждого установленного и включённого аддона является директорией-источником автозагрузки классов. Это означает, что класс
\Foo\Bar\MyClass
, находящийся в папке app/addons/my_changes/Foo/Bar/MyClass.php, будет автоматически загружен в память при вызове в коде конструкции вроде$my_class_instance = new \Foo\Bar\MyClass();
.Обязательно требуется группировать диррективы
use
друг с другом. Пример:use Tygh\Registry; use Tygh\Settings; use Tygh\Addons\SchemesManager as AddonSchemesManager; use Tygh\BlockManager\SchemesManager as BlockSchemesManager; use Tygh\BlockManager\ProductTabs; use Tygh\BlockManager\Location; use Tygh\BlockManager\Exim;
Шаблоны проектирования
Не рекомендуется создавать Singleton
-классы, и классы, состоящие из статических методов. Код, их использующий, практически невозможно покрыть юнит-тестами.
Оформление SQL-запросов
Запрос необходимо разделять следующим образом (кавычки и точки должны жестко соблюдаться):
$partner_balances = db_get_hash_array( "SELECT pa.partner_id, u.user_login, u.firstname, u.lastname, u.email, SUM(amount) as amount" . " FROM ?:aff_partner_actions as pa" . " LEFT JOIN ?:users as u ON pa.partner_id = u.user_id" . " LEFT JOIN ?:aff_partner_profiles as pp ON pa.partner_id = pp.user_id" . " LEFT JOIN ?:affiliate_plans as ap ON ap.plan_id = pp.plan_id AND ap.plan_id2 = pp.plan_id2" . " AND ap.plan_id3 = pp.plan_id3" . " WHERE pa.approved = 'Y' AND payout_id = 0 ?p ?p" . " ORDER BY $sorting $limit", 'partner_id', $condition, $group );
Закрывающая скобка обязательно переносится на новую строку. Таким образом мы выделяем нашу многострочную структуру в единый блок, что облегчает чтение кода.
Данные, используемые в запросах, обязательно нужно внедрять в них с помощью плейсхолдеров. Запрещено вставлять значения переменных в текст запроса напрямую.
Если текст SQL-запроса формируется из нескольких частей, находящихся в отдельных переменных, каждая составная часть обязательно должна быть обёрнута в вызов функции
db_quote
. Это позволяет избежать путаницы с плейсхолдерами.Желательно составные части текста SQL-запроса внедрять в него с помощью плейсхолдера ?p.
Пример для предыдущих двух пунктов:
$joins = array(); // Каждая составная часть запроса обёрнута в вызов db_quote(), вне зависимости от наличия необходимости в плейсхолдерах $joins[] = db_quote(' LEFT JOIN `foo` AS `f` ON `f`.`product_id` = `products`.`product_id`'); $joins[] = db_quote(' LEFT JOIN `bar` AS `b` ON `b`.`product_id` = `products`.`product_id` AND `b`.`order_id` = ?n', $order_id); $query = db_quote( 'SELECT * FROM `products`' . ' WHERE `products`.`status` = "A"' . ' ?p', // Список joins внедрён в запрос с помощью плейсхолдера "?p" implode(' ', $joins) );
Подробную информацию о плейсхолдерах и работе с ними вы можете найти в соответствующем разделе документации.
Общие правила
- Настоятельно не рекомендуем использовать “приглушение” PHP-ошибок с помощью оператора
@
. - Нельзя допускать появления любых ошибок, выдаваемых PHP-интерпретатором — Warnings, Notices и т. п. Случаи с несуществующими переменными, неправильными типами данных и т.п. должны обрабатываться в коде.
- Запрещено использовать функции
current()
иeach()
, если вы достоверно не знаете, где именно находится внутренний указатель в массиве. Если вам нужно получить первый элемент в массиве — используйте функциюreset()
. - Запрещено использовать
HTTP_REFERER
. Если вам нужно отредиректиться туда, откуда пришли — передавайтеredirect_url
.
Использование исключений
Чтобы систематизировать отлавливание фатальных ошибок программы (когда дальнейшее выполнение невозможно), в CS-Cart введены исключения (exceptions).
Когда нужно вызывать исключение
Когда что-то пошло не так, например: не найден нужный класс; вызван хук, который не объявлен и т.п. — всё, что не дает программе выполняться дальше.
Как вызывать исключение
Пишем:
use Tygh\Exceptions\DeveloperException;
...
throw new DeveloperException('Registry: object not found')
Название класса — это тип ошибки. Первый параметр — это сообщение, которое мы хотим отобразить:
new ClassNotFoundException() // попытка вызвать неизвестный класс
new ExternalException() // ошибка, возвращаемая внешним сервисом
new DatabaseException() // ошибка при работе с базой данных
new DeveloperException() // ошибка разработчика - вызывается то, что не должно вызываться
new InputException() // неправильные входные данные
new InitException() // ошибка инициализации магазина
new PermissionsException() // недостаточно прав для операции
Отладочная информация
Если у нас включен дебагер, выставлена константа DEVELOPMENT
или мы в консольном режиме — на экран выведется отладочная информация.
В остальных случаях отобразится страница store_closed.html и будет выдана ошибка 503 (если возможно). Отладочная информация появится в коде этой страницы, в самом низу внутри HTML-комментария. Это сделано, чтобы не показывать посетителям магазина техническую информацию прямо на странице.
PHPUnit
Важно
Данная инструкция актуальна только при наличии доступа к репозиторию CS-Cart.
Установка
Устанавливаем PHPUnit c зависимостями:
cd /path/to/cart/app/lib
composer install --dev
Запуск
Запускаем новые тесты:
/path/to/cart/app/lib/vendor/bin/phpunit -c /path/to/cart/_tools/unit_tests/phpunit.xml
Запускаем legacy-тесты:
/path/to/cart/_tools/restore.php u
/path/to/cart/app/lib/vendor/bin/phpunit -c /path/to/cart/_tools/build/phpunit.xml
Предупреждение
Не запускайте legacy-тесты в живом магазине! Они меняют базу данных.