Описанный ниже стиль кодирования используется при разработке ядра Cotonti и его официальных расширений. Если вы хотите участвовать в разработке системы, постарайтесь придерживаться данного стиля. Мы не принуждаем вас использовать этот стиль при разработке ваших приложений с использованием Cotonti. В этом случае вы можете использовать тот стиль, который вам больше подходит.
Эти основные правила касаются всех файлов с программным кодом в проекте (HTML, PHP, CSS, JS и т.д.), если не оговорено другое.
Нет жесткого органичения длины строки, но желательно чтобы строки не превышали 120 символов. Переносить строку следует там, где это имеет наибольший смысл, а не тогда, когда ее длина приближается к 120 символам.
…в дополнение к основным правилам:
PHP код должен следовать стандарту написания кода PSR-12, и PSR-1, и стандарту автозагрузки php-файлов PSR-4.
Если вы используете PHPStorm, вы можете включить инструменты для форматирования кода в Settings
→ Editor
→ Code Style
→ PHP
→ Set from
→ PSR12
.
<?php
или <?=
и НЕ ДОЛЖНО использоваться никаких других вариаций тегов, например <?
.?>
не нужен.$a + $b
, $string = $foo . 'bar'
, $i++
, но не $a+$b
.$one, $two
, но не $one,$two
.===
и неравенства !==
.declare(strict_types=1);
//
, а не с #
.Все PHP файлы в самом начале должны содержать описание в формате PHPDoc.
Очень хорошо, когда в заголовке описаны переменные, которые используются в этом файле, а определены где-то в другом месте.
Стандартный заголовок для файлов (этот шаблон заголовка должен быть в начале каждого файла Cotonti):
/** * @package {PACKAGENAME} * @version {VERSION} * @copyright (c) 2008-2023 Cotonti Team * @license BSD License * * @var array<string, mixed> $user * @var XTemplate $t */
@version
не обязательно. Например, если вы создаете расширение, версия уже прописана в установочном файле и тег @version
может "путать" других разработчиков.
Не забывайте документировать классы. Классам нужен свой тег @package, тот же что и в заголовке.
/** * Cotonti MySQL Database adapter class. * A compact extension to standard PHP PDO class with slight Cotonti-specific needs, * handy functions and query builder. * * @package cotonti * @see http://www.php.net/manual/en/class.pdo.php * * @property-read int $affectedRows Number of rows affected by the most recent query * @property-read int $count Total query count * @property-read int $timeCount Total query execution time */ class MySQL extends Adapter
Документируйте функции. Каждая функция должна как минимум иметь описание того, что она делает. Также рекомендуется документировать параметры. Используйте @param
, @return
, @throws
в таком порядке.
/** * Returns total number of records contained in a table * @param string $tableName Table name * @return int * @throws Exception if the table name is not exists */ public function countRows($tableName)
Если атрибуты @param
или @return
являются избыточными из-за использования нативных типов, их можно не указывать:
/** * Execute the job. */ public function handle(AudioProcessor $processor): void { // }
Однако, если нативный тип является универсальным, уточняйте тип с помощью атрибутов @param
или @return
:
/** * Get the attachments for the post. * @return array<int, Attachment> */ public function attachments(int $postId): array { // ... }
Все имена должны быть описательными, но краткими. Читая имя класса или переменной должно быть понятно зачем они нужны. Например: $CurrentUser
, getUserData($id)
.
Имена классов ДОЛЖНЫ быть в PascalCase. Например: Controller
, Model
.
Константы ДОЛЖНЫ быть в верхнем регистре со знаком подчеркивания в качестве разделителя слов. Например: STATUS_PUBLISHED
.
Названия папок/пространств имен в нижнем регистре.
Мы НЕ ИСПОЛЬЗУЕМ нижнее подчеркивание для обозначения приватных свойств и методов классов.
Имена переменных ДОЛЖНЫ быть в camelCase. Каждое слово, кроме первого, должно начинаться с заглавной буквы. Например: $currentUser
- правильно, а $current_user
и $currentuser
- нет.
Ситуации, когда допускаются односимвольные имена для переменных: языковые строки: $L
, строковые ресурсы: $R
и когда переменная является индексом для цикла.
Индексы циклов:
В этом случае индекс цикла верхнего уровня всегда должен быть $i
. Если цикл содержит вложенный цикл, его индекс должен быть $j
, далее $k
, и т.д. Если индексом цикла является какая-либо существующая переменная, то это правило не применяется. Например:
for ($i = 0; $i < $outerSize; $i++) { for ($j = 0; $j < $innerSize; $j++) { foo($i, $j); } }
Имена функций и методов переменных ДОЛЖНЫ быть в camelCase. Исключение - функции ядра Cotonti. Пример хороших имен функций: printLoginStatus()
, getUserData()
, и т.п.
В Cotonti мы используем стандартный префикс cot_
для всех имен функций, чтобы избежать конфликта имен. Это стандарт для функций API ядра системы: cot_mailPrepareAddress($address)
.
Но мы не требуем использовать префикс cot_
в ваших расширениях до тех пор, пока эти функции не используются функциями ядра. При желании вы можете использовать свой префикс.
Для аргументов функций используются те же правила что и для имен переменных.
Основная философия здесь заключается в том, чтобы не нарушать читабельность и понятность кода из-за лени. Но тут нужно соблюдать баланс и следовать здравому смыслу. Например printLoginStatusForAGivenUser()
будет уже через чур. Вот это название функции будет лучше: printUserLoginStatus()
, или просто printLoginStatus()
.
Все типы данных и значения PHP должны быть в нижнем регистре. Это относится и к true
, false
, null
.
Для приведения типов мы используем операторы, а не функции. И только их короткие формы. После оператора ставим один пробел.
$value1 = (int) $argument1; // верно $value2 = (bool) $argument2; // верно $value3 = (integer) $argument3; // не верно $value4 = invtal($argument2, 8); // только если это действительно необходимо
Изменение типа существующей переменной считается плохой практикой. Постарайтесь не использовать такой подход, за исключением случаев, когда это действительно необходимо.
public function save(Transaction $transaction, int $argument2 = 100) { $transaction = new Connection; // плохо $argument2 = 200; // хорошо }
Если строка не содержит переменных или одинарных кавычек, используйте одинарные кавычки.
$str = 'Например так.';
Если строка содержит одинарную кавычку, используйте двойные кавычки во избежание излишнего экранирования.
Подстановка переменных
$str1 = "Привет, $username!"; $str2 = "Привет, {$username}!";
Следующий вариант запрещен:
$str3 = "Привет, ${username}!";
Конкатенация
При конкатенации строк выделяйте точку пробелами:
$name = 'Cotonti' . ' CMF';
Для длинных строк используйте следующий подход:
$sql = 'SELECT * ' . 'FROM posts ' . 'WHERE id = 121 ';
Для массивов мы используем краткий синтаксис.
$arr = [3, 14, 15, 'Cotonti', 'CMF'];
При большом количестве элементов, каждый элемент располагается на отдельной строке. После последнего элемента СТАВИМ запятую.
$mail = [ 'to' => 'user@domain.com', 'from' => ['admin@site.com', 'SiteTitle'], 'cc' => [['user2@example.com', 'User2'], 'user3@example.com', 'User4 <user4@example.com>'], ];
if ($expr1) { // if body } elseif ($expr2) { // elseif body } else { // else body; } // Такой код недопустим: if (!$model && null === $event) throw new Exception('test');
Всегда ДОЛЖНО использоваться elseif
а не else if
. Т.е. все ключевые слова состоят из одного слова.
Длинные выражения в скобках разбивайте на несколько строк, где каждая строка имеет как минимум один отступ. В этом случае первое условие ДОЛЖНО быть на следующей строке. Закрывающая круглая скобка и открывающая фигурная ДОЛЖНЫ быть на своей отдельной строке с одним пробелом между ними. Булевы операторы между условиями всегда ДОЛЖНЫ быть в начале строки.
if ( $expr1 && $expr2 ) { // if body }
Избегайте использования else
после ключевого слова return
:
$result = $this->getResult(); if (empty($result)) { return false; } else { // process result }
так на много лучше:
$result = $this->getResult(); if (empty($result)) { return false; } // process result
Конструкция switch
выглядит следующим образом. Обратите внимание на расположение пробелов, круглых и фигурных скобок.
switch ($expr) { case 0: echo 'First case, with a break'; break; case 1: echo 'Second case, which falls through'; // no break case 2: case 3: case 4: echo 'Third case, return instead of break'; return; default: echo 'Default case'; }
Тернарные операторы следует использовать только для очень простых вещей. Желательно, чтобы они использовались только для присваивания значений, а не для вызовов функций или чего-то более сложного. Иначе это вредит читабельности кода и усложняет отладку. Примеры:
// Так плохо ($i < $size && $j > $size) ? do_stuff($foo) : do_stuff($bar); // А так хорошо $min = ($i < $j) ? $i : $j;
Здесь под термином "класс" понимаются все классы, интерфейсы и трейты.
extends
и implements
ДОЛЖНЫ быть объявлены в той же строке, что и имя класса. Список implements
(extends
для интерфейсов) может быть разделен на несколько строк, где каждая строка имеет один отступ. При этом первый элемент в списке ДОЛЖЕН находиться в следующей строке, и в каждой строке должен быть только один интерфейс.Общедоступные (public) и защищенные (protected) свойства должны быть объявлены в начале класса, раньше объявления методов; Закрытые (private) свойства, так же, должны быть объявлены в начале класса, но могут быть добавлены и непосредственно перед методами, использующими их, в случае, если эти переменные используются только небольшой частью методов класса.
<?php /** * Doc блок файла */ namespace Vendor\Package; use FooClass; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; /** * Doc блок класса */ class ClassName extends ParentClass implements \ArrayAccess, \Countable { public $publicProp1; public $publicProp2; protected $protectedProp; private $privateProp; public function someMethod() { // ... } }
Открывающая фигурная кавычка ДОЛЖНА быть расположена на строке, следующей за строкой объявления функции, а закрывающая скобка ДОЛЖНА находиться в следующей строке после тела функции.
В списке аргументов ДОЛЖЕН быть один пробел после запятой.
function foo(int $arg1, string &$arg2, array $arg3 = []) { // тело функции }
Список аргументов может быть разбит на несколько строк, где каждая строка имеет один отступ. В этом случае первый аргумент ДОЛЖЕН быть на следующей строке и ДОПУСКАЕТСЯ только один агрумент на строку.
public function aVeryLongMethodName( ClassTypeHint $arg1, string &$arg2, array $arg3 = [] ) { // тело метода }
В объявлении методов/функций всегда явно указывайте тип аргументов и возвращаемых значений:
// Хорошо /** * Execute the job. */ public function handle(AudioProcessor $processor): void { // } // Хуже /** * Execute the job. * @param AudioProcessor processor * @return void */ public function handle($processor) { // } // Плохо /** * Execute the job. */ public function handle($processor) { // }
При вызове функции или метода НЕ ДОЛЖНО быть пробела между именем функции и открывающейся скобкой. НЕ ДОЛЖНО быть пробела после открывающейся скобки и перед закрывающейся. В списке аргументов НЕ ДОЛЖНО быть пробела перед запятой и ДОЛЖЕН быть один после
SQL запросы зачастую нечитабельны без форматирования, а временами они бывают очень большими. Форматирование значительно повышает читаемость кода. SQL запросы следует форматировать следующим образом, ориентируясь на ключевые слова:
$sql = 'SELECT p.*, u.* ' . 'FROM ' . Cot::$db->pages . ' AS p ' . . 'LEFT JOIN ' . Cot::$db->pages . ' AS u ON u.user_id = p.page_ownerid ' . "WHERE p.page_ownerid = 1 AND p.page_cat = 'news' " . 'LIMIT 10';
Используйте параметры для безопасной подстановки в запрос данных, введенных пользователями (даже если вы уверены, что переменная не может содержать одинарных кавычек. Никогда не доверяйте данным, передаваемым пользователями):
$result = Cot::$db->query( 'SELECT * FROM ' . Cot::$db->forum_topics . ' WHERE ft_cat = :cat AND ft_id = :topicId', ['cat' => $section, 'topicId' => $param] );
Избегайте конструкций, специфичных для определенных типов БД
not equals
описанный в стандарте SQL-92 - это <>
. !=
также поддерживается большинством СУБД, но лучше использовать <>
.INSERT ... ON DUPLICATE KEY UPDATE
- это MySQL конструкция и не работает в других СУБД.`
для названий таблиц и столбцов - специфично для MySQL. Лучше используйте CotDB::quoteTableName()
и CotDB::quoteColumnName()
или сокращенные варианты этих методов: CotDB::quoteT()
, CotDB::quoteC()
.let
, а не var
для объявления переменных и const
для объявления констант.const COLOR_ORANGE = "#ffa500"
. Большинство переменных – константы в другом смысле: они не меняются после присвоения. Но при разных запусках функции это значение может быть разным. Для таких переменных следует использовать const
и camelCase в имени.===
, неравенство !==
.let single = 'single-quoted';
.alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3.
.
Поблагодарили: 42 раза
Насчёт стиля по закрывающим фигурным скобкам - очень спорный вопрос. Мне удобнее когда скобки находятся друг над или под другом - сразу видно какая скобка к чему относится и компиляторы подсвечивают блоки кода и таким образом расположенные скобки. У вас почему-то приняли за ствндарт когда открывающая скобка в конце строки открывающей блок кода , а закрываюзщая где-то там под блоком. По-моему это плохой стиль для таких блоков.
Поблагодарили: 180 раз
Касательно расположения скобок я того же мнения + привык за всю историю существования кота именно к такому положению. Но принятый за основу psr-12 гласит именно о таком "кривом" расположении ... типа этот стандарт международно признаный ...
Поблагодарили: 211 раз
Предлагаемый вариант с фигурными скобками хорош тем, что экономит место на экране. Занимать целую строку открывающей скобкой так себе дело. Плюс практически в любом редакторе есть индикация закрывающей/открывающей скобки. Так что здесь рациональнее именно так.
По замене табуляторов на 4 пробела не совсем интересно. Хотя табуляторы в разных редакторах имеют разную длину, так что пробелами нерационально, зато гарантировано ровно будет. ((