Zabbix Code Guidelines

Sidebar

coding:php

PHP coding guidelines

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

General

  • All code and comments MUST be written in English.
  • Global variables 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 sub-directory is renamed.
  • Zabbix API methods MUST be used instead of writing direct SQL queries to database.

Files

  • In PHP files one opening <?php tag MUST exist at the first line of the file.
  • The closing ?> tag MUST be omitted from files containing only PHP.
  • All PHP files MUST use only UTF-8 without BOM for PHP code.
  • All PHP files MUST use the Unix LF (linefeed) line ending only.
  • All PHP files MUST end with a non-blank line, terminated with a single LF.

Lines

  • Lines MUST NOT be longer than 120 characters; lines longer than that MUST be split into multiple subsequent lines of no more than 120 characters each with two exceptions:
    • Validation rules MUST always be written on one line.
    • Translation strings MUST always be written on one line.
  • There MUST NOT be trailing whitespace at the end of lines.
  • Blank lines MAY be added to improve readability and to indicate related blocks of code except where explicitly forbidden.
  • There MUST NOT be more than one statement per line.

Indenting

  • Code MUST use an indent of one tab for each indent level, and MUST NOT use spaces for indenting.

Declare, namespace and import statements

The header of a PHP file may consist of a number of different blocks. If present, each of the blocks below MUST be separated by a single blank line, and MUST NOT contain a blank line. Each block MUST be in the order listed below, although blocks that are not relevant may be omitted.

  • Opening <?php tag with optional declare statement declare(strict_types = 1);.
  • Copyright comment.
  • The namespace declaration of the file.
  • One or more use import statements.
  • The remainder of the code in the file.

When a file contains a mix of HTML and PHP, any of the above sections may still be used. If so, they MUST be present at the top of the file, even if the remainder of the code consists of a closing PHP tag and then a mixture of HTML and PHP.

  • Declare statement declare(strict_types = 1); SHOULD only be added for new files.

The following example illustrates a complete list of all blocks:

<?php declare(strict_types = 1);
/*
** Zabbix
** Copyright (C) 2001-2020 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/
 
 
namespace Vendor\Package;
 
use Vendor\Package\{ClassA as A, ClassB, ClassC as C};
use Vendor\Package\SomeNamespace\ClassD as D;
use Vendor\Package\AnotherNamespace\ClassE as E;
 
/**
 * CExampleClass is an example class.
 */
class CExampleClass {
 
    // some logic here
}

Keywords and types

  • All PHP reserved keywords and types MUST be in lower case.
  • Any new types and keywords added to future PHP versions MUST be in lower case.
  • Short form of type keywords MUST be used i.e. bool instead of boolean, int instead of integer etc.

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

  • Visibility MUST be declared on all methods.
  • Method names MUST NOT be prefixed with a single underscore to indicate protected or private visibility.
  • Function and method names MUST always start with a lowercase letter. When a function or method name consists of more than one word, the first letter of each new word MUST be capitalized (camelCase).
  • Method and function names MUST NOT be declared with space after the method name. There MUST NOT be a space after the opening parenthesis, and there MUST NOT be a space before the closing parenthesis.

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

  • The term “class” refers to all classes, interfaces, and traits.
  • 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 MUST 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 = [];

  • Since Zabbix 3.0, arrays MUST be declared using the [] shorthand. Older versions SHOULD use array().

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

  • When instantiating a new class, parentheses MUST always be present even when there are no arguments passed to the constructor.
  • Variables referencing objects SHOULD in some way associate to the class the variable is an object of.

$relation_map = new CRelationMap();

  • Variables with assignment operators SHOULD use parenthesis and closing parenthesis SHOULD be on a new line.

// correct
$filter_set = ($filter['groupids'] || $filter['hostids']);
 
$another_filter_set = ($filter['select'] !== '' || $filter['application'] !== '' || $filter['groupids']
    || $filter['hostids']
);
 
// wrong
$filter_set = $filter['groupids'] || $filter['hostids'];
 
$another_filter_set = ($filter['select'] !== '' || $filter['application'] !== '' || $filter['groupids']
    || $filter['hostids']);

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

  • The opening brace MUST be on the same line as the conditional statement.
  • The closing brace MUST be on the next line after the body.

Example:

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

if, elseif, else

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

Example:

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 operators

  • The conditional operator, also known simply as the ternary operator, MUST be preceded and followed by at least one space around both the ? and : characters.
  • 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'];

  • The lefthand operand MUST NOT be left out.

Example:

// wrong
$result = $input ?: $default;
 
// correct
$result1 = ($input !== null) ? $input : $default;
$result2 = ($input === 'string') ? $input : $default;
$result3 = ($input == ZBX_CONSTANT) ? $input : $default;
$result4 = array_key_exists('key', $input) ? $input['key'] : $default;

  • Null coalescing operator MUST NOT be used.

Example:

// wrong
$result = $input['key'] ?? $default;
 
// correct
$result = array_key_exists('key', $input) ? $input['key'] : $default;

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

Example:

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

switch, case

  • 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, finally

Example:

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

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.

Example:

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

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

Example:

// 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. Avoid using assignment by reference when working with large arrays.

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, do while

Example:

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

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

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.

Example:

_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')),
$maintenance_status = 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 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']];

list()

  • The shorthand [] MUST be used for list().

Example:

$data = [
    ["id" => 1, "name" => 'Tom'],
    ["id" => 2, "name" => 'Fred'],
];
 
// wrong
list("id" => $id1, "name" => $name1) = $data[0];
 
// correct
["id" => $id1, "name" => $name1] = $data[0];
 
// wrong
foreach ($data as list("id" => $id, "name" => $name)) {
    // logic here with $id and $name
}
 
// correct
foreach ($data as ["id" => $id, "name" => $name]) {
    // logic here with $id and $name
}

Three dot operator

  • Arrays and Traversable objects MUST NOT be unpacked into argument lists when calling functions by using the operator.

Example:

// wrong
function add($a, $b, $c) {
    return ($a + $b + $c);
}
 
$operators = [2, 3];
$result = add(1, ...$operators);

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(string $name, string $value, string $action, array $items) {
    // some logic here
}
 
// bad
public function __construct(string $name = 'combobox', ?string $value, ?string $action, array $items = []) {
    // some logic here
}

Function usage

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

Example:

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(array $items): void {
    // some logic here
}
 
// wrong
function processItems(): void {
    $items = getRequest('items');
 
    // more logic here
}

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.

Example:

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

  • Functions SHOULD return values early.

Example:

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

  • Returning boolean values MUST be in parenthesis.

Example:

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

Classes

  • The term “class” refers to all classes, interfaces, and traits.
  • 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(int $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()
    ];
}

Method and function arguments

  • In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma.
  • When using the reference operator & before an argument, there MUST NOT be a space after it.
  • Method and function arguments with default values MUST go at the end of the argument list.
  • ID type arguments SHOULD NOT use “string” type, if strict_types is used.
  • * In code ID is considered to be string, but could be stored as int by PHP at some stages. Mostly due to auto conversion, when ID is used as key in array.

Example:

public function functionName(int $arg1, int &$arg2, array $arg3 = []): void {
    // some logic here
}

  • When you have a return type declaration present, there MUST be one space after the colon followed by the type declaration. The colon and declaration MUST be on the same line as the argument list closing parenthesis with no spaces between the two characters.

Example:

public function functionName(int $arg1, int $arg2): string {
    return 'foo';
}
 
public function anotherFunction(string $arg1, string $arg2,
        int $arg3): string {
    return 'foo';
}

  • In nullable type declarations, there MUST NOT be a space between the question mark and the type.

Example:

// correct for Zabbix >=5.0
public function functionName(?string $arg1, ?int &$arg2, ?array $arg3): ?string {
    return 'foo';
}
 
// correct for Zabbix <=4.4
public function functionName($arg1 = null, &$arg2, array $arg3 = null) {
    return 'foo';
}

abstract, final, and static

  • When present, the abstract and final declarations MUST precede the visibility declaration.
  • When present, the static declaration MUST come after the visibility declaration.

Example:

abstract class ClassName {
    protected static $foo;
 
    abstract protected function methodName();
 
    final public static function anotherMethod() {
        // some logic here
    }
}

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(array $hostids, int $status) {
    // some logic here
}
 
// wrong
function updateHostStatus(array $hostids,int $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);
}

  • The increment/decrement operators MUST NOT have any space between the operator and operand.

Example:

$i++;
++$j;

  • Type casting operators MUST NOT have any space within the parentheses.

Example:

$int_value = (int) $input;

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(array $hostids, int $status) {
    // some logic here
}
 
// wrong
function updateHostStatus(array $hostids, int $status){
    // some logic here
}
 
// correct
function updateHostStatus(array $hostids, int $status) {
    // some logic here
}
 
// wrong
function updateHostStatus (array $hostids, int $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.

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 MUST 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 a 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.
  • Methods and functions marked as @deprecated MUST NOT be used for new code.

Example :

<?php declare(strict_types = 1);
 
/**
 * 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|null
 */
function performMagic(FairyDust $fairy_dust, array $magic_stones, int $stones_limit): ?int {
    // some logic here
}

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

Example:

<?php declare(strict_types = 1);
 
/**
 * 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, int $stones_limit): void {
    // 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 __DIR__.'/include/hosts.inc.php';
 
// wrong
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 request parameter should be encoded, not the whole string.
$span = new CSpan('Hello, '.CHtml::encode(getRequest('name')));
echo $span;

Plain HTML example:

<span>Hello, <?= CHtml::encode(getRequest('name')) ?></span>

  • Parameters inserted into JavaScript code SHOULD be encoded into JSON using json_encode() function. For Zabbix <=4.4 the CJs::encodeJson() method MUST BE used.
  • Translatable strings MUST also be encoded, because they can contain ampersands.

Example:

// correct for Zabbix >=5.0
console.log(<?= json_encode($data['name']) ?>);
console.log(<?= json_encode(['name' => $data['name'], 'key' => $data['item']]) ?>);
console.log(<?= json_encode(_('Triggers')) ?>);
 
// correct for Zabbix <=4.4
console.log(<?= CJs::encodeJson($data['name']) ?>);
console.log(<?= CJs::encodeJson(['name' => $data['name'], 'key' => $data['item']]) ?>);
console.log(<?= CJs::encodeJson(_('Triggers')) ?>);

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

Example:

jQuery('#name').html(<?= json_encode(CHtml::encode($data['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' => $data['name'], 'key' => $data['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 (only the fields that will be used in the following code MUST be specified) or countOutput
    • groupCount (is only used together with countOutput)
    • additional table fields, for example, selectItems
    • IDs, for example hostids
    • flags (skipDependent, monitored, editable etc.)
    • filter
    • search
    • sortfield
    • sortorder
    • limit
    • 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.

API development rules

  • $sql_parts['limit'] vs $options['limit'] : limit should be validated. If API method has API validation and limit is validated as part of it, no additional validation is required and $options['limit'] SHOULD be used. Otherwise we should use the following code sample.

  // limit
  if (zbx_ctype_digit($options['limit']) && $options['limit']) {
      $sql_parts['limit'] = $options['limit'];
  }
 

  • API_ALLOW_NULL flag SHOULD often be used in validation in api.get methods (make sure to check it's usage in similar fields in existing APIs). This flag SHOULD NOT be used in api.create and api.update methods, where validation should be more strict.
  • All $sql_parts SHOULD be initialized in beginning of api.get method (don't omit unused ones). limit SHOULD be omitted, if $options['limit'] will be used as in rule above.

  $sql_parts = [
      'select'    => ['dashboard' => 'd.dashboardid'],
      'from'      => ['dashboard' => 'dashboard d'],
      'where'     => [],
      'order'     => [],
      'group'     => [],
      'limit'     => null
  ];
 

  • api.get SHOULD NOT return primary key in it's objects, if it was not requested in output.
  • If possible, API validation rules SHOULD be formatted as single block without separation into variables or functions.
  • api.create and api.update methods should return ids in wrapper object.

  return ['dashboardids' => $dashboardids];
 

  • API_NOT_EMPTY flag was not implemented for API_OBJECT validation on purpose. If object has no required fields, it MAY be empty.
  • createRelationMap() and related methods SHOULDN'T be used. Instead fetch each row from select separately and put the data, where needed.

  // correct
  $groupPrototypes = DBFetchArrayAssoc(DBselect('...'), 'group_prototypeid');
  foreach ($result as $id => $host) {
      $result[$id]['groupPrototypes'] = [];
  }
  $query = DBselect(
    'SELECT ...'.
      ' WHERE '.dbConditionInt('group_prototypeid', array_keys($groupPrototypes))
  );
  while ($groupPrototype = DBFetch($query)) {
    $result[$groupPrototype['hostid']]['groupPrototypes'][] = $groupPrototype;
  }
 
  // wrong
  $groupPrototypes = DBFetchArray(DBselect('...'));
  $relationMap = $this->createRelationMap($groupPrototypes, 'hostid', 'group_prototypeid');
  $groupPrototypes = API::getApiService()->select('group_prototype', [
    'group_prototypeids' => $relationMap->getRelatedIds(),
    ...
  ]);
  $result = $relationMap->mapMany($result, $groupPrototypes, 'groupPrototypes');