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.
.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.<?php
tag MUST exist at the first line of the file.?>
tag MUST be omitted from files containing only PHP.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.
<?php
tag with declare statement declare(strict_types = 0);
. (starting from 5.0)use
import statements.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.
The following example illustrates a complete list of all blocks:
<?php declare(strict_types = 0);
/*
** 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
}
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
/frontends/php/include/defines.inc.php
./frontends/php/include/translateDefines.inc.php
./frontends/php/app/
directory.Example:
// controller
/frontends/php/app/controllers/CControllerExample.php
// view
/frontends/php/app/views/example.php
// partial
/frontends/php/app/partials/example.php
// JavaScript file used in the view
/frontends/php/app/views/js/example.js.php
// JavaScript file used in the partial
/frontends/php/app/partials/js/example.js.php
disableSIDValidation()
method for pages that we expect to be accessed by direct link.Each MVC view and partial files must contain the following PHPDoc comment below the copyright comment, leaving two empty lines above the comment and one empty line after.
For MVC view files:
For MVC partial files:
Example:
// correct
$hostid = 12345;
$item_applications = [];
$db_application_prototypes = [];
// wrong
$hostId = 12345;
$host_id = 12345;
$itemApplications = [];
$dbapplication_prototypes = [];
Example:
// global define
define('ZBX_DB_MYSQL', 'MYSQL');
// translatable define
define('DATE_FORMAT', _('Y-m-d'));
// constant in class
const STATE_NEW = 0;
Example:
// correct
function create() {
// some logic here
}
function getName() {
// some logic here
}
// wrong
function Create() {
// some logic here
}
function get_name() {
// some logic here
}
Example:
// correct
function exportHtmlSource() {
// some logic here
}
// wrong
function exportHTMLSource() {
// some logic here
}
Example:
class CWriter {
// some logic here
}
class CXmlWriter extends CWriter {
// some logic here
}
class CFastXmlWriter extends CXmlWriter {
// some logic here
}
Example:
Example:
Example:
Example:
[]
shorthand.// correct
$another_filter_set = ($filter['select'] !== '' || $filter['application'] !== '' || $filter['groupids']
|| $filter['hostids']
);
// wrong
$another_filter_set = ($filter['select'] !== '' || $filter['application'] !== '' || $filter['groupids']
|| $filter['hostids']);
// correct
$array = [];
foreach ($array as $key => $value) {
$array[$key] = convert($value);
}
foreach (array_keys($array) as $key) {
do_some_action($array[$key]);
}
// correct
$variable1 = 'value1';
$variable2 = 'value2';
foreach (['variable1', 'variable2'] as $variable) {
$$variable = convert($$variable);
}
// wrong
foreach ($array as $key => $value) {
$$key = convert($value);
}
foreach (array_keys($array) as $key) {
do_some_action($$key);
}
See php.net for a list of superglobal variables.
$_REQUEST
values SHOULD NOT be accessed directly. For newer MVC controllers (located in the /ui/app/controllers folder) dedicated methods hasInput()
, getInput()
, etc. SHOULD be used instead. For older controllers (located directly in the /ui folder) dedicated functions like hasRequest()
, getRequest()
SHOULD be used instead.Example:
// correct (for newer MVC controllers)
if ($this->hasInput('name')) {
echo $this->getInput('name');
}
// correct (for older controllers)
if (hasRequest('name')) {
echo getRequest('name');
}
// wrong
if (isset($_REQUEST['name'])) {
echo $_REQUEST['name'];
}
zbx_dbstr()
function, regardless of value origin.dbConditionString()
, dbConditionInt()
or dbConditionId()
functions SHOULD be used, depending on database field type. These functions will take care of correctly escaping values.Example:
DBselect('SELECT c.actionid FROM conditions c WHERE '.dbConditionString('c.value', $values));
DBselect('SELECT oh.operationid FROM opcommand_hst oh WHERE '.dbConditionId('oh.hostid', $hostids));
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));
All logic related to data visualization SHOULD be part of the 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 the controller and pass the 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
}
$data
instead of class variable $this->data
.Example:
// correct
if ($data['groups']) {
// some logic here
}
// wrong
if ($this->data['groups']) {
// some logic here
}
Example:
// correct
if (hasRequest('add')) {
// some logic here
}
// wrong
if (hasRequest('add'))
{
// some logic here
}
if
statements MUST always use curly braces {}
.Example:
// correct
if (hasRequest('add')) {
$action = 'add';
}
// wrong
if (hasRequest('add'))
$action = 'add';
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
}
if
statements that include elseif
, the elseif
is written as one word.// correct
if (hasRequest('add')) {
// some logic here
}
elseif (hasRequest('update')) {
// some logic here
}
// wrong
if (hasRequest('add')) {
// some logic here
}
else if (hasRequest('update')) {
// some logic here
}
Example:
if (($messages = getMessages($result, $msg_title)) !== null) {
$output['messages'] = $messages->toString();
}
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
}
Example:
if (($expression1 == $expression2 && $expression3 == $expression4)
|| ($expression5 == $expression6
&& $expression7 == $expression8)
|| $expression9 == $expression10) {
// some logic here
}
?
and :
characters.Example:
$name = $expression1 == $expression2 ? 'a' : 'b';
$name = array_key_exists('name_expanded', $items) ? $items['name_expanded'] : $items['name'];
?:
characters MUST be written together.Example:
// correct
$array = $input_array ?: $default;
// wrong
$int = $input_int ?: $default;
$string = $input_string ?: $default;
Example:
$name = ($long_expression1 == $very_long_expression1 || $long_expression2 == $very_long_expression2)
? 'a'
: 'b';
??
is prohibited.break;
can be omitted only for the default case.Smaller switch example:
Larger 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
}
Example:
switch ($type) {
case 1:
// line 1
// break; is not missing here
case 2:
case 3:
// line 1
break;
default:
// line 1
// line 2
}
case
blocks.break;
MUST 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
}
Example:
try {
DB::insert('auditlog', [$values]);
return true;
}
catch (DBException $e) {
// catch body
}
finally {
// finally body
}
Example:
// correct
if ($string1 === $string2) {
// some logic here
}
// wrong
if ($string1 == $string2) {
// some logic here
}
Example:
// correct - preg_match() method can return integer 0 or 1, or "false".
if (preg_match(...) === 0) {
// some logic here
}
// wrong - both variables contains integers. There is no need for strict comparison.
if ($length1 === $length2) {
// some logic here
}
Example:
'is_null()
' function.Example:
// correct
if ($var === null) {
// some logic here
}
// correct
$form = $this->hasForm() ? $this->getForm()->toString() : null;
// wrong
if (is_null($var)) {
// some logic here
}
// wrong
$form = $this->getForm()?->toString();
Example:
// correct
if (bccomp($data['proxyid'], $host['proxy_hostid']) == 0) {
// some logic here
}
// wrong
if ($data['proxyid'] == $host['proxy_hostid']) {
// some logic here
}
empty()
MUST NOT be used.Example:
if ($i == 0) {
// some logic here
}
if ($string === '') {
// some logic here
}
if ($array) {
// some logic here
}
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
}
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
}
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
}
&
instead of the array index. In the end of the 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;
}
foreach
cycles. PHP does not guarantee the same result in such scenarios.Example:
foreach ($eventids as $i => $eventid) {
if ($eventid == 10150) {
unset($eventids[$i]);
}
}
foreach ($eventids as $i => $eventid) {
$eventids = array_flip($eventids);
}
Example:
Example:
Example:
.
(a dot) spans multiple lines, the dot is at the end of the previous line (not in the beginning of the next one).Example:
// correct
$sql = 'SELECT i.itemid'.
' FROM items i';
// wrong
$sql = 'SELECT i.itemid '
.'FROM items i';
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
MUST 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']);
In order to make a string translatable in Zabbix you have to just wrap the original string in a function call:
New translatable strings are automatically parsed behind the scene and stored in /ui/locale
directory.
To use string translations in preprocessed JavaScript files (.js.php files) PHP short tags MUST be used, and the json_encode
will take care of correctly escaping the potential quotes in the actual translation.
Example:
To use string translations in global JavaScript files (.js files), the translations MUST be added to the $translate_strings
array in /ui/jsLoader.php
.
Placeholders
To put variables inside the string, you SHOULD use the Zabbix frontend _s()
function:
If more than one variable is inserted, variable placeholders MUST have order number.
Example:
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:
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.
Make sure both Singular and Plural strings have the same parameters used in them. Otherwise, translation file sync will be broken. https://support.zabbix.com/browse/ZBX-23554
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 the 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 that 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:
This comment will be applied to the first found string in the source code after this comment.
Example:
// correct
$data = [
'username' => $username,
'password' => $password,
'email' => $email
];
// wrong
$data = [
'username' => $username,
'password' => $password,
'email' => $email,
];
// correct
$data = [$username, $password, $email];
// wrong
$data = [
$username,
$password,
$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
+
operator due to better performance.Example:
// correct
$options += $default_options;
// wrong
$options = array_merge($default_options, $options);
Built-in array functions
array_push()
.Example:
// correct
foreach ($hosts as $host) {
$hostids[] = $host['hostid'];
}
// wrong
foreach ($hosts as $host) {
array_push($hostids, $host['hostid']);
}
array_keys(array_flip())
instead of array_unique()
.Example:
Example:
// correct
$hostids = [];
foreach ($hosts as $host) {
$hostids[$host['hostid']] = true;
}
if (array_key_exists($hostid, $hostids)) {
// some logic here
}
// wrong
$hostids = [];
foreach ($hosts as $host) {
$hostids[] = $host['hostid'];
}
if (in_array($hostid, $hostids)) {
// some logic here
}
array_key_exists()
MUST be used instead of isset()
.Example:
// correct
if (array_key_exists('name', $items)) {
// some logic here
}
// wrong
if (isset($items['name'])) {
// some logic here
}
list()
[]
MUST be used for list()
.Example:
$data = [
['id' => 1, 'name' => 'Tom'],
['id' => 2, 'name' => 'Fred']
];
// correct (single line)
['id' => $id, 'name' => $name] = $data[0];
// correct (multiple lines)
[
'id' => $id,
'name' => $name
] = $data[0];
// wrong
list('id' => $id, 'name' => $name) = $data[0];
// correct
foreach ($data as ['id' => $id, 'name' => $name]) {
// logic here with $id and $name
}
// wrong
foreach ($data as list('id' => $id, 'name' => $name)) {
// logic here with $id and $name
}
Three dot operator
...
operator.Example:
// wrong
function add($a, $b, $c) {
return ($a + $b + $c);
}
$operators = [2, 3];
$result = add(1, ...$operators);
Function declaration
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
}
Example:
// correct
public function __construct(string $name, string $value, string $action, array $items) {
// some logic here
}
// wrong
public function __construct(string $name = 'combobox', ?string $value, ?string $action, array $items = []) {
// some logic here
}
// correct
public function setEnabled(bool $enabled = true) {
// some logic here
}
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',
'another_parameter' => 'value'
]);
// wrong
monitor_stuff($hostid, $itemid, 'A long informative message that would not fit if written together with the other parameters');
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'));
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
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
}
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;
}
private
, protected
, or public
modifiers. The var
construct is not permitted.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);
Constructor
// correct
protected string $name;
protected string $value;
public function __construct(string $name, string $value = '') {
$this->name = $name;
$this->value = $value;
}
// wrong
public function __construct(protected string $name, protected string $value = '') {}
Method and function arguments
&
before an argument, there MUST NOT be a space after it.int
or string
, ID arguments SHOULD NOT have any type specified.Example:
Example:
public function functionName(int $arg1, int $arg2): string {
return 'foo';
}
public function anotherFunction(string $arg1, string $arg2,
int $arg3): string {
return 'foo';
}
Example:
abstract, final, and static
abstract
and final
declarations MUST precede the visibility declaration.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
}
}
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
}
Example:
// correct
if ($this->parseConstant()) {
$constant = end($this->constants);
}
// wrong
if ( $this->parseConstant() ) {
$constant = end($this->constants);
}
Example:
Example:
.
(a dot), comparison, assignment, bitwise, logical, string, and type operators MUST be preceded and followed by one 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(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
}
/*!
).Example:
Example:
{
and }
, it SHOULD be written on a separate line at the top of the block followed by a blank line.Example:
Example:
Example:
/*
* After {$ there was something else. This is not a valid macro. Reset and look for macros
* in the rest of the string.
*/
Example:
@return
is not needed.mixed
.@throws
after parameters.@deprecated
MUST NOT be used for new code.Example :
<?php declare(strict_types = 0);
/**
* 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
}
'@see
' can be used. It SHOULD be written after the description and before the parameters.Example:
<?php declare(strict_types = 0);
/**
* Performs a cool magic trick.
*
* @see MyClass::__constructor()
*
* @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
}
@var
parameter and property type.Example:
/**
* IP address range with maximum amount of IP addresses.
*
* @var string
*/
private $max_ip_range;
For file include require_once
is used. require_once
raises a fatal error if the file is not found. The path MUST be relative to the directory of the current file.
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';
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) {
document.addEventListener('load', () => {
fields_to_trim.push('#alias', '#name', '#surname');
});
}
// wrong
<?php if ($data['is_profile']): ?>
document.addEventListener('load', () => {
fields_to_trim.push('#alias', '#name', '#surname');
});
<?php endif ?>
CTag
class or its descendants like CDiv
, CSpan
, CLink
and others is RECOMMENDED, as the CTag
class will take care of correctly escaping the data.Example:
// correct
(new CDiv($data['unsafe_content']))->show();
// wrong
<div><?= $data['unsafe_content'] ?></div>
CHtml::encode()
method MUST be used. This approach is only allowed if there is no possibility to make use of CTag
class or its descendants like CDiv
, CSpan
, CLink
and others.Example:
// correct
<?= CHtml::encode($data['unsafe_content']) ?>
// wrong
<?= $data['unsafe_content'] ?>
// wrong
<div><?= CHtml::encode($data['unsafe_content']) ?></div>
CTag::setAttribute()
method MUST be used for each data string.Example:
// correct
(new CDiv())
->setAttribute('data-unsafe-string', $data['unsafe_string'])
->show();
// wrong
<div data-unsafe-string="<?= $data['unsafe_string'] ?>"></div>
// wrong
<div data-unsafe-string="<?= CHtml::encode($data['unsafe_string']) ?>"></div>
CTag::setAttribute()
method MUST be used for the encoded JSON string.Example:
// correct
(new CDiv())
->setAttribute('data-unsafe-array', json_encode($data['unsafe_array']))
->show();
Example:
// correct
(new CDiv())
->setAttribute('data-unsafe-string', $data['unsafe_string'])
->setAttribute('onclick', 'alert(this.dataset.unsafeString);')
->show();
// wrong
(new CDiv())
->setAttribute('onclick', 'alert("'.$data['unsafe_string'].'");')
->show();
// wrong
(new CDiv())
->setAttribute('onclick', 'alert("'.CHtml::encode($data['unsafe_string']).'");')
->show();
Example:
// correct
(new CScriptTag('
view.init('.json_encode([
'unsafe_string' => $data['unsafe_string',
'unsafe_array' => $data['unsafe_array']
]).');
'))
->setOnDocumentReady()
->show();
// correct
const view = new class {
init({unsafe_string, unsafe_array}) {
console.log(unsafe_string);
console.log(unsafe_array);
}
}
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.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
)selectItems
hostids
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
]);
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:
If the 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.
// correct
$groupids = API::HostGroup()->get([]);
API::Host()->get([
'groupids' => $groupids
]);
// wrong
API::Host()->get([
'groupids' => API::HostGroup()->get([])
]);
$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.$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 its objects, if it was not requested in output
.api.create
and api.update
methods should return ids in wrapper object.API_NOT_EMPTY
flag was not implemented for API_OBJECT
validation on purpose. If an 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');
DBstart()
and DBend()
are useful in controllers to revert changes from multiple API requests if later one fails. But there is no need to use these functions for just one API call, as an API call uses them itself.// correct
DBstart();
$result = API::HostGroup()->update([
'groupid' => $groupid,
'name' => $name
]);
if ($result) {
$result = API::HostGroup()->propagate([
'groups' => ['groupid' => $groupid],
'permissions' => true
]);
}
$result = DBend($result);
//wrong
DBstart();
$result = API::HostGroup()->create(['name' => $name]);
$result = DBend($result);