Описанный ниже стиль кодирования используется при разработке ядра 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):
1 2 3 4 5 6 7 8 9 |
/** * @package {PACKAGENAME} * @version {VERSION} * @copyright (c) 2008-2023 Cotonti Team * @license BSD License * * @var array<string, mixed> $user * @var XTemplate $t */ |
@version
не обязательно. Например, если вы создаете расширение, версия уже прописана в установочном файле и тег @version
может "путать" других разработчиков.
Не забывайте документировать классы. Классам нужен свой тег @package, тот же что и в заголовке.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * 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 * * @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
в таком порядке.
1 2 3 4 5 6 7 |
/** * 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
являются избыточными из-за использования нативных типов, их можно не указывать:
1 2 3 4 5 6 7 |
/** * Execute the job. */ public function handle(AudioProcessor $processor ): void { // } |
Однако, если нативный тип является универсальным, уточняйте тип с помощью атрибутов @param
или @return
:
1 2 3 4 5 6 7 8 |
/** * 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
, и т.д. Если индексом цикла является какая-либо существующая переменная, то это правило не применяется. Например:
1 2 3 4 5 |
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
.
Для приведения типов мы используем операторы, а не функции. И только их короткие формы. После оператора ставим один пробел.
1 2 3 4 5 |
$value1 = (int) $argument1 ; // верно $value2 = (bool) $argument2 ; // верно $value3 = (integer) $argument3 ; // не верно $value4 = invtal( $argument2 , 8); // только если это действительно необходимо |
Изменение типа существующей переменной считается плохой практикой. Постарайтесь не использовать такой подход, за исключением случаев, когда это действительно необходимо.
1 2 3 4 5 |
public function save(Transaction $transaction , int $argument2 = 100) { $transaction = new Connection; // плохо $argument2 = 200; // хорошо } |
Если строка не содержит переменных или одинарных кавычек, используйте одинарные кавычки.
1 |
$str = 'Например так.' ; |
Если строка содержит одинарную кавычку, используйте двойные кавычки во избежание излишнего экранирования.
Подстановка переменных
1 2 |
$str1 = "Привет, $username!" ; $str2 = "Привет, {$username}!" ; |
Следующий вариант запрещен:
1 |
$str3 = "Привет, ${username}!" ; |
Конкатенация
При конкатенации строк выделяйте точку пробелами:
1 |
$name = 'Cotonti' . ' CMF' ; |
Для длинных строк используйте следующий подход:
1 2 3 |
$sql = 'SELECT * ' . 'FROM posts ' . 'WHERE id = 121 ' ; |
Для массивов мы используем краткий синтаксис.
1 |
$arr = [3, 14, 15, 'Cotonti' , 'CMF' ]; |
При большом количестве элементов, каждый элемент располагается на отдельной строке. После последнего элемента СТАВИМ запятую.
1 2 3 4 5 |
$mail = [ 'to' => 'user@domain.com' , 'from' => [ 'admin@site.com' , 'SiteTitle' ], 'cc' => [[ 'user2@example.com' , 'User2' ], 'user3@example.com' , 'User4 <user4@example.com>' ], ]; |
1 2 3 4 5 6 7 8 9 10 |
if ( $expr1 ) { // if body } elseif ( $expr2 ) { // elseif body } else { // else body; } // Такой код недопустим: if (! $model && null === $event ) throw new Exception( 'test' ); |
Всегда ДОЛЖНО использоваться elseif
а не else if
. Т.е. все ключевые слова состоят из одного слова.
Длинные выражения в скобках разбивайте на несколько строк, где каждая строка имеет как минимум один отступ. В этом случае первое условие ДОЛЖНО быть на следующей строке. Закрывающая круглая скобка и открывающая фигурная ДОЛЖНЫ быть на своей отдельной строке с одним пробелом между ними. Булевы операторы между условиями всегда ДОЛЖНЫ быть в начале строки.
1 2 3 4 5 6 |
if ( $expr1 && $expr2 ) { // if body } |
Избегайте использования else
после ключевого слова return
:
1 2 3 4 5 6 |
$result = $this ->getResult(); if ( empty ( $result )) { return false; } else { // process result } |
так на много лучше:
1 2 3 4 5 6 |
$result = $this ->getResult(); if ( empty ( $result )) { return false; } // process result |
Конструкция switch
выглядит следующим образом. Обратите внимание на расположение пробелов, круглых и фигурных скобок.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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' ; } |
Тернарные операторы следует использовать только для очень простых вещей. Желательно, чтобы они использовались только для присваивания значений, а не для вызовов функций или чего-то более сложного. Иначе это вредит читабельности кода и усложняет отладку. Примеры:
1 2 3 4 5 |
// Так плохо ( $i < $size && $j > $size ) ? do_stuff( $foo ) : do_stuff( $bar ); // А так хорошо $min = ( $i < $j ) ? $i : $j ; |
Здесь под термином "класс" понимаются все классы, интерфейсы и трейты.
extends
и implements
ДОЛЖНЫ быть объявлены в той же строке, что и имя класса. Список implements
(extends
для интерфейсов) может быть разделен на несколько строк, где каждая строка имеет один отступ. При этом первый элемент в списке ДОЛЖЕН находиться в следующей строке, и в каждой строке должен быть только один интерфейс.Общедоступные (public) и защищенные (protected) свойства должны быть объявлены в начале класса, раньше объявления методов; Закрытые (private) свойства, так же, должны быть объявлены в начале класса, но могут быть добавлены и непосредственно перед методами, использующими их, в случае, если эти переменные используются только небольшой частью методов класса.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?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() { // ... } } |
Открывающая фигурная кавычка ДОЛЖНА быть расположена на строке, следующей за строкой объявления функции, а закрывающая скобка ДОЛЖНА находиться в следующей строке после тела функции.
В списке аргументов ДОЛЖЕН быть один пробел после запятой.
1 2 3 4 |
function foo(int $arg1 , string & $arg2 , array $arg3 = []) { // тело функции } |
Список аргументов может быть разбит на несколько строк, где каждая строка имеет один отступ. В этом случае первый аргумент ДОЛЖЕН быть на следующей строке и ДОПУСКАЕТСЯ только один агрумент на строку.
1 2 3 4 5 6 7 |
public function aVeryLongMethodName( ClassTypeHint $arg1 , string & $arg2 , array $arg3 = [] ) { // тело метода } |
В объявлении методов/функций всегда явно указывайте тип аргументов и возвращаемых значений:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// Хорошо /** * 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 запросы следует форматировать следующим образом, ориентируясь на ключевые слова:
1 2 3 4 5 |
$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' ; |
Используйте параметры для безопасной подстановки в запрос данных, введенных пользователями (даже если вы уверены, что переменная не может содержать одинарных кавычек. Никогда не доверяйте данным, передаваемым пользователями):
1 2 3 4 |
$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.
.
Поблагодарили: 48 раз
Насчёт стиля по закрывающим фигурным скобкам - очень спорный вопрос. Мне удобнее когда скобки находятся друг над или под другом - сразу видно какая скобка к чему относится и компиляторы подсвечивают блоки кода и таким образом расположенные скобки. У вас почему-то приняли за ствндарт когда открывающая скобка в конце строки открывающей блок кода , а закрываюзщая где-то там под блоком. По-моему это плохой стиль для таких блоков.
Поблагодарили: 180 раз
Касательно расположения скобок я того же мнения + привык за всю историю существования кота именно к такому положению. Но принятый за основу psr-12 гласит именно о таком "кривом" расположении ... типа этот стандарт международно признаный ...
Поблагодарили: 217 раз
Предлагаемый вариант с фигурными скобками хорош тем, что экономит место на экране. Занимать целую строку открывающей скобкой так себе дело. Плюс практически в любом редакторе есть индикация закрывающей/открывающей скобки. Так что здесь рациональнее именно так.
По замене табуляторов на 4 пробела не совсем интересно. Хотя табуляторы в разных редакторах имеют разную длину, так что пробелами нерационально, зато гарантировано ровно будет. ((