Вы здесь

Кодирование PHP

Стандарты кодирования Друпала основаны на PEAR Coding standards. При комментировании и именовании следует использовать американское написание слов (например color, а не colour). Стандарты кодирования не зависят от версии Друпала, являются действительными для любой версии и весь новый код должен им соответствовать.

Отступы и пробелы

  • Отступы создаются двумя пробелами, табуляция не используется
  • В конце строк пробелы не используются
  • Для переноса строк используется запись \n (стандарт Unix, запись \r\n (стандарт Windows) не используется)
  • Все файлы должны заканчиваться пустой строкой. Это позволит улучшить читаемость файла после применения патча, когда строки добавляются в конец файла

Операторы

Все двойные операторы (операторы работающие с двумя значениями) — +, -, =, !=, ==, > и так далее — должны отделяться от остального текста пробелом до и после оператора (для удобства чтения), например $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; ?>

Line length and wrapping

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>"
  • Одиночная кавычка используется в строке перевода. Например, строка "He's a good person." в одиночных кавычках будет выглядеть как 'He\'s a good person.'. Такой вариант записи будет неправильно обработан генераторами .pot-файлов, кроме того, его и не слишком удобно читать

Объединение строк

Всегда используйте пробел между точкой и объединяемыми частями, для улучшения читаемости.

<?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-кода всегда используйте именно такую запись тегов:

<?php
 
?>
(XML-стиль, не используйте краткую запись — <? ?> — или какую-либо другую запись). Это обязательное требование Друпала, которое позволяет использовать PHP-код в разных ОС и разных условиях обработки/выполнения кода.

Отметьте: заключительная часть ?>, должна быть опущена во всех кодовых файлах (.module, .inc и так далее). Закрывающая часть является необязательной и её отсутствие позволяет предотвратить учёт использования ненужных пробелов в конце файла, что может вызвать проблемы в каких-нибудь системах. Дополнительная информация доступна в документе PHP Code tags.

Точка с запятой

Язык PHP требует использования точки с запятой в конце большинства строк, но позволяет опустить их использование в конце блоков кода. Стандарты кодирования Друпала требуют использования точки с запятой всегда, даже если это конец блока кода. В частности, точку с запятой следует использовать и в однострочных блоках:

Правильно:

<?php print $tax; ?>

Неправильно:

<?php print $tax ?>

CVS-заголовок

Все кодовые файлы Друпала должны содержать следующий блок комментария, с которого должен начинаться файл:

<?php
// $Id$

Этот тег будет автоматически расширен при использовании CVS до содержания в нём полезной информации, например:

<?php
// $Id: CODING_STANDARDS.html,v 1.7 2005/11/06 02:03:52 webchick Exp $

Отметьте: пункт «CVS-заголовок» устарел, так как в настоящее время, для хранения кода, начал использоваться Git.

Примеры URL

Используйте запись 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

Константы

  • Константы должны всегда писаться в верхнем регистре
  • Слова должны разделяться с использованием знака подчёркивания
  • Префикс констант определяется названием модуля в котором они используются (префикс пишется также в верхнем регистре)
  • В Друпале 8 и более поздних версиях, константы должны определяться с использованием ключевого слова PHP const (вместо define(), потому что это улучшает быстродействие):

    <?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.

Namespaces

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

"use"-ing classes

  • In a file that declares a namespace, classes not in that namespace must be specified with a "use" statement at the top of the file. That includes global classes, including stdClass.
  • In a file that does not declare a namespace (and is therefore in the global namespace), classes in any namespace other than global must be specified with a "use" statement at the top of the file.
  • When importing a class with "use", do not include a leading \. (This is recommended by the PHP documentation.)
  • Never specify a class name in code with a \ in it. If it will be used, specify it at the top of the file with a "use" statement.
  • When specifying a class name in a string, use its full name including namespace, without leading \.

    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.

  • Specify a single class per use statement. Do not specify mutiple classes in a single use statement.
  • As an alternative for easier copy-paste ability, hook implementations may, at the module author's discretion, use a full class name inline rather than importing it with a "use" statement if the class is used only once.
  • API documentation (in .api.php files) should use full class names. Note that if a class is used more than once in multiple hook signatures it must still be "use"ed and then only the short names of the class used in the function.

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.

Class aliasing

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

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.

Naming conventions

  • Classes and interfaces should use UpperCamel naming.
  • Methods and class properties should use lowerCamel naming.
  • Classes should not use underscores in class names unless absolutely necessary to derive names inherited class names dynamically. That is quite rare, especially as Drupal does not mandate a class-file naming match.
  • Names should not include "Drupal".
  • Class names should not have "Class" in the name.
  • Interfaces should always have the suffix "Interface".
  • Test classes should always have the suffix "Test".
  • Protected or private properties and methods should not use an underscore prefix.
  • Classes and interfaces should have names that stand alone to tell what they do without having to refer to the namespace, read well, and are as short as possible without losing functionality information or leading to ambiguity. Notes:
    • If necessary for clarity or to prevent ambiguity, include the last component of the namespace in the name.
    • Exception for Drupal 8.x: due to the way database classes are loaded, do not include the database engine name (MySQL, etc.) in engine-specific database class names.
    • Exception for test classes: Test classes only need to be unambiguous within the context of the module they are testing.

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;
  }
}
?>

Use of interfaces

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.

Visibility

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.

Type hinting

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.

  • DO specify a type when conformity to a specific interface is an assumption made by the function or method. Specifying the required interface makes debugging easier as passing in a bad value will give a more useful error message.
  • DO NOT specify a type for a class. If specifying a type, always specify an Interface. That allows other developers to provide their own implementations if necessary without modifying existing code.

Example:

<?php
// Wrong:
function make_cat_speak(GarfieldTheCat $cat) {
  print
$cat->meow();
}

// Correct:
function make_cat_speak(FelineInterface $cat) {
  print
$cat->meow();
}
?>

Instantiation

Creating classes directly is discouraged. Instead, use a factory function that creates the appropriate object and returns it. This provides two benefits:

  • It provides a layer of indirection, as the function may be written to return a different object (with the same interface) in different circumstances as appropriate.
  • PHP does not allow class constructors to be chained, but does allow the return value from a function or method to be chained.

Chaining

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.

PHP Exceptions

Basic conventions

  • As Exceptions are classes, they should follow all coding standards for object-oriented code like any other class.
  • All Exceptions must end with the suffix "Exception".
  • All Exceptions must include an appropriate translated string as a message, unless the Exception is thrown very early in the bootstrap process before the translation system is available. That is extremely rare.
  • Exception classes should be named for the subsystem to which they relate, and the type of error. That is, [Subsystem][ErrorType]Exception.

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

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);
}
?>

Inheritance

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.
?>

Temporary placeholders and delimiters

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.

Finding your placeholders

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

При создании модуля или темы учтите, что проект может использоваться на сайтах по всему миру, на которых используются языки в кодировке Unicode, а не ASCII или европейские кодировки. Некоторые встроенные в PHP функции по обработке строк, некорректно работают с кодировкой Unicode.

По этой причине, в Друпале реализованы функции, которые позволяют заменить стандартные текстовые функции PHP. При кодировании в Друпале, всегда используйте эти заменители, за исключением оговоренных случаев. Проверить использование этих заменителей можно с помощью модуля Coder.

Вот эти функции-заменители:

  • drupal_strlen(): заменяет функцию PHP strlen(). Отметьте: если вы используете strlen() только для проверки того, что длина строки не равна нулю, то заменитель можно не использовать
  • drupal_strtolower(): заменяет функцию PHP strtolower()
  • drupal_strtoupper(): заменяет функцию PHP strtoupper() (смотрите также drupal_ucfirst())
  • drupal_substr(): заменяет функцию PHP substr() (смотрите также truncate_utf8() и drupal_truncate_bytes())

Полный список всех функций оборачивающих обычные функции PHP в Друпале: PHP wrapper functions. Если вы кодируете с текстом, то следует также прочитать руководство по обработке текста в безопасном стиле.

Пишите код совместимый с E_ALL

Установка уровня сигнализации об ошибках

Версии Друпала 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

Используйте isset() или !empty()

Если хотите протестировать значение переменной, элемента массива или свойство объекта, то используйте:

<?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 SimpleTest coding standards

Naming conventions

Drupal 8

  • Non-PSR-0 tests: (suffix: TestCase)
    Foo[Web|Unit]?TestCase extends [Web|Unit]TestBase
  • PSR-0 tests: (suffix: Test)
    namespace Drupal\$module\Tests;

    Foo[Web|Unit]Test extends [Web|Unit]TestBase
  • In any case, you need to import:
    use Drupal\simpletest\[Web|Unit]TestBase;

File names

  • For modules, a single [module name].test file is the general rule. It should be placed directly in the module folder.
  • For core facilities (all API functions that are in includes/), tests files are named [facility name].test and are placed in the modules/system/tests folder.
  • If other supporting files are required for the tests to work (for example, hidden modules, XML files, images) they should be located in a /tests/ subdirectory within the module directory.

Mock module names

  • If a test requires defining hooks, it can create a mock module.
  • This module should be named [test file name]_test.module (its associated info file would then be [test file name]_test.info.
  • Set the property hidden = TRUE in the .info file to prevent it from being enabled in the interface.

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.

Splitting up your tests

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:

  • ensure that the code operates in a certain way
  • provide a runnable example of the code's usage

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:

  • Each test becomes a simple, self-contained example of how the function can be used, answering the question "Given these inputs what behavior can I expect?"
  • Since each test only looks at a single aspect you'll find that you're much more willing to test edge cases. Unlike kitchen sink test you won't have to worry about "breaking up the flow" with an extra test.

Test template

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',
    );
  }
?>

  • The very first function has to be getInfo().
  • name, description and group are not enclosed in t() calls.
  • name should be short and should not contain any period. It should describes an overview of what subsystem is tested by the class; for example, "User access rules" or "Menu link creation/deletion"
  • description should start with a verb describing the test. It should end with a period.
  • group should be the human-readable name of the module (Node, Statistics), or the human-readable name of the Drupal facility tested (Form API or XML-RPC).
  • There is no PHPDoc on this function since it is an inherited method.

<?php
 
public function setUp() {
   
parent::setUp('kitten_module', 'basket_module');
    
// Setup tasks go here.
 
}
?>

  • setUp() is optional. If your test is for specific module(s), make sure to pass each module's name as a parameter to setUp() so it will be automatically enabled for you. If the module don't have to perform any particular setup, it should not be defined.
  • setUp() and tearDown() are called before and after every test function is called.
  • There is no PHPDoc on this function since it is an inherited method.

<?php
 
public function tearDown() {
   
// Teardown tasks go here.
    
parent::tearDown();
  }
?>

  • tearDown() should not be used, except in very particular cases. Simpletest will simply drop the test database, so you don't even have to undo the setup tasks performed in setUp().
  • setUp() and tearDown() are called before and after every test function is called.
  • There is no PHPDoc on this function since it is an inherited method.

<?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.
}
?>