Zabbix Code Guidelines

3.04.04.4 (current)| In development:5.0 (devel)| Unsupported:1.82.02.22.43.23.44.2Guidelines

User Tools

Site Tools


Sidebar

coding:php

PHP coding guidelines

General

  • All code and comments should be written in English.
  • Global variables except of PHP predefined ($_GET, $_POST, $_SERVER, …) must not be used.
  • .htaccess files only define settings for the current directory. This is done to avoid the possibility of missing a change for this file if some subdirectory is renamed.
  • Zabbix API methods must be used instead of writing direct SQL queries to database.
Formatting
  • In PHP files only one opening “<?php” tag must exist at the first line of file. Closing “?>” should be omitted.
  • Indentation should consist of tabs.
  • Maximum line length is 120 characters.
Directory hierarchy
  • All classes should be placed under classes directory.
  • If there is bunch of classes that are related to one functionality, they should be placed in a sub-directory.

Example:

/frontends/php/include/classes/export/CExport.php

/frontends/php/include/classes/export/writers/CWriter.php
/frontends/php/include/classes/export/writers/CXmlWriter.php
/frontends/php/include/classes/export/writers/CJsonWriter.php

/frontends/php/include/classes/export/exportelements/CExportElement.php
/frontends/php/include/classes/export/exportelements/CHostExportElement.php
/frontends/php/include/classes/export/exportelements/CItemExportElement.php
Directory hierarchy in MVC
  • MVC is located under directory /frontends/php/app/

Example:

// controller
/frontends/php/app/controllers/CControllerMediatypeEdit.php

// view
/frontends/php/app/views/administration.mediatype.edit.php

// js
/frontends/php/app/views/administration.mediatype.edit.js.php
Define locations
  • Global defines are located in /frontends/php/include/defines.inc.php
  • Translatable defines are located in /frontends/php/include/translateDefines.inc.php

Naming conventions

Variables
  • Variable names should be self describing, short as possible, but still readable.
  • Variables should contain only lowercase letters. Numbers are permitted in variable names but are discouraged in most cases.
  • Each word should be separated by an underscore.

Example:

// correct
$hostid = 12345;
$item_applications = [];
$db_application_prototypes = [];
 
// wrong
$hostId = 12345;
$host_id = 12345;
$itemApplications = [];
$dbapplication_prototypes = [];

Defines and constants
  • Names representing constants (final variables) must be all uppercase using underscore to separate words.

Example:

// global define
define('ZBX_DB_MYSQL', 'MYSQL');
 
// translatable define
define('DATE_FORMAT', _('Y-m-d'));
 
// constant in class
const STATE_NEW = 0;

Functions and methods
  • Function names must always start with a lowercase letter. When a function name consists of more than one word, the first letter of each new word must be capitalized (camelCase).

Example:

// correct
function create() {
    // some logic here
}
 
function getName() {
    // some logic here
}
 
// wrong
function Create() {
    // some logic here
}
 
function get_name() {
    // some logic here
}

  • Abbreviations and acronyms should not be uppercase when used as name.

Example:

// correct
function exportHtmlSource() {
    // some logic here
}
 
// wrong
function exportHTMLSource() {
    // some logic here
}

Classes
  • All Zabbix class names must start with a “C” prefix and be written in CUpperCamelCase. The “C” prefix serves as a historical alternative to namespaces.
  • If class extends another class, child class name should be prefixed to parent class name.

Example:

class CWriter {
    // some logic here
}
 
class CXmlWriter extends CWriter {
    // some logic here
}
 
class CFastXmlWriter extends CXmlWriter {
    // some logic here
}

Filenames
  • Spaces in file names are strictly prohibited.
  • Include and view file names are written in lowercase letters.
  • View file name represents the menu sections and actions separated by dots.

Example:

// Administration -> Media type -> Edit
administration.mediatype.edit.php

  • Class file naming should be named as class name (starting with capital “C”) with “.php” extension.

Example:

class CWriter {} -> CWriter.php
class CXmlWriter {} -> CXmlWriter.php

  • MVC controller class file names should be prefixed with “Controller” name.

Example:

class CControllerProxyEdit {} -> CControllerProxyEdit.php

  • Full file path should not be longer than 80 characters.

Example:

// Good
/frontends/php/include/classes/widgets/fields/CWidgetFieldMsGraphPrototype.php = 79 characters

// Bad
/frontends/php/include/classes/widgets/fields/CWidgetFieldMultiselectGraphPrototype.php = 88 characters
Reason for this is distributive creation. It is not processing filepaths with length more than 99 characters, including the folder with version name.
zabbix-4.4.0alpha3/frontends/php/include/classes/widgets/fields/CWidgetFieldMsGraphPrototype.php

Variables

Variable declaration
  • Each variable must be declared on a separate line.

Example:

// correct
$itemids = [];
$hostids = [];
 
// wrong
$itemids = $hostids = [];

  • Array declaration should be used with brackets from Zabbix version 3.0. Older versions should use array().

// correct for Zabbix >=3.0
$itemids = [];
 
// correct for Zabbix <=2.4
$itemids = array();

  • Variables referencing objects should in some way associate to the class the variable is an object of.

$relation_map = new CRelationMap();

Superglobal variables

See php.net for a list of superglobal variables.

  • Superglobal variables should never be modified during script execution.
  • $_REQUEST values should not be accessed directly, but using the hasRequest() and getRequest() functions instead.

Example:

// correct
if (hasRequest('name')) {
    echo getRequest('name');
}
 
// wrong
if (isset($_REQUEST['name'])) {
   echo $_REQUEST['name'];
}

Variables in SQL queries
  • Variables with single value used as expression values in SQL queries must be escaped using zbx_dbstr() function, regardless of variable value origin.
  • Array variables should use either with dbConditionString() or dbConditionInt() (depending on database field type) functions. Functions contain built-in zbx_dbstr().

Example:

DBselect('SELECT c.actionid FROM conditions c WHERE '.dbConditionString('c.value', $hostids));
DBselect('SELECT oh.operationid FROM opcommand_hst oh WHERE '.dbConditionInt('oh.hostid', $hostids));

  • Constants are not processed by zbx_dbstr().

Example:

// correct
DBselect('SELECT r.rightid FROM rights r WHERE r.permission>'.PERM_DENY);
 
// wrong
DBselect('SELECT r.rightid FROM rights r WHERE r.permission>'.zbx_dbstr(PERM_DENY));

Pre-processing of variables for use in views

All logic related to data visualization should be part of view. No pre-processing aimed to simplify views is allowed in controllers.

Note that this restriction does not apply to situations when we really need to perform complex calculations in controller and pass result of the calculation to a view.

Example:

// correct
// controller
$data["host"]["status"] = $host["status"];
 
// view
if ($data["host"]["status"] == HOST_STATUS_MONITORED) {
    // some logic here
}
 
// wrong
// controller
$data["isHostEnabled"] = ($host["status"] == HOST_STATUS_MONITORED);
 
// view
if ($data["isHostEnabled"]) {
    // some logic here
}

  • Views should use variable $data instead of class variable $this->data.

Example:

// correct
if ($data['groups']) {
    // some logic here
}
 
// wrong
if ($this->data['groups']) {
    // some logic here
}

  • Views should not use API calls, SQL queries and calculations.

Conditional statements

  • Statement arguments should not be included in parenthesis. See also PEAR standard.

Example:

require_once dirname(__FILE__).'/include/hosts.inc.php';

  • The opening brace is written on the same line as the conditional statement.
  • The closing brace is always written on its own line.

// correct
if (hasRequest('add')) {
    // some logic here
}
 
// wrong
if (hasRequest('add'))
{
    // some logic here
}

If/Else/Elseif
  • “if” statements should always use curly braces {}.

Example:

// correct
if (hasRequest('add')) {
    $action = 'add';
}
 
// wrong
if (hasRequest('add'))
    $action = 'add';

  • For “if” statements that include “elseif” or “else”, the formatting conventions are similar to the “if” construct.

Example:

// correct
if (hasRequest('add')) {
    // some logic here
}
 
if (hasRequest('add')) {
    // some logic here
}
else {
    // some logic here
}
 
// correct
if (hasRequest('add')) {
    // some logic here
}
elseif (hasRequest('update')) {
    // some logic here
}
else {
    // some logic here
}
 
// wrong
if (hasRequest('add')) {
    // some logic here
} else {
    // some logic here
}

  • For “if” statements that include “elseif”, the “elseif” is written as one word. Otherwise it's a standard “else” statement that should use a curly brace after the “else”. See previous example for that one.

// wrong
if (hasRequest('add')) {
    // some logic here
}
else if (hasRequest('update')) {
    // some logic here
}
 
// correct
if (hasRequest('add')) {
    // some logic here
}
elseif (hasRequest('update')) {
    // some logic here
}

  • Inline assignments are allowed, but are not recommended inside of the control structures. Avoid if possible.

Example:

if (($messages = getMessages($result, $msg_title)) !== null) {
    $output['messages'] = $messages->toString();
}

  • If the conditional statement causes the line length to exceed the maximum line length, conditions are broken into multiple lines using two tabs.

Examples:

if ($long_expression1 == $very_long_expression2
        || $long_expression2 == $very_long_expression2) {
    // some logic here
}
 
if ($long_expression1 == $very_long_expression2
        && $long_expression2 == $very_long_expression2
        && $long_expression3 == $very_long_expression3) {
    // some logic here
}

  • First conditional statement is indented by two tabs, but inner statements are indented by one tab.

Example:

if (($expression1 == $expression2 && $expression3 == $expression4)
        || ($expression5 == $expression6
            && $expression7 == $expression8)
        || $expression9 == $expression10) {
    // some logic here
}

Ternary operator
  • Conditional statement should be wrapped inside parenthesis.

Example:

// correct
$name = ($expression1 == $expression2) ? 'a' : 'b';
 
// wrong
$name = $expression1 == $expression2 ? 'a' : 'b';

  • If the conditional statement uses a function, parenthesis is not required.

Example:

// correct
$name = array_key_exists('name_expanded', $items) ? $items['name_expanded'] : $items['name'];
 
// wrong
$name = (array_key_exists('name_expanded', $items)) ? $items['name_expanded'] : $items['name'];

  • If conditional statement causes the line length to exceed the maximum line length, statements are broken into separate lines.

$name = ($long_expression1 == $very_long_expression1 || $long_expression2 == $very_long_expression2)
    ? 'a'
    : 'b';

Switch
  • “break;” can be ommited only for default case.

Small switch example:

switch ($type) {
    case 1:
        // line 1
        break;
    case 2:
        // line 1
        break;
    default:
        // line 1
}

Big switch example:

switch ($type) {
    case 1:
        // line 1
        // line 2
        // line 3
        break;
 
    case 2:
        // line 1
        // line 2
        // line 3
        break;
 
    default:
        // line 1
        // line 2
        // line 3
}

  • If we have a case label followed by code that falls through to another case label, comment should be present, confirming that it's intentional.

Example:

switch ($type) {
    case 1:
        // line 1
        // break; is not missing here
    case 2:
    case 3:
        // line 1
        break;
    default:
        // line 1
        // line 2
}

  • Curly braces are not used for “case” blocks.
  • “break;” should use one indentation.

Example:

// correct
switch ($type) {
    case 1:
        // line 1
        break;
    case 2:
        // line 1
        break;
    default:
        // line 1
}
 
// wrong
switch ($type) {
    case 1:
        // line 1
    break;
    case 2: {
        // line 1
    }
    break;
    default:
        // line 1
}

Try/Catch

Example:

try {
    DB::insert('auditlog', [$values]);
    return true;
}
catch (DBException $e) {
    return false;
}

Comparison

  • Strict comparison is preferred to non-strict.
  • Strict comparison must always be used for comparing strings.

Example:

// correct
if ($string1 === $string2) {
    // some logic here
}
 
// wrong
if ($string1 == $string2) {
    // some logic here
}

  • The “Equal” or “Not equal” operators must be used to compare numeric values to zero.

Example:

// correct
if ($itemid != 0) {}
 
// wrong
if ($itemid) {}

  • To check an expression against null, the identical operator should be used instead of the 'is_null()' function.

Example:

// correct
if ($var === null) {
    // some logic here
}
 
// wrong
if (is_null($var)) {
    // some logic here
}

  • For database ID comparison and large integers, bclib functions should be used.

Example:

// correct
if (bccomp($data['proxyid'], $host['proxy_hostid']) == 0) {
    // some logic here
}
 
// wrong
if ($data['proxyid'] == $host['proxy_hostid']) {
    // some logic here
}

  • Function empty() should not be used.
  • zbx_empty() usage is also discouraged.

Examples:

if ($i == 0) {
    // some logic here
}
 
if ($string === '') {
    // some logic here
}
 
$array = [];
if ($array) {
    // some logic here
}

  • When comparing a data that is expected to be integer to 0, it should be compared against (integer) 0.

Examples:

// correct
if (getRequest('filter_groupid') == 0) {
 
// wrong
if (getRequest('filter_groupid') === '0') {

Loops

For

Example:

// single variable
for ($i = 0; $i < $weeks; $i++) {
    $period_start = $start + SEC_PER_WEEK * $i;
}
 
// multiple variables
for ($x = $x1, $y = $y1; $x <= $x2; $x++, $y = $y1 + ($x - $x1) * $dy / $dx) {
    // some logic here
}

  • Calling count() in a for loop in PHP is slower than assigning the count to a variable and using that variable in the for loop instead.

// correct
$cnt = count($array);
for ($i = 0; $i < $cnt; $i++) {
    // some logic here
}
 
// wrong
for ($i = 0; $i < count($array); $i++) {
    // some logic here
}

Foreach
  • Key variable in foreach loop must be called “$key” and used only if key is used in statements. In event when key represents real object ID, it can be called more intellectually like “$objectid”.

Example:

$items = [0 => 'a', 1 => 'b', 2 => 'c'];
foreach ($items as $key => $item) {
    // some logic here
}
 
$items = [10001 => 'a', 10002 => 'b', 10003 => 'c'];
foreach ($items as $itemid => $item) {
    // some logic here
}

  • If array elements are modified within the loop, precede the value with & instead of array index. In the end of loop, the variable should be unset.

Example:

$arr = [1, 2, 3, 4, 'b' => 5, 'a' => 6];
// correct
foreach ($arr as &$value) {
    $value = (int) $value * 2;
}
unset($value);
 
// wrong
foreach ($arr as $i => $value) {
    $arr[$i] = (int) $value * 2;
}

  • Array elements are unset by index.

Example:

foreach ($eventids as $i => $eventid) {
    if ($eventid == 10150) {
        unset($eventids[$i]);
    }
}

While

Example:

while ($item = DBfetch($db_items)) {
    // some logic here
}

Do/While

Example:

do {
    echo $i--;
} while ($i > 0);

Formatting

Strings
  • Single quotes should be used to define strings.
  • Variable substitution is not permitted.

Example:

// correct
$a = 'Example String';
 
// wrong
$a = "Example $string";

  • If a concatenation with . (a dot) spans multiple lines, the dot is at the end of the previous line (not in the beginning of the next one).
  • Space (if required) is added at the beginning of the second row.

Example:

// correct
$sql = 'SELECT i.itemid'.
    ' FROM items i';
 
// wrong
$sql = 'SELECT i.itemid '
    .'FROM items i';

  • Translatable strings are not split into multiple lines.

Example:

// correct
$error = _s('Graph "%1$s" already exists on "%2$s" (inherited from another template).');
 
// wrong
$error = _s('Graph "%1$s" already exists on "%2$s"'.
    ' (inherited from another template).'
);

  • “%s” should not be used even if there is one parameter.

Example:

// correct
$error = _s('Invalid key "%1$s".', $item['_key']);
 
// wrong
$error = _s('Invalid key "%s".', $item['_key']);

Translatable strings

Acknowledgement: parts of instructions have been borrowed from http://codex.wordpress.org/Translating_WordPress

In order to make a string translatable in Zabbix you have to just wrap the original string in a function call:

$hello = _('Hello, dear user!');

Originally strings that should be translated were kept in /frontends/php/include/locale directory, but new strings should be put directly in the sources marked for translation.

To make a string available for translation in JavaScript, it must be added to the $tranStrings array in /frontends/php/jsLoader.php.

Placeholders

To put variables inside of the string, you should use the Zabbix frontend _s() function:

_s("Hosts deleted: %1$s.", $hosts_deleted);

If more than one variable is inserted, variable placeholders must have order number. Example:

_s('Item [%1$s] deleted from template [%2$s].', $item, $template);

Plurals

The output from the previous example would be: “Hosts deleted: 8.”. To make this string more human-readable, you can use the Zabbix frontend _n() function:

_n("Deleted %1$s host.", "Deleted %1$s hosts.", $hosts_deleted);

"Deleted 1 host."
"Deleted 8 hosts."

Even if the original text might not require any count for singular form, the number placeholder still must be used. For example, “Last %1$s issue” instead of just “Last issue”. Omitting the number from the English version requires the translators to know that it must be added, and thus is error prone.

For information on plural strings in the po file, consult http://www.gnu.org/software/hello/manual/gettext/Plural-forms.html and http://translate.sourceforge.net/wiki/l10n/pluralforms

Disambiguation by context

Sometimes one term is used in several contexts and although it is one and the same word in English it has to be translated differently in other languages. For example the word Post can be used both as a verb (Click here to post your comment) and as a noun (Edit this post). In such cases the _x() function should be used. It is similar to _s(), but it has an additional second argument - the context:

$column = _x('Change', 'noun in latest data');
...
// some other place in the code
$button = _x('Change', 'action button');

By using this method in both cases we will get the “Change” string for the original version, but the translators will see two “Change” strings for translation, each in the different contexts.

_xn() function is similar to _n() function except that it also supports context as the fourth parameter.

Both functions support unlimited number of placeholders. Examples:

_x('Message for arg1 "%1$s" and arg2 "%2$s"', 'context', 'arg1Value', 'arg2Value');
//returns: 'Message for arg1 "arg1Value" and arg2 "arg2Value"'
 
_xn('%1$s message for arg1 "%2$s"', '%1$s messages for arg1 "%2$s"', 3, 'context', 'arg1Value');
//returns: '3 messagges for arg1 "arg1Value"'

Recommendations

It's recommended to avoid using the context and gettext comments together for a particular string. But if the short context is not sufficiently descriptive, then the gettext comment is allowed. The reason is that Pootle, Virtaal tools have a minor disadvantage in using gettext comments (// GETTEXT:) together with the context. The gettext comment will replace information about source code lines where this string is used.

A very short context (in one word) is not recommended, it's less clear and less noticeable in the translation tools. The recommended length is 2-3 words. It is not recommended to use any punctuation for context string.

For example the code could be (a real approved usage):

$pagespan = new CSpan('< '._x('Previous', 'page navigation'), 'darklink');
DOBJECT_STATUS_UP => _x('Up', 'discovery status'),
new CCol(_x('Up', 'discovery results in dashboard')),
$maintenanceStatus = new CSpan(_x('Active', 'maintenance status'), 'green');
['pow' => 3, 'short' => _x('G', 'Giga short'), 'long' => _('Giga')],

Date and time Rather than using the built-in PHP locale switching features, which are not configured for very many languages on most hosts, Zabbix uses its internal translation module and gettext functions to accomplish date and time translations and formatting.

These are PHP date() formatting strings and they allow you to change the formatting of the date and time for your locale.

Zabbix uses the translations elsewhere in the localization file for full and short textual representations of month names and weekday names. Format characters which will give translated representations are: 'D l F M'. Description of returned values are the same as for [http://php.net/manual/en/function.date.php PHP date() function].

This special string is to select which elements to include in the date & time, as well as the order in which they're presented.

Comments for translators

If a translatable string might benefit from clarification, a comment for translators should be added. Such comments should be prefixed with 'GETTEXT:', for example:

// GETTEXT: r is date format string as described in http://php.net/date

This comment will be applied to the first found string in the source code after this comment.

Arrays

  • For function arrays definitions, there is no trailing comma after the last element both for definitions on single and multiple lines.
  • If an array element definition spans multiple lines, closing parenthesis should be on a separate line. Most parameters can be written in first line.

Example:

// correct
$data = [
    'username' => $username,
    'password' => $password,
    'email' => $email
];
 
// wrong
$data = [
    'username' => $username,
    'password' => $password,
    'email' => $email,
];
 
// correct
$trigger_options = [
    'output' => ['triggerid', 'expression', 'description', 'status',
        'value', 'priority'
    ],
    'monitored' => true,
    'preservekeys' => true
];
 
// wrong
$trigger_options = [
    'output' => ['triggerid', 'expression', 'description', 'status',
        'value', 'priority'],
    'monitored' => true,
    'preservekeys' => true];

Array merging

  • array_merge() usage is permitted but discouraged due to slow performance.
  • Arrays can be merged using “+”.

Example:

$down_to_up_triggerids[$triggerid] += $down_to_up_triggerids[$up_triggerid];

Built-in array functions

  • For less than 3 array values it is recommended to use “if” statements instead of in_array().

Example:

// correct
if ($status == HOST_STATUS_MONITORED || $status == HOST_STATUS_TEMPLATE) {
    // some logic here
}
 
// wrong
if (in_array($status, [HOST_STATUS_MONITORED, HOST_STATUS_TEMPLATE])) {
    // some logic here
}

  • It is not recommended to use array_push().

Example:

// correct
foreach ($hosts as $host) {
     $hostids[] = $host['hostid'];
}
 
// wrong
foreach ($hosts as $host) {
     array_push($hostids, $host['hostid']);
}

  • For better performance it is recommended to use array_keys(array_flip()) instead of array_unique().
  • It is also good to use filling an array with keys using “foreach” loop. Filling such array requires only boolean value.

Example:

array_keys(array_flip($hostids));
 
foreach ($hosts as $host) {
    $hostids[$host['hostid']] = true;
}

  • When creating list of unique IDs, boolean should be stored there instead of value. It will make code more readable, with little to no impact on performance.

Example:

// correct
foreach($hosts as $host) {
    $arr[$host['hostid']] = true;
}
$hostids = array_keys($arr);
 
// wrong
foreach($hosts as $host) {
    $arr[$host['hostid']] = $host['hostid'];
}
$hostids = $arr;

  • For array keys, instead of isset(), array_key_exists() should be used.

Example:

// correct
if (array_key_exists('name', $items)) {
    // some logic here
}
 
// wrong
if (isset($items['name'])) {
    // some logic here
}

Array dereferencing

  • Array dereferencing is permitted in case function returns only one indexed array element and there are no variables used. This is only for sake of readability.

Example:

// correct
$parsed_macro = (new CUserMacroParser($macro))->getMacros()[0];
 
// wrong
ZBase::getHostInventoryModes()[$operation['opinventory']['inventory_mode']];

Functions

Function declaration

  • For function parameter definitions there is no trailing comma after the last element both for definitions on single and multiple lines.
  • If a function parameter definition spans multiple lines, closing parenthesis should be similar to “if” statement.

Example:

// correct
public function __construct(array $options, CImportReferencer $referencer,
        CImportedObjectContainer $imported_object_container, CTriggerExpression $trigger_expression) {
    // some logic here
}
 
// wrong
public function __construct(array $options, CImportReferencer $referencer,
        CImportedObjectContainer $imported_object_container, CTriggerExpression $trigger_expression
    ) {
    // some logic here
}

  • Default values in function parameters can be used, but is discouraged.

Example:

// good
public function __construct($name, $value, $action, array $items) {
    // some logic here
}
 
// bad
public function __construct($name = 'combobox', $value = null, $action = null, array $items = []) {
    // some logic here
}

  • If function requires array or object as a parameter, that parameter should be type hinted.

Examples:

function makeTriggersPopup(array $triggers, array $actions, array $config) {
    // some logic here
}
 
public function import(CImportDataAdapter $adapter) {
    // some logic here
}

Function usage

  • If preprocessing is not required for function parameters, it is passed directly to function without creation of separate variable.

Examples:

DBselect('SELECT i.itemid FROM items');
 
API::Item->get([
    'itemids' => [123],
    'editable' => true,
    'preservekeys' => true
]);

  • Parameters, that do not fit into a single line should be written on a separate line:

Example:

// correct
monitor_stuff($hostid, $itemid,
    'A long informative message that would not fit if written together with the other parameters'
);
 
// correct
monitor_stuff($hostid, $itemid, [
    'parameter' => 'value',
    'anotherParameter' => 'value'
]);
 
// wrong
monitor_stuff($hostid, $itemid, 'A long informative message that would not fit if written together with the other parameters');

  • The closing parenthesis of a function call must always be at the same indentation level as the opening.

Example:

// correct
info(_s(
    'A pretty long message that contains the following parameters: "%1$s", "%2$s", "%3$s"',
    'parameter one',
    'parameter two',
    'parameter three'
));
 
// wrong
info(_s(
    'A pretty long message that contains the following parameters: "%1$s", "%2$s", "%3$s"',
    'parameter one',
    'parameter two',
    'parameter three'));

  • Functions shoud not access superglobals even using hasRequest() and getRequest() functions, but accept them as parameters.

Example:

// correct
function processItems($items) {
    // some logic here
}
 
// wrong
function processItems() {
    $items = getRequest('items');
}

Return values

  • If the function receives a parameter and modifies it, it should be returned, not passed by reference. This should make the code easier to understand and prevent situations, when a function modifies the array unexpectedly.

Examples:

// correct
function modifyValues(array $values) {
    // some logic here
 
    // return the modified values
    return $values;
}
 
// wrong
function modifyValues(array &$values) {
    // some logic here
}

  • Functions should return values early.

Example:

// good
function foo($a, $b) {
    if ($a == 0) {
        return false;
    }
 
    // more logic here
 
    return true;
}
 
// bad
function foo($a, $b) {
    if ($a == 0) {
        $result = false;
    }
 
    // more logic here
 
    return $result;
}

  • Returning boolean values must be in parenthesis.

Example:

// correct
function foo($a, $b) {
    return ($a == $b);
}
 
// wrong
function foo($a, $b) {
    return $a == $b;
}

Classes
  • Only one class is permitted in each PHP file.
  • Variables and methods inside classes must always declare their visibility by using one of the private, protected, or public modifiers. The var construct is not permitted.
  • Since Zabbix 3.0 method chaining is used.

Example:

// single method
$passwd_field = (new CPassBox('passwd'))->setWidth(ZBX_TEXTAREA_SMALL_WIDTH);
 
// multiple methods
$mediatype_form = (new CForm())
    ->setId('mediaTypeForm')
    ->addVar('form', 1);
 
// multiple methods with submethods
$mediatype_formlist
    ->addRow(_('Type'), $row)
    ->addRow(_('SMTP server'),
        (new CTextBox('smtp_server', $data['smtp_server']))->setWidth(ZBX_TEXTAREA_STANDARD_WIDTH)
    )
    ->addRow(_('SMTP email'), (new CTextBox('smtp_email', $data['smtp_email']))->setWidth(ZBX_TEXTAREA_STANDARD_WIDTH))
    ->addRow(_('Connection security'),
        (new CRadioButtonList('smtp_security', (int) $data['smtp_security']))
            ->addValue(_('None'), SMTP_CONNECTION_SECURITY_NONE)
            ->setModern(true)
    )
    ->addRow(_('SSL verify peer'), (new CCheckBox('smtp_verify_peer'))->setChecked($data['smtp_verify_peer']));
 
// when parameters do not fit into one line, the closing parenthesis for the object is on a new line
$delay_input = (new CNumericBox('delay_flex['.$i.'][delay]', $delay_flex['delay'], 5, $discovered_item, true,
    false
))->setAttribute('placeholder', 50);

  • When class uses other known several classes and all of them are used, they can be passed in class constructor.

Example:

public function __construct($current_version, CConverterChain $converter_chain) {
    $this->current_version = $current_version;
    $this->converter_chain = $converter_chain;
}

  • When class uses other multiple classes, but not all of them are used, they should be initialized after validation.

Example:

// depending on XML version, corresponding class is called
public function __construct() {
    $this->version_validators = [
        '1.0' => 'C10XmlValidator',
        '2.0' => 'C20XmlValidator',
        '3.0' => 'C30XmlValidator'
    ];
}
 
$data['zabbix_export'] = (new $this->version_validators[$version]())
    ->validate($data['zabbix_export'], '/zabbix_export');
 
// wrong
public function __construct() {
    $this->version_validators = [
        '1.0' => new C10XmlValidator(),
        '2.0' => new C20XmlValidator(),
        '3.0' => new C30XmlValidator()
    ];
}

Regular expressions
  • Named subpatterns must be defined using the old “?P<name>pattern” syntax to be compatible with PCRE libraries older than 7.0.
Blank spaces
  • One blank space should appear after commas in argument lists.

Example:

// correct
$a = [1, 2, 3];
 
// wrong
$a = [1,2,3];
 
// correct
function updateHostStatus($hostids, $status) {
    // some logic here
}
 
// wrong
function updateHostStatus($hostids,$status) {
    // some logic here
}

  • No blank spaces in conditional statement after opening and before closing parenthesis.

Example:

// correct
if ($this->parseConstant()) {
    $constant = end($this->constants);
}
 
// wrong
if ( $this->parseConstant() ) {
    $constant = end($this->constants);
}

  • All binary operators except . (a dot) should be separated from their operands by spaces. Blank spaces should never separate unary operators such as unary minus, increment (“++”), and decrement (“–”) from their operands.
  • A keyword followed by a parenthesis should be separated by a space.

Examples:

// correct
if (hasRequest('action') && getRequest('action') === 'add') {
    // some logic here
}
 
// wrong
if (hasRequest('action')&&getRequest('action') === 'add') {
    // some logic here
}
 
// correct
for ($i = 0; $i < 10; $i++) {
    // some logic here
}
 
// wrong
for ($i=0; $i<10; $i++) {
    // some logic here
}
 
// correct
function updateHostStatus($hostids, $status) {
    // some logic here
}
 
// wrong
function updateHostStatus($hostids, $status){
    // some logic here
}
 
// correct
function updateHostStatus($hostids, $status) {
    // some logic here
}
 
// wrong
function updateHostStatus ($hostids, $status) {
    // some logic here
}

Blank lines
  • Blank line should appear before block comments.
  • One blank line after class declaration.
  • One blank line after method definition in class.
  • Two blank lines after licence comment.
  • Blank line should be at the end of file.
Comments
  • License comments should not be minified ( comment with /*! ).
  • Double slashes are used in simple comments.

Example:

// simple comment

  • Full sentence in a comment starts with a capital letter and ends with a dot.

Example:

// This is a full sentence comment.

  • If a comment pertains to the whole block between { and }, it should be written on a separate line at the top of the block followed by a blank line.

Example:

if (!$this->error) {
    // Initializing local variables.
 
    $this->is_valid = true;
    $this->pos = 0;
}

  • Block comments can be used with the first letter being capital.

Example:

/*
 * New block comment.
 */

  • Full words should be used in comments instead of shortened versions - for example, “parameter” instead of “param”.
  • Multiline comments should use “block comment” syntax and should not exceed maximum line length.

Example:

/*
 * After {$ there was something else. This is not a valid macro. Reset and look for macros
 * in the rest of the string.
 */

PHPDoc
  • All functions, methods and properties should have a PHPDoc comment with a brief description of it's purpose and interface. Tag descriptions should be aligned using spaces and tags belonging to one group should be separated by a single line to improve readability. If function doesn't return value @return is not needed. For more than two types it's better to combine types using the pipe char instead of mixed. It is better to write @throws after parameters. Keep in mind that it is not @throw, but @throws.

Example :

<?php
/**
 * Performs a cool magic trick.
 * 
 * @param FairyDust $fairy_dust    The FairyDust object to use in the trick.
 * @param array     $magic_stones  An array of magic stones.
 * @param int       $stones_limit  Limit of magic stones.
 *
 * @throws Exception if something goes wrong.
 *
 * @return int|bool
 */
function performMagic(FairyDust $fairy_dust, array $magic_stones, $stones_limit) {
    // some logic here
}

  • Addition parameters '@static' and '@see' can be used. '@see' and '@static' should be written after function description and before parameters.

Example:

/**
 * Performs a cool magic trick.
 *
 * @see MyClass::__constructor()
 *
 * @static
 *
 * @param FairyDust $fairy_dust    The FairyDust object to use in the trick.
 * @param array     $magic_stones  An array of magic stones.
 * @param int       $stones_limit  Limit of magic stones.
 *
 * @throws Exception if something goes wrong.
 */
public static function performMagic(FairyDust $fairy_dust, array $magic_stones, $stones_limit) {
    // some logic here
}

  • Class properties should have a PHPDoc comment with @var parameter and property type.

Example:

/**
 * IP address range with maximum amount of IP addresses.
 *
 * @var string
 */
private $max_ip_range;

View helpers
  • Use specific funtions to set HTML attributes.

Example:

// correct
$cancel_button->setEnabled(false);
 
// wrong
$cancel_button->setAttribute('disabled', 'disabled');

File includes

For file include require_once is used. require_once raises a fatal error if the file is not found. All of the include paths should be relative to the directory of the file, that includes the script.

Example:

// correct
require_once dirname(__FILE__).'/include/hosts.inc.php';
 
// wrong
require_once 'include/hosts.inc.php';

Using PHP in HTML or JavaScript
  • When using PHP in HTML or JavaScript code the alternative control structure syntax must be used.
  • PHP short tags are allowed since Zabbix 3.0.
  • Do not build Javascript with PHP, so it's recommented to assign PHP values into Javascript variables early.

Example:

<!-- correct -->
<ul>
    <?php if ($list): ?>
        <?php foreach ($list as $element): ?>
            <li><?= $li ?></li>
        <?php endforeach ?>
    <?php endif ?>
</ul>
 
<!-- wrong -->
<ul>
    <?php if ($list) { ?>
        <?php foreach ($list as $element) { ?>
            <li><?php echo $li ?></li>
        <?php } ?>
    <?php } ?>
</ul>
 
<!-- correct -->
var is_profile = <?= $this->data['is_profile'] ?>;
if (!is_profile) {
    fields_to_trim.push('#alias', '#name', '#surname');
}
 
<!-- not recommended -->
<?php if (!$this->data['is_profile']): ?>
    fields_to_trim.push('#alias', '#name', '#surname');
<?php endif ?>
 
 
<!-- not recommended -->
jQuery(document).ready(function() {
    <?php if ($data['visibility']): ?>
        var switcher = new CViewSwitcher('data_type', 'change', <?= zbx_jsvalue($data['visibility'], true) ?>);
    <?php endif ?>
}
 
<!-- wrong -->
jQuery(document).ready(function() {
    <?php if ($data['visibility'])) { ?>
        var switcher = new CViewSwitcher('data_type', 'change', <?= zbx_jsvalue($data['visibility'], true) ?>);
    <?php } ?>
}

HTML encoding and JavaScript escaping
  • All unsafe data in both HTML and JavaScript must be sanitized before outputting to avoid syntax errors and XSS attacks.
  • HTML tag and attribute contents should be escaped using the CHtml::encode() method.

Example:

// Note, that only the GET parameter should be encoded, not the whole string.
$span = new CSpan('Hello, '.CHtml::encode($_GET['name']));
echo $span;

Plain HTML example:

<span>Hello, <?= CHtml::encode($_GET['name']) ?></span>

  • Parameters inserted into JavaScript code should be encoded into JSON using the CJs::encodeJson() method. Translatable strings must also be encoded, because they can contain ampersands.

Example:

console.log(<?php echo CJs::encodeJson($_GET['name']) ?>);
console.log(<?php echo CJs::encodeJson(['name' => $_GET['name'], 'key' => $_GET['item']]) ?>);
console.log(<?php echo CJs::encodeJson(_('Triggers')) ?>);

  • If the value will be used in HTML code, it should additionally be HTML encoded.

Example:

jQuery('#name').html(<?= CJs::encodeJson(CHtml::encode($_GET['name'])) ?>);

  • When passing an array to JavaScript via the data-* attributes, it can be safely serialized with the CHtml::serialize() method.

Example:

<a href="#" id="item" data-params="<?= CHtml::serialize(['name' => $_GET['name'], 'key' => $_GET['item']]) ?>">My Item</a>
 
// The data() method will automatically handle JSON decoding and return an object.
jQuery('#item').data('params');

Using the API

  • API_OUTPUT_EXTEND must never be used when performing get requests since it will request unnecessary data and may result in poor performance. Pass an array of specific fields instead.
  • API get() method options should be sorted in the following order:
    • output (table primary key is mandatory)
    • IDs, additional table fields and flags
    • search or filter
    • sortorder
    • sortfield
    • limit
    • groupCount
    • countOutput or preservekeys

Example:

$triggers = API::Trigger()->get([
    'output' => ['triggerid', 'description'],
    'selectItems' => ['itemid', 'name'],
    'selectLastEvent' => ['acknowledged'],
    'hostids' => $hostids,
    'skipDependent' => true,
    'monitored' => true,
    'editable' => true,
    'filter' => ['state' => null],
    'sortfield' => ['priority'],
    'sortorder' => ZBX_SORT_UP,
    'limit' => 1,
    'preservekeys' => true
]);

Input field limits

All input fields in the frontend are limited according to what the receiving database field length limit is. API does input length validation the same way.

For example :

  • IP input fields are limited to 39 characters (to accommodate IPv6 addresses and user macros).
  • DNS input fields are limited to 64 characters.
  • Port input fields are limited to 64 characters (to accommodate user macros).

Handling incorrect user input

If user input is incorrect (text for a numeric value, too large number, incorrect symbols used etc), it is never trimmed, discarded or modified - all user input is preserved as-is.