Стандарты кодирования Друпала основаны на PEAR Coding standards. При комментировании и именовании следует использовать американское написание слов (например color, а не colour). Стандарты кодирования не зависят от версии Друпала, являются действительными для любой версии и весь новый код должен им соответствовать.
Все двойные операторы (операторы работающие с двумя значениями) — +, -, =, !=, ==, > и так далее — должны отделяться от остального текста пробелом до и после оператора (для удобства чтения), например $foo = $bar;, а не $foo=$bar;. Одинарные операторы (операторы имеющие дело с одним значением) — ++ — не отделяются пробелом от переменной или числа с которым они имеют дело.
Используйте пробел между (типом) и $переменной: (int) $mynumber.
Конструкции включают if, for, while, switch и так далее. Вот пример записи конструкции:
if (condition1 || condition2) {
action1;
}
elseif (condition3 && condition4) {
action2;
}
else {
defaultaction;
}
Отметьте: не используйте else if, всегда используйте elseif.
Для конструкции switch следует использовать следующую запись:
switch (condition) {
case 1:
action1;
break;
case 2:
action2;
break;
default:
defaultaction;
}
Для конструкции do-while следует использовать следующую запись:
do {
actions;
} while ($condition);
В шаблонах, используйте двоеточие вместо фигурных скобок. Note that there should not be a space between the closing paren after the control keyword, and the colon, and HTML/PHP inside the control structure should be indented. For example:
<?php if (!empty($item)): ?>
<p><?php print $item; ?></p>
<?php endif; ?>
<?php foreach ($items as $item): ?>
<p><?php print $item; ?></p>
<?php endforeach; ?>
The following rules apply to code. See Doxygen and comment formatting conventions for rules pertaining to comments.
• In general, all lines of code should not be longer than 80 chars.
• Lines containing longer function names, function/class definitions, variable declarations, etc are allowed to exceed 80 chars.
• Control structure conditions may exceed 80 chars, if they are simple to read and understand:
if ($something['with']['something']['else']['in']['here'] == mymodule_check_something($whatever['else'])) {
...
}
if (isset($something['what']['ever']) && $something['what']['ever'] > $infinite && user_access('galaxy')) {
...
}
// Non-obvious conditions of low complexity are also acceptable, but should
// always be documented, explaining WHY a particular check is done.
if (preg_match('@(/|\\)(\.\.|~)@', $target) && strpos($target_dir, $repository) !== 0) {
return FALSE;
}
• Conditions should not be wrapped into multiple lines.
• Control structure conditions should also NOT attempt to win the Most Compact Condition In Least Lines Of Code Award™:
// DON'T DO THIS!
if ((isset($key) && !empty($user->uid) && $key == $user->uid) || (isset($user->cache) ? $user->cache : '') == ip_address() || isset($value) && $value >= time())) {
...
}
Instead, it is recommended practice to split out and prepare the conditions separately, which also permits documenting the underlying reasons for the conditions:
// Key is only valid if it matches the current user's ID, as otherwise other
// users could access any user's things.
$is_valid_user = (isset($key) && !empty($user->uid) && $key == $user->uid);
// IP must match the cache to prevent session spoofing.
$is_valid_cache = (isset($user->cache) ? $user->cache == ip_address() : FALSE);
// Alternatively, if the request query parameter is in the future, then it
// is always valid, because the galaxy will implode and collapse anyway.
$is_valid_query = $is_valid_cache || (isset($value) && $value >= time());
if ($is_valid_user || $is_valid_query) {
...
}
Note: This example is still a bit dense. Always consider and decide on your own whether people unfamiliar with your code will be able to make sense of the logic.
Пробел используется:
Пробел не используется:
$var = foo($bar, $baz, $quux);
В случае, когда используется блок связанных команд, пробелы могут использоваться для их выравнивания — это улучшает чтение:
$short = foo($bar);
$long_variable = foo($baz);
function funstuff_system($field) {
$system["description"] = t("This module inserts funny text into posts randomly.");
return $system[$field];
}
При вызове класса не имеющего аргументов, всегда используйте круглые скобки:
$foo = new MyClassName();
Это сохраняет последовательность с конструкциями имеющими аргументы:
$foo = new MyClassName($arg1, $arg2);
Отметьте: если название класса является переменной, переменная должна быть заявлена до получения названия класса, после чего может быть использована конструкция:
$bar = 'MyClassName';
$foo = new $bar();
$foo = new $bar($arg1, $arg2);
Массивы должны оформляться с использованием пробела между каждым элементом (после запятой) и оператором указания, — => — если он необходим (с двух сторон):
$some_array = array('hello', 'world', 'foo' => 'bar');
Отметьте: когда строка использует более 80 знаков (обычный случай при кодировании форм и меню), каждый элемент следует располагать на новой строке с отступом в один уровень:
$form['title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#size' => 60,
'#maxlength' => 128,
'#description' => t('The title of your node.'),
);
Отметьте: запятая в конце последнего элемента массива — это не опечатка! Это помогает предотвратить ошибку, если другой элемент будет помещён в конец списка позже.
В Друпале нет жёсткого правила по использованию одинарных или двойных кавычек. Где возможно, сохраняйте последовательность с уже существующим кодом и уважайте стиль другого разработчика.
Мы бы хотели только заметить здесь пару вещей. Как известно, строки с одинарными кавычками обрабатываются быстрее, так как обработчик не смотрит на используемые переменные. Поэтому рекомендуется их и использовать, но за исключением двух случаев:
"<h2>$header</h2>"
Всегда используйте пробел между точкой и объединяемыми частями, для улучшения читаемости.
<?php
$string = 'Foo' . $bar;
$string = $bar . 'foo';
$string = bar() . 'foo';
$string = 'foo' . 'bar';
?>
При объединении простых переменных, используйте двойные кавычки с переменной внутри них; в других случаях используйте одиночные кавычки.
<?php
$string = "Foo $bar";
?>
При объединении с использованием оператора присваивания, — .= — используйте пробелы с каждой стороны оператора.
<?php
$string .= 'Foo';
$string .= $bar;
$string .= baz();
?>
Комментирование файлов должно удовлетворять требованиям Doxygen, смотрите документ Doxygen и комментирование кода.
Использование любого оператора (require_once() — безусловное, include_once() — условное) подключения файлов классов гарантирует, что файлы будут подключены только один раз. Эти операторы используют один список подключенных файлов, так что вы можете не беспокоиться о смешивании операторов подключения. Файл подключенный с помощью оператора require_once() не будет повторно подключен при использовании оператора include_once().
Отметьте: include_once() и require_once() — это операторы, не функции. Вы не должны использовать для них правила оформления функций и писать вместе с ними название файла.
При подключении кода из той же папки или подпапки, начинайте запись пути к файлу с точки:
include_once ./includes/mymodule_formatting.inc
В Drupal 7 и более поздних версиях используйте DRUPAL_ROOT:
require_once DRUPAL_ROOT . '/' . variable_get('cache_inc', 'includes/cache.inc');
Для определения границ PHP-кода всегда используйте именно такую запись тегов:
<?php
?>
Отметьте: заключительная часть ?>, должна быть опущена во всех кодовых файлах (.module, .inc и так далее). Закрывающая часть является необязательной и её отсутствие позволяет предотвратить учёт использования ненужных пробелов в конце файла, что может вызвать проблемы в каких-нибудь системах. Дополнительная информация доступна в документе PHP Code tags.
Язык PHP требует использования точки с запятой в конце большинства строк, но позволяет опустить их использование в конце блоков кода. Стандарты кодирования Друпала требуют использования точки с запятой всегда, даже если это конец блока кода. В частности, точку с запятой следует использовать и в однострочных блоках:
Правильно:
<?php print $tax; ?>
Неправильно:
<?php print $tax ?>
Все кодовые файлы Друпала должны содержать следующий блок комментария, с которого должен начинаться файл:
<?php
// $Id$
Этот тег будет автоматически расширен при использовании CVS до содержания в нём полезной информации, например:
<?php
// $Id: CODING_STANDARDS.html,v 1.7 2005/11/06 02:03:52 webchick Exp $
Отметьте: пункт «CVS-заголовок» устарел, так как в настоящее время, для хранения кода, начал использоваться Git.
Используйте запись example.com для всех примеров URL, как рекомендуется в RFC 2606:
3. Reserved Example Second Level Domain Names
The Internet Assigned Numbers Authority (IANA) also currently has the following second level domain names reserved which can be used as examples.
• example.com
• example.net
• example.org
Название внутренних функций (предназначенных для локального использования в конкретном модуле) должны начинаться знаком подчёркивания:
_node_get()
$this->_status
<?php
/**
* Indicates that the item should be removed at the next general cache wipe.
*/
const CACHE_TEMPORARY = -1;
?>
Отметьте: const не работает с PHP-выражениями. В этом случае (когда константа определяется на основе условий или не является буквенным значением) следует использовать define():
<?php
if (!defined('MAINTENANCE_MODE')) {
define('MAINTENANCE_MODE', 'error');
}
?>
Глобальные переменные
Если вам нужно определить глобальные переменные, то их названия должны начинаться со знака подчёркивания, затем должно идти название связанное с названием проекта и затем опять знак подчёркивания.
Классы именуются с использованием смешанного регистра, при этом первая буква пишется в верхнем регистре (CamelCase). Пример:
<?php
abstract class DatabaseConnection extends PDO {
?>
Методы классов и свойства именуются с использованием смешанного регистра, при этом первая буква пишется в нижнем регистре (lowerCamelCase). Пример:
<?php
public $lastStatement;
?>
The use of private class methods and properties should be avoided -- use protected instead, so that another class could extend your class and change the method if necessary. Protected (and public) methods and properties should not use an underscore prefix, as was common in PHP 4-era code.
For more information on class and OO standards, see Object-oriented code.
Все файлы документации должны использовать расширение .txt для того, чтобы сделать их просмотр в операционной системе Windows более простым. Название файла должно записываться в верхнем регистре, а расширение в нижнем. Примеры: README.txt, INSTALL.txt, CHANGELOG.txt.
PHP 5.3 introduces namespaces to the language. This page documents how namespaces should be referenced within Drupal. Familiarity with the link above is assumed.
Note: Due to a bug in the syntax formatter used by Drupal.org, the \ character cannot be used within a code sample. Until that bug is resolved, the * character is used in place of \ below. Please pardon our dust, and help fix that bug.
Not all files in Drupal declare a namespace. As of Drupal 8 an increasing number do, but not all, and prior to Drupal 8 virtually no code used namespaces in order to remain compatible with PHP 5.2. There are therefore two slightly different standards
Escape the namespace separator in double-quoted strings: "Drupal\\Context\\ContextInterface"
Do not escape it in single-quoted strings: 'Drupal\Context\ContextInterface'
As stated elsewhere, single-quoted strings are generally preferred.
Example:
<?php
/**
* @file
* Definition of Drupal*Subsystem*Foo.
*/
namespace Drupal*Subsystem;
// Global classes must be imported into a namespace.
use DateTime;
// This imports just the Cat class from the Drupal*Othersystem namespace.
use Drupal*Othersystem*Cat;
// Bar is a class in the Drupal*Subsystem namespace in another file.
// It is already available without any importing.
/**
* Defines a Foo.
*/
class Foo {
/**
* Constructs a new Foo object.
*/
public function __construct(Bar $b, Cat $c) {
$d = new DateTime();
}
}
?>
<?php
/**
* @file
* The Example module.
*
* This file is not part of any namespace, so all global namespaced classes
* are automatically available.
*/
use Drupal*Subsystem*Foo;
/**
* Does stuff with Foo stuff.
*
* @param Drupal*Subsystem*Foo $f
* A Foo object used to bar the baz.
*/
function do_stuff(Foo $f) {
// The DateTime class does not need to be imported as it is already global
$d = new DateTime();
}
?>
Note that this puts all of the \ usage up at the top of the file, and also creates a nice index of everything a given file depends on.
PHP allows classes to be aliased when they are imported into a namespace. In general that should not be done unless doing so would cause a name collision. If that happens, alias both colliding classes by prefixing the next higher portion of the namespace.
Example:
<?php
use Foo*Bar*Baz as BarBaz;
use Stuff*Thing*Baz as ThingBaz;
/**
* Tests stuff for the whichever.
*/
function test() {
$a = new BarBaz(); // This will be Foo\Bar\Baz
$b = new ThingBaz(); // This will be Stuff\Thing\Baz
}
?>
That helps keep clear which one is which, and where it comes from. Aliasing should only be done to avoid collisions of that sort.
Modules creating classes should place their code inside a custom namespace.
The convention for those namespaces is
namespace Drupal*example_module
To support autoloading these classes should be placed inside the correct folder: sites/all/modules/example_module/lib/Drupal/example_module
Drupal follows common PHP conventions for object-oriented code, and established industry best practices. As always, though, there are Drupal-specific considerations.
Stand-alone name examples:
Namespace | Good name | Bad names |
---|---|---|
Drupal\Core\Database\Query\ | QueryCondition | Condition (ambiguous) DatabaseQueryCondition (Database doesn't add to understanding) |
Drupal\Core\FileTransfer\ | LocalFileTransfer | Local (ambiguous) |
Drupal\Core\Cache\ | CacheDatabaseDriver | Database (ambiguous/misleading) DatabaseDriver (ambiguous/misleading) |
Drupal\entity\ | Entity EntityInterface |
DrupalEntity (unnecessary words) EntityClass (unnecessary words) |
Drupal\comment\Tests\ | ThreadingTest | CommentThreadingTest (only needs to be unambiguous in comment context) Threading (not ending in Test) |
A complete example of class/interface/method names:
<?php
interface FelineInterface {
public function meow();
public function eatLasagna($amount);
}
class GarfieldTheCat implements FelineInterface {
protected $lasagnaEaten = 0;
public function meow() {
return t('Meow!');
}
public function eatLasagna($amount) {
$this->lasagnaEaten += $amount;
}
}
?>
The use of a separate Interface definition from an implementing class is strongly encouraged. It encourages separation of concerns and allows more flexibility in extending code later. It also makes the documentation easier to read as the documentation is neatly centralized in the interface. All interfaces should be fully documented according to established documentation standards.
If there is even a remote possibility of a class being swapped out for another implementation at some point in the future, split the method definitions off into a formal Interface. A class that is intended to be extended must always provide an Interface that other classes can implement rather than forcing them to extend the base class.
All methods and properties of classes must specify their visibility: public, protected, or private. The PHP 4-style "var" declaration must not be used.
The use of public properties is strongly discouraged, as it allows for unwanted side effects. It also exposes implementation specific details, which in turn makes swapping out a class for another implementation (one of the key reasons to use objects) much harder. Properties should be considered internal to a class.
The use of private methods or properties is strongly discouraged. Private properties and methods may not be accessed or overridden by child classes, which limits the ability of other developers to extend a class to suit their needs.
PHP supports optional type specification for function and method parameters for classes and arrays. Although called "type hinting" it does make a type required, as passing an object that does not conform to that type will result in a fatal error.
Example:
<?php
// Wrong:
function make_cat_speak(GarfieldTheCat $cat) {
print $cat->meow();
}
// Correct:
function make_cat_speak(FelineInterface $cat) {
print $cat->meow();
}
?>
Creating classes directly is discouraged. Instead, use a factory function that creates the appropriate object and returns it. This provides two benefits:
PHP allows objects returned from functions and methods to be "chained", that is, a method on the returned object may be called immediately, like so:
<?php
// Unchained version
$result = db_query("SELECT title FROM {node} WHERE nid = :nid", array(':nid' => 42));
$title = $result->fetchField();
// Chained version
$title = db_query("SELECT title FROM {node} WHERE nid = :nid", array(':nid' => 42))->fetchField();
?>
As a general rule, a method should return $this, and thus be chainable, in any case where there is no other logical return value. Common examples are those methods that set some state or property on the object. It is better in those cases to return $this rather than TRUE/FALSE or NULL.
The use of subclassed Exceptions is preferred over reusing a single generic exception class with different error messages as different classes may then be caught separately.
Example:
<?php
class WidgetNotFoundException extends Exception {}
function use_widget($widget_name) {
$widget = find_widget($widget_name);
if (!$widget) {
throw new WidgetNotFoundException(t('Widget %widget not found.', array('%widget' => $widget_name)));
}
}
?>
try-catch blocks should follow a similar line-breaking pattern to if-else statements, with each catch statement beginning a new line.
Example:
<?php
try {
$widget = 'thingie';
$result = use_widget($widget);
// Continue processing the $result.
// If an exception is thrown by use_widget(), this code never gets called.
}
catch (WidgetNotFoundException $e) {
// Error handling specific to the absence of a widget.
}
catch (Exception $e) {
// Generic exception handling if something else gets thrown.
watchdog('widget', $e->getMessage(), WATCHDOG_ERROR);
}
?>
PHP requires that all exceptions inherit off of the Exception class, either directly or indirectly.
When creating a new exception class, it should be named according to the subsystem they relate to and the error message they involve. If a given subsystem includes multiple exceptions, they should all extend from a common base exception. That allows for multiple catch blocks as necessary.
<?php
class FelineException extends Exception {}
class FelineHairBallException extends FelineException {}
class FelineKittenTooCuteException extends FelineException {}
try {
$nermal = new Kitten();
$nermal->playWith($string);
}
catch (FelineHairBallException $e) {
// Do error handling here.
}
catch (FelineKittenTooCuteException $e) {
// Do different error handling here.
}
catch (FelineException $e) {
// Do generic error handling here.
}
// Optionally also catch Exception so that all exceptions stop here instead of propagating up.
?>
When writing a content filter module, or any code that processes or modifies content, it is tempting to use an obscure character as a place-holder, especially if only your code will see it: But this cannot be guaranteed. Non-printing, invalid or undocumented characters might not be handled correctly in the unlikely event that they are seen by a browser or feed-reader. And the more unlikely they are to be seen – the less likely they are to be tested. This will mean that some code will be written to find and eradicate these insidious characters, possibly including the ones your code is using to do its work.
To avoid this happening, and extending the lifetime of your code, please use an appropriate alpha-numeric string – prefixed by the name of the module (as a name-space) and a hyphen - or underscore _ – and surrounded by […].
If you need delimiting place-holders, the closing delimiter can incorporate a / after the initial [ and may suffix the modulename.
A PCRE such as
'@\[modulename-tag\](.+?)\[/modulename-tag\]@'
or
'@\[modulename-tag\](.+?)\[/tag-modulename\]@'
if you suffixed the modulename as mentioned above can be used to match the string you have previously delimited.
При создании модуля или темы учтите, что проект может использоваться на сайтах по всему миру, на которых используются языки в кодировке Unicode, а не ASCII или европейские кодировки. Некоторые встроенные в PHP функции по обработке строк, некорректно работают с кодировкой Unicode.
По этой причине, в Друпале реализованы функции, которые позволяют заменить стандартные текстовые функции PHP. При кодировании в Друпале, всегда используйте эти заменители, за исключением оговоренных случаев. Проверить использование этих заменителей можно с помощью модуля Coder.
Вот эти функции-заменители:
Полный список всех функций оборачивающих обычные функции PHP в Друпале: PHP wrapper functions. Если вы кодируете с текстом, то следует также прочитать руководство по обработке текста в безопасном стиле.
Версии Друпала 6 игнорируют оповещения E_NOTICE, E_STRICT и E_DEPRECATED в интересах сайтов размещённых в интернете. Чтобы посмотреть все ошибки PHP при разработке или тестировании сайта, можно изменить код файл includes/common.inc с:
<?php
if ($errno & (E_ALL ^ E_DEPRECATED ^ E_NOTICE)) {
?>
на
<?php
if ($errno & (E_ALL | E_STRICT)) {
?>
Версии Друпала 7 оповещают об ошибках всех уровней, которые являются частью E_ALL и позволяют настроить PHP для оповещения об ошибках дополнительных уровней, таких как E_STRICT. Чтобы посмотреть все ошибки PHP при разработке или тестировании сайта, можно добавить в файл .htaccess код:
php_value error_reporting -1
Если хотите протестировать значение переменной, элемента массива или свойство объекта, то используйте:
<?php
if (isset($var))
?>
или
<?php
if (!empty($var))
?>
а не
<?php
if ($var)
?>
если есть вероятность, что переменная $var не определена.
Разница между isset() и !empty() в том, что isset() возвращает значение ИСТИНА даже если значением переменной является пустая строка или ноль. Чтобы определиться, какую переменную нужно использовать, подумайте, является пустая строка или ноль допустимыми значениями.
Следующий код может вызвать ошибку E_NOTICE:
<?php
function _form_builder($form, $parents = array(), $multiple = FALSE) {
// (...)
if ($form['#input']) {
// some code (...)
}
}
?>
В этом примере, переменная $form передаётся в функцию. Если form['#input'] расценивается как истина, то выполняется какой-то код. Однако, если значения $form['#input'] не существует, то функция выведет следующее сообщение об ошибке: Undefined index: #input in includes/form.inc on line 194.
Даже если массив $form уже объявлен и передан в функцию, каждый индекс массива должен быть объявлен явно. Предыдущий код должен быть таким:
<?php
function _form_builder($form, $parents = array(), $multiple = FALSE) {
// (...)
if (!empty($form['#input'])) {
// some code (...)
}
}
?>
Будьте внимательны!
Функция isset() возвращает значение ИСТИНА, когда переменная равна 0 и ЛОЖЬ, когда переменная равна NULL. В некоторых случаях лучше использовать функцию is_null(), особенно, когда тестируется значение переменной возвращаемое SQL-запросом.
Drupal 8
Foo[Web|Unit]?TestCase extends [Web|Unit]TestBase
namespace Drupal\$module\Tests;
Foo[Web|Unit]Test extends [Web|Unit]TestBase
use Drupal\simpletest\[Web|Unit]TestBase;
File names
Mock module names
Test class names
Test class names are camel case and end with TestCase. For example, a test designed to check the node view functionality would be named: NodeViewTestCase.
Test function names
All tests functions are prefixed with test.
When you're unit testing a single function--as opposed to doing functional testing of a module's page--each test function should focus on a specific aspect of the function's operation. The "kitchen sink" style of tests is common in core right now but in it's bad practice. Test should serve two purposes:
Monolithic tests generally fail badly because once one portion of the tests fails it cascades through the later tests. This makes it hard to isolate exactly where the fault lies. By breaking the code up into separate tests you may duplicate some code--though if you're duplicating code between a majority of the tests it should probably go into the setUp() function--but you'll have an isolated test that passes or fails on its own.
You also get several positive side effects by writing smaller tests:
After the opening PHP tag and a blank line comes the @file declaration:
<?php
/**
* @file
* Tests for Aggregator module.
*/
?>
or
<?php
/**
* @file
* Tests for common.inc.
*/
?>
The @file declaration should contain a simple summary of what tests are included in the file. Either for the module, or for the file.
<?php
/**
* Functional tests for the Kitten Basket.
*/
class KittenBasketTestCase extends DrupalWebTestCase {
?>
The comment above the class is mandatory, it is a rephrasing of the 'name' of the test found in getInfo() function ('Kitten basket functionality' -> 'Functional tests for the Kitten basket').
<?php
public static function getInfo() {
return array(
'name' => 'Kitten basket functionality',
'description' => 'Move kittens in and out of their basket and assert that none of them were hurt in the process.',
'group' => 'Kitten basket',
);
}
?>
<?php
public function setUp() {
parent::setUp('kitten_module', 'basket_module');
// Setup tasks go here.
}
?>
<?php
public function tearDown() {
// Teardown tasks go here.
parent::tearDown();
}
?>
<?php
/**
* One-sentence description of what test entails.
*/
public function testExampleThing() {
// Assertions, etc. go here.
}
/**
* One-sentence description of what test entails.
*/
public function testNextExampleThing() {
// Assertions, etc. go here.
}
// Can have many more testFoo functions.
}
?>