The following code style is used for Cotonti CMF core and official extensions development. If you want to pull-request code into the core, consider using it. We aren't forcing you to use this code style for your application based on Cotonti. Feel free to choose what suits you better.
This common rules consider all source code files in the project (HTML, PHP, CSS, JS, etc.) if other is not defined.
There is not a hard limit on line length. Each line of text in your code should be at most 120 characters long.
Put the line break wherever it makes the most aesthetic sense, not necessarily the breaking point closest to 120 characters.
…in addition to general rules
PHP code should follow the PSR-12, and the PSR-1 coding standards and the PSR-4 autoloading standard.
If you are using PhpStorm, it can help you to maintain the required code style. You can turn it on in Settings
→ Editor
→ Code Style
→ PHP
→ Set from
→ PSR12
.
<?php
or <?=
opening tags and MUST NOT use the other tag variations such as <?
.?>
.$a + $b
, $string = $foo . 'bar'
, $i++
, not $a+$b
.$one, $two
, but not $one,$two
.===
(inequality !==
).declare(strict_types=1);
directive.//
and not #
.All PHP files should contain some documentation info formatted as PHPDoc.
It is very good when the header describes the variables that are used in this file, but are defined somewhere else.
Standard header for new files (this template of the header must be included at the start of all Cotonti files):
/** * @package {PACKAGENAME} * @version {VERSION} * @copyright (c) 2008-2023 Cotonti Team * @license BSD License * * @var array<string, mixed> $user * @var XTemplate $t */
@version
is optional. For example, if you are developing some extension, you do not need to define @version
tag as version info already set in extension setup file and may confuse other developers.
Do not forget to comment the class. Classes need a separate @package definition, it is the same as the header package name.
/** * 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
Do not forget to comment the functions. Each function should have at least a comment of what this function does. Also, it is recommended to document the parameters too. Use @param
, @return
, @throws
in that order.
/** * Returns total number of records contained in a table * @param string $tableName Table name * @return int * @throws Exception if the table name is exists */ public function countRows($tableName)
When the @param
or @return
attributes are redundant due to the use of native types, they can be omitted:
/** * Execute the job. */ public function handle(AudioProcessor $processor): void { // }
However, when the native type is generic, specify the generic type through the use of the @param
or @return
attributes:
/** * Get the attachments for the post. * @return array<int, Attachment> */ public function attachments(int $postId): array { // ... }
All names must be descriptive, but concise. Seeing the name of a class or variable, it should be clear what they are needed for. For example: $CurrentUser
, getUserData($id)
.
Class names MUST be declared in PascalCase. For example: Controller
, Model
.
Constants MUST be declared in all upper case with underscore separators. For example: STATUS_PUBLISHED
.
Directory/namespace names in lower case
We DO NOT USE underscores to denote class private properties and methods.
Variable names MUST be in camelCase. Each word, except the first one, must begin with a capital letter. For example: $currentUser
is correct, but $current_user
and $currentuser
are not.
Situations when one-character variable names are allowed: language strings: $L
, string resources: $R
, and when the variable is an index for some looping construct.
Loop Indices:
In this case, the index of the outer loop should always be $i
. If there's a loop inside that loop, its index should be $j
, followed by $k
, and so on. If the loop is being indexed by some already-existing variable with a meaningful name, this guideline does not apply, example:
for ($i = 0; $i < $outerSize; $i++) { for ($j = 0; $j < $innerSize; $j++) { foo($i, $j); } }
Functions and methods names MUST be in camelCase. The exception is Cotonti core functions. Good function names are printLoginStatus()
, getUserData()
, etc.
In Cotonti we are using standard cot_
prefix for any function to avoid naming conflicts. It is standard for the core API to be reusable: cot_mailPrepareAddress($address)
.
But you are not forced for use cot_
prefix in your plugins, unless those functions are going to be reused as the core API. You can use your own prefix within your extension API.
Function Arguments are subject to the same guidelines as variable names.
The basic philosophy here is to not hurt code clarity for the sake of laziness. This has to be balanced by a little bit of common sense, though: printLoginStatusForAGivenUser()
goes too far, for example - that function would be better named printUserLoginStatus()
, or just printLoginStatus()
.
All PHP types and values should be used lowercase. That includes true
, false
, null
.
We use operators for type casting, not functions. And only their short forms. Put one space after the operator.
$value1 = (int) $argument1; // right $value2 = (bool) $argument2; // right $value3 = (integer) $argument3; // wrong $value4 = invtal($argument2, 8); // only if it is really necessary
Changing type of an existing variable is considered as a bad practice. Try not to write such code unless it is really necessary.
public function save(Transaction $transaction, int $argument2 = 100) { $transaction = new Connection; // bad $argument2 = 200; // good }
If string doesn't contain variables or single quotes, use single quotes.
$str = 'Like this.';
If string contains single quotes you can use double quotes to avoid extra escaping.
Variable substitution:
$str1 = "Hello $username!"; $str2 = "Hello {$username}!";
The following is not permitted:
$str3 = "Hello ${username}!";
Concatenation
Add spaces around dot when concatenating strings:
$name = 'Cotonti' . ' CMF';
When string is long format is the following:
$sql = 'SELECT * ' . 'FROM post ' . 'WHERE id = 121 ';
For arrays we're using short array syntax.
$arr = [3, 14, 15, 'Cotonti', 'CMF'];
If there are too many elements for a single line, each element is located on a separate line. After the last element we put a comma.
$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; } // The following is NOT allowed: if (!$model && null === $event) throw new Exception('test');
The keyword elseif
SHOULD be used instead of else if
so that all control keywords look like single words.
Expressions in parentheses MAY be split across multiple lines, where each subsequent line is indented at least once. When doing so, the first condition MUST be on the next line. The closing parenthesis and opening brace MUST be placed together on their own line with one space between them. Boolean operators between conditions MUST always be at the beginning of the line.
if ( $expr1 && $expr2 ) { // if body }
Prefer avoiding else
after return
where it makes sense:
$result = $this->getResult(); if (empty($result)) { return false; } else { // process result }
is better as:
$result = $this->getResult(); if (empty($result)) { return false; } // process result
A switch
structure looks like the following. Note the placement of parentheses, spaces, and braces.
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'; }
Ternary operators should only be used to do very simple things. Preferably, they will only be used to do assignments, and not for function calls or anything complex at all. They can be harmful to readability if used incorrectly, so don't fall in love with saving typing by using them, examples:
// Bad place to use them ($i < $size && $j > $size) ? do_stuff($foo) : do_stuff($bar); // OK place to use them $min = ($i < $j) ? $i : $j;
The term "class" refers to all classes, interfaces, and traits.
extends
and implements
keywords MUST be declared on the same line as the class name. Lists of implements
and, in the case of interfaces, extends
MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one interface per line.Public and protected variables SHOULD be declared at the top of the class before any method declarations. Private variables should also be declared at the top of the class but may be added right before the methods that are dealing with them in cases where they are only related to a small subset of the class methods.
<?php /** * File doc block */ namespace Vendor\Package; use FooClass; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; /** * Class doc block */ class ClassName extends ParentClass implements \ArrayAccess, \Countable { public $publicProp1; public $publicProp2; protected $protectedProp; private $privateProp; public function someMethod() { // ... } }
Opening brace of a function SHOULD be on the line after the function declaration, and the closing brace MUST go on the next line following the body.
In the argument list there MUST be one space after each comma.
function foo(int $arg1, string &$arg2, array $arg3 = []) { // function body }
Argument lists MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one argument per line.
public function aVeryLongMethodName( ClassTypeHint $arg1, string &$arg2, array $arg3 = [] ) { // method body }
In the methods/functions declaration, always specify the native types of arguments and return values:
// Good /** * Execute the job. */ public function handle(AudioProcessor $processor): void { // } // Not good /** * Execute the job. * @param AudioProcessor processor * @return void */ public function handle($processor) { // } // Bad /** * Execute the job. */ public function handle($processor) { // }
When making a method or function call, there MUST NOT be a space between the method or function name and the opening parenthesis, there MUST NOT be a space after the opening parenthesis, and there MUST NOT be a space before the closing parenthesis. In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma.
SQL Statements are often unreadable without some formatting, since they tend to be big at times. Though the formatting of sql statements adds a lot to the readability of code. SQL statements should be formatted in the following way, basically writing keywords:
$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';
Use parameters to safely substitute user-supplied data into the query (even if you are sure the variable cannot contain single quotes - never trust your input):
$result = Cot::$db->query( 'SELECT * FROM ' . Cot::$db->forum_topics . ' WHERE ft_cat = :cat AND ft_id = :topicId', ['cat' => $section, 'topicId' => $param] );
Avoid DB specific SQL
<>
. !=
is also supported by most databases, but it's better to use <>
.INSERT ... ON DUPLICATE KEY UPDATE
- is a MySQL specific extension to SQL.`
for table and column names are MySQL specific. Better use CotDB::quoteTableName()
and CotDB::quoteColumnName()
or abbreviations of these methods: CotDB::quoteT()
, CotDB::quoteC()
.let
rather than var
to declare variables and const
to declare constants.const COLOR_ORANGE = "#ffa500"
. Most variables are constants in a different sense: they don't change value after assignment. But with different function launches their value may vary. Such variables should use const
and camelCase in the name.===
(inequality !==
).let single = 'single-quoted';
.alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3.
.
Bedankt: 42 tijden
Насчёт стиля по закрывающим фигурным скобкам - очень спорный вопрос. Мне удобнее когда скобки находятся друг над или под другом - сразу видно какая скобка к чему относится и компиляторы подсвечивают блоки кода и таким образом расположенные скобки. У вас почему-то приняли за ствндарт когда открывающая скобка в конце строки открывающей блок кода , а закрываюзщая где-то там под блоком. По-моему это плохой стиль для таких блоков.
Bedankt: 180 tijden
Касательно расположения скобок я того же мнения + привык за всю историю существования кота именно к такому положению. Но принятый за основу psr-12 гласит именно о таком "кривом" расположении ... типа этот стандарт международно признаный ...
Bedankt: 211 tijden
Предлагаемый вариант с фигурными скобками хорош тем, что экономит место на экране. Занимать целую строку открывающей скобкой так себе дело. Плюс практически в любом редакторе есть индикация закрывающей/открывающей скобки. Так что здесь рациональнее именно так.
По замене табуляторов на 4 пробела не совсем интересно. Хотя табуляторы в разных редакторах имеют разную длину, так что пробелами нерационально, зато гарантировано ровно будет. ((