Skip to content

Instantly share code, notes, and snippets.

@Andrew8xx8
Forked from bzzeke/cscart-php-style-guide.md
Created November 2, 2012 13:29
Show Gist options
  • Save Andrew8xx8/4001374 to your computer and use it in GitHub Desktop.
Save Andrew8xx8/4001374 to your computer and use it in GitHub Desktop.
CS-Cart PHP code style guide

Правильный путь PHP разработки

The Right Way

Обязательно к прочтению перед работой.

Стандарты

Общий стиль

Полное соблюдение PSR (0, 1, 2)

Автоматический исправитель кодстайла под эти стандарты ТУТ, так же есть в папке _tools/codestyle

Правило бойскаута: оставлять место после себя чище чем оно было до твоего визита. Т.е. переписывайте код который не соответствует стандартам и правилам хорошего тона.

jQuery-like форматирование вызовов функций, массивов и т.д.

	$_data = array (
		'addon' => $addon_scheme->getId(),
		'priority' =>  $addon_scheme->getPriority(),
	);

	db_query("REPLACE INTO ?:addon_descriptions ?e", array(
		'lang_code' => $translation['lang_code'],
		'addon' =>  $addon_scheme->getId(),
		'name' => $translation['value'],
		'description' => $translation['description']
	));

	return array(
		'status' => Response::STATUS_OK,
		'data' => array(
			'settings' => $result,
			'search' => $params
		)
	);

Переменные

В нижнем регистре. Разделитель подчёркивание.

Обязательно осмысленное именование. Никаких $tmp, $t, $k, $v и т.д.

Константы

Полностью в верхнем регистре, разделитель подчёркивание (_)

Строки

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

Комментарии

  • Комментарии пишутся только на английском языке. Для комментирования кода кода внутри функции/в контроллере использовать двойной слеш //;
  • Использования perl style(#) не допускается;
  • Не пишите коментарий который дублирует то что и так выражено кодом. Помните правило, что лучше код без комментариев, чем код с ложными и неактуальными комментариями;
  • Будьте точны и кратки.

Плохо:

	if (!empty($tabs)) {
		// If tabs not empty do:
// Enable/disable tabs for addon
ProductTabs::instance()->updateAddonTabStatus($addon, $new_status);

Хорошо:

	// Check options type. We need to apply only Selectbox, radiogroup and checkbox modifiers
	if (empty($orig_options)) {
		// ...
	}

PHP Doc

Обязателен для всех новых функций.

Описание аттрибутов возможных в doc-комментарии тыц 1, тыц 2

Если функция не возвращает значение, то правильно будет НЕ ПИСАТЬ @return вообще.

Пример куска кода отвечающего вышеописанным правилам

	$tabs = $addon_scheme->getSections();

	if (!empty($tabs)) {

		// ...

		foreach($tabs as $tab_index => $tab) {
			$section_tab_id = Settings::instance()->updateSection(array(
				'parent_id'    => $addon_section_id,
				'edition_type' => $tab['edition_type'],
				'name'         => $tab['id'],
				'position'     => $tab_index * 10,
	 			'type'         => isset($tab['separate']) ? Settings::SEPARATE_TAB_SECTION : Settings::TAB_SECTION,
			));

			// Import translations for tab
			if (!empty($section_tab_id)) {
				$settings = $addon_scheme->getSettings($tab['id']);

				foreach ($settings as $k => $setting) {
					// ...
				}
			}
		}
	}

Функции

Именование

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

/**
 * Returns addon's setting variants (similar to fn_get_settings_variants)
 * 
 * @deprecated deprecated since version 3.0
 * @param string $addon Addon name to get option for
 * @param string $option_name Option name
 * @param string $lang_code 2-letter language code (e.g. 'EN', 'RU', etc.)
 * @return array Variants list
 */
function fn_get_addon_option_variants($addon, $option_name, $lang_code = CART_LANGUAGE)
{
	//...
}

/**
 * Execute query and format result as associative array with column names as keys
 *
 * @param string $query unparsed query
 * @param mixed ... unlimited number of variables for placeholders
 * @return array structured data
 */
function db_get_array($query) 
{
	//...
}

Параметры

Если у параметров есть дефолтные значения либо, они по смыслу не являются основными то их необходимо объединять в один параметр $extra. Таким образом, в функцию будут передаваться только основным параметры плюс массив экстра.

Вот как выглядела функция прежде:

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, $extra)
{
        // extra default values
    $extra_default = 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
    )
    $extra = fn_array_merge($extra_default, $extra);

Следует понимать разницу между $params & $extra. Первая использоуется в основном при поиске, и содержит перечень атрибутов и условий для поиска. В то время как $extra агрегирует избыточные параметры. Основная идея для чего это было сделано - облегчить и улучшить стиль передачи всех пришедших в функцию параметров в хуки.

Следовать DRY (DO NOT REPEAT YOURSELF)

Если какой-либо кусок кода встречается в двух и более местах в контроллере/функции, то код выносится в отдельную функцию в ядро (fn.[тут по смыслу].php).

Возвращать значение это хорошо

Все функции должны что-то возвращать!(true/false or $variable) (правило не распространяется на методы классов)

Точка выхода

Функция по возможности должна иметь только одну точку выхода. Использование двух и более точек выхода допускается лишь в случае, если этим достигается низкий порог дальнейшей условности, в простейшем случае для экономии ресурсов (например функция fn_apply_exceptions_rules в fn.catalog.php).

ООП

Имена классов

С прописной буквы в CamelCase.

class Api
{
class ClassLoader
{

Константы

Полностью в верхнем регистре, разделитель подчёркивание (_)

class Api
{
	/**
	 * Key of resource name in _REQUEST
	 *
	 * @const REST_PATH_PARAM_NAME
	 */
	const DEFAULT_REQUEST_FORMAT = 'text/plain';

Свойства

Полностью в нижнем регистре, разделитель подчёркивание.

Закрытые и приватные свойства ДОЛЖНЫ начинаться с подчёркивания.

class Api
	/**
	 * Current request data
	 *
	 * @var Request $_request
	 */
	private $_request = null;

	/**
	 * Sample var
	 *
	 * @var array $_request
	 */
	private $_sample_var = array();	

Методы

Со строчной буквы в camelCase. Закрытые и приватные методы ДОЛЖНЫ начинаться с подчёркивания.

class SomeClass 
{
	/**
	 * 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()
	{
		// ...
	}

Пространства имён

Начиная с версии 3.1.1 используются прстранства имён.

Tygh -- название пространства имён ядра магазина.

Все классы ядра должны входить в это пространство имён. Если несколько классов относятся по смыслу к одному функционалу, то нужно выделять их в отдельное подспространство, как например классы блок менеджера (Tyqh\BlockManager) и Api (Tyqh\Api)

Объявляется пространство имён так:

namespace Tygh;

Подпространство имён:

namespace Tygh\BlockManager;

Все функции, классы, констаны и т.д объявленные в этом пространстве имён будут доступны из глобального пространства или из другого только в случае указания этого пространства.

Например имеем такой файл:

namespace My\Name;

class MyClass {}
function myfunction() {}
const MYCONST = 1;

$a = new MyClass; // тут всё ок

В другом файле мы должны указывать пространство имён, если онне принадлежит тому же

$c = new \My\Name\MyClass; // Так работает.

$c = new MyClass; // Так не работает.

В третем файле попробуем тоже самое только с use

use My\Name;

$c = new \My\Name\MyClass; // Так работает.

$c = new MyClass; // И так работает.

Пространтсво имён, как и директива use действует только на один файл в котором объявлено. На те файлы которые подключаются с помощью include и reqire, действие не распространяется.

В каждом файле, в котором используются классы обязательно писать в начале директиву 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;

Обязательно группировать use диррективы друг с другом.

Директива use это аналог reauire (include). Она добавляет каждому используему в файле имени класса то пространство имён в котором он находится. Она не подключает файл.

Автозагрузка классов

Все классы подключаются автоматически из тех путей которые добавлены в ClassLoader. По этому без крайней необходимости не стоит подключать классы вручную (reauire или include).

Все дирерктории аддонов по умолчанию в этот путь включаются, если аддон включен и установлен.

Для того чтобы добавить специфическую папку нужно выполнить вот эту строку:

Registry::get('class_loader')->addIncludePath('\\путь\\до\\папки\\с\\классами');

Дирректория с аддоном может содержать как свои пространства имён так и классы расширяющие Tygh. Можно увидеть на примере аддона StoreImport.

Оформление SQL запросов (D)

При выполнении вставки в базу данных использовать плейсхолдеры для значения полей. Для вставки данных, не перечислять поля вручную, а использовать массив с плейсхолдером ?e или ?u!

$a = array(
    'col1' => $some_var,
    'col2' => CONST,
    'col3' => 'text'
);

db_query('INSERT INTO ?:mytable ?e', $a);

Перчень плейсхолдеров (placeholders) можно найти в девелоперской документации database-standards Обязательно разделять строки более 120. Автоперенос строк в данном случае не спасает потому как переносит как ему вздумается, в результате я бы не сказал что код от этого становиться понятнее.

Запрос необхомо разделять следующим образом (кавычки и точки должны жестко соблюдаться):

$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
    );

Закрывающая скобка обязатально переносится на новую строку! Таким образом мы выделяем нашу многострочную структуру в единый блок, что облегчает чтение кода.

Запрещено (X)

  • Никогда не нужно использовать собаку @ перед переменными или функциями - это плохой стиль программирования, ведущий к большому количеству ошибок. Например в php5 (а другой мы и не используем), передавая переменную с собакой по ссылке - на самом деле по ссылке она не передается, а просто копируется.
  • Нельзя допускать появление любых ошибок, выдаваемых php-интерпретатором - warning, notices и т.п. Несуществующие переменные, неправильные типы переменных и т.п. должны обрабатываться в коде
  • Не использовать функцию current() для получения значения массива во всех случаях (например, есть у вас массив, вы знаете что в нем одно значение и хотите это значение получить - в этом случае надо использовать reset), кроме того когда вам ИМЕННО нужно получить текущее значение (хотя в принципе данный прием использовать у нас негде, т.е. все массивы перебираются foreach'ами). Причина, по которой не надо использовать эту функцию в том, что разные версии php по-разному выставляют внутренний указатель почему-то.
  • Добавлять функцию в ядро без хуков и описания.
  • Использовать HTTP_REFERER, если вам нужно отредиректиться туда, откуда пришли - передавайте redirect_url

Начиная с версии 3.1.1

В общем

  • Минимальная версия PHP теперь 5.3
  • Используем PSR стандарты (пока не полностью)
  • Используем пространства имён
  • Стараемся по возможности выносить всё в классы

Структура директорий

  • acme
  • app - *папка application. агрегирует код магазина. Не должа быть доступна извне, кроме payments8
    • addons
      • sample_addon - пример аддона. Повторяет директорию app/ ядра
        • controllers
        • databse файлы БД TODO: перенести в var
        • schemas
        • Tygh
        • SampleAddon - папка с классами определёнными в пространстве имён SampleAddon аддона
        • lib
    • controllers
      • admin
      • common
      • customer
    • functions
    • payments
    • schemas
    • shippings
    • Tygh - классы из пространства имён ядра
    • lib
  • design
  • images
  • install
  • js
    • addons
      • sample_addon - папка с JS файлами аддона
    • lib
    • tygh
  • stores
  • var

По коду

  • Вместо Registry::get('view') и глобальной перемнной $view используем Registry::getView()
  • Вместо Registry::get('ajax') и глобальной перемнной $ajax используем Registry::getAjax()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment