Coding style
Overview
Scope
This document describes style guidelines for developers working on or with Moodle code. It talks purely about the mechanics of code layout and the choices we have made for Moodle. The intent of this specification is to reduce cognitive friction when scanning code from different authors. It does so by enumerating a shared set of rules and expectations about how to format PHP code.
Unless otherwise specified, this Coding Style document will defer to PSR-12, and PSR-1 in that order.
Where a de-facto Moodle standard is encountered but is undocumented, an appropriate MDLSITE issue should be raised to have the standard either documented within this Coding style guide, or rejected and the PSR recommendations adopted instead.
A "de-facto Moodle standard" is any coding style which is commonly and typically used in Moodle.
Goals
Consistent coding style is important in any development project, and particularly when many developers are involved. A standard style helps to ensure that the code is easier to read and understand, which helps overall quality.
Abstract goals we strive for:
- simplicity
- readability
- tool friendliness, such as use of method signatures, constants, and patterns that support IDE tools and autocompletion of method, class, and constant names.
When considering the goals above, each situation requires an examination of the circumstances and balancing of various trade-offs.
Much of the existing Moodle code may not follow all of these guidelines - we continue to upgrade this code when we see it.
For details about using the Moodle API to get things done, see the coding guidelines.
Useful tools
Several tools are available to help you in write code that conforms to this guide:
- The Moodle Code checker (integrates with eclipse/phpstorm)
- The Moodle PHPdoc checker
It is worth using both tools to check the code you are writing as they both perform slightly different checks. If you can get your code to pass both then you are well on the way to making friends with those who will be reviewing your work.
File Formatting
PHP tags
Always use "long" php tags. However, to avoid whitespace problems, DO NOT include the closing tag at the very end of the file.
<?php
require('config.php');
Indentation
Use an indent of 4 spaces with no tab characters. Editors should be configured to treat tabs as spaces in order to prevent injection of new tab characters into the source code.
Do not indent the main script level:
<?php
require('config.php');
$a = required_param('a', PARAM_INT);
if ($a > 10) {
call_some_error($a);
} else {
do_something_with($a);
}
<?php
require('config.php');
$a = required_param('a', PARAM_INT);
if ($a > 10) {
call_some_error($a);
} else {
do_something_with($a);
}
SQL queries use special indentation, see SQL coding style.
Maximum Line Length
The key issue is readability.
Aim for 132 characters if it is convenient, it is not recommended to use more than 180 characters.
The exception are string files in the /lang
directory where lines $string['id'] = 'value';
should have the value defined as a single string of any length, wrapped by quotes (no concatenation operators, no heredoc and no newdoc syntax). This helps to parse and process these string files without including them as a PHP code.
Wrapping lines
Whenever wrapping lines, following rules should generally apply:
- Indent with 4 spaces by default.
- Indent the wrapped line with control statement conditions or a function/class declaration with 4 additional spaces to visually distinguish it from the following body of the control statement or the function/class.
See examples in the following sections.
Wrapping control structures
while ($fileinfolevel && $params['component'] === 'user'
&& $params['filearea'] === 'private') {
$fileinfolevel = $fileinfolevel->get_parent();
$params = $fileinfolevel->get_params();
}
Wrapping if-statement conditions
There is nothing special and the control structures rule would still apply.
if (($userenrol->timestart && $userenrol->timestart < $limit) ||
(!$userenrol->timestart && $userenrol->timecreated < $limit)) {
return false;
}
However, if you have a few conditions in one control structure, try setting some helper variables for evaluating the conditions to improve the readability.
$iscourseorcategoryitem = ($element['object']->is_course_item() || $element['object']->is_category_item());
$usesscaleorvalue = in_array($element['object']->gradetype, [GRADE_TYPE_SCALE, GRADE_TYPE_VALUE]);
if ($iscourseorcategoryitem && $usesscaleorvalue) {
// This makes the conditions easier to review and understand.
}
Compare it with the following.
// DO NOT DO THIS.
if (($element['object']->is_course_item() || $element['object']->is_category_item())
&& ($element['object']->gradetype == GRADE_TYPE_SCALE
|| $element['object']->gradetype == GRADE_TYPE_VALUE)) {
// Too long lines with complex conditions are discouraged even when they are indented properly.
}
Wrapping class declarations
class foo implements bar, baz, qux, quux, quuz, corge, grault,
garply, waldo, fred, plugh, xyzzy, thud {
// Class body indented with 4 spaces.
}
Alternatively you may want to provide each implemented interface on its own line if it helps readability:
class provider implements
// These lines are indented with 8 spaces to distinguish them visually from the class body.
\core_privacy\local\metadata\provider,
\core_privacy\local\request\subsystem\provider,
\core_privacy\local\request\core_userlist_provider {
// Class body indented with 4 spaces.
}
Wrapping of the function signatures
/**
* ...
*/
protected function component_class_callback_failed(\Throwable $e, string $component, string $interface,
string $methodname, array $params) {
global $CFG, $DB;
if ($this->observer) {
// ...
}
}
/**
* ...
*/
protected function component_class_callback_failed(
\Throwable $e,
string $component,
string $interface,
string $methodname,
array $params
) {
global $CFG, $DB;
if ($this->observer) {
// ...
}
}
Wrapping parameters of a function call
Normally, parameters will just fit on one line. If they eventually become too long to fit a single line or of it helps readability, indent with 4 spaces.
do_something($param1, $param2, null, null,
$param5, null, true);
do_something(
$param1,
$param2,
null,
null,
$param5,
null,
true
);
Wrapping arrays
There is nothing special and the general rules apply again. Indent the wrapped line with 4 spaces.
$plugininfo['preferences'][$plugin] = ['id' => $plugin, 'link' => $pref_url,
'string' => $modulenamestr];
In many cases, the following style with each item on its own line will make the code more readable.
$plugininfo['preferences'][$plugin] = [
'id' => $plugin,
'link' => $pref_url,
'string' => $modulenamestr,
];
The last item has a trailing comma left there which allows to extend the list of items later with a cleaner diff. For the same reason, it is better not to align the assignment operators.
Wrapping arrays passed as function parameter
This is just an example of combining some of the examples above.
$url = new moodle_url('/course/loginas.php', [
'id' => $course->id,
'sesskey' => sesskey(),
]);
Line Termination
- Every line must be terminated by a Unix line feed character (LF, decimal 10, hexadecimal 0x0A).
- Carriage returns (CR, decimal 13, hexadecimal 0x0D) must NOT be used alone or with LFs.
- There must be no whitespace characters (spaces or tabs) at the end of any line.
- There must be no extra blank lines at the end of a file; every file should end with a single LF character.
This is consistent with the conventions of PSR-12.
Whitespace
Similar to Section 2.3 of PSR-12, each line of code should not have trailing whitespace. This is also applicable for multiline string literals such as SQL statements.
Assignment operator
- One or more spaces are allowed before assignments.
- One and only one space is allowed after assignments.
$foo = true;
$foobar = false;
$bafoo = 'Hello world';
$foo = true;
$foobar = false;
$bafoo = 'Hello world';
When making decisions about the spaces to apply before assignments, please consider both surrounding and similar code.
Naming Conventions
Filenames
Filenames should:
- be whole english words
- be as short as possible
- use lowercase letters only
- end in
.php
,.html
,.js
,.css
or.xml
Classes
Class names should always be lower-case English words, separated by underscores:
class some_custom_class {
function class_method() {
echo 'foo';
}
}
Always use ()
when creating new instances even if constructor does not need any parameters.
$instance = new some_custom_class();
When you want a plain object of no particular class, for example when you are preparing some data to insert into the database with $DB->insert_record, you should use the PHP standard class stdClass
. For example:
$row = new stdClass();
$row->id = $id;
$row->field = 'something';
$DB->insert_record('table', $row);
Alternatively you can cast an array to an object:
$row = (object) [
'id' => $id,
'field' => 'something',
];
$DB->insert_record('table', $row);
Before Moodle 2.0, Moodle defined a class named object
extending stdClass
, and recommended instantiation using new object();
.
This has now been deprecated. Please use stdClass
or the array instantiation instead.
Functions and Methods
Function names should be simple English lowercase words, and start with the Frankenstyle prefix and plugin name to avoid conflicts between plugins. Words should be separated by underscores.
Verbosity is encouraged: function names should be as illustrative as is practical to enhance understanding.
The uses of type hints and return type declarations is required in PHP in all possible locations for all new code. There will be necessary exclusions, such as code extending existing non-compliant code and implementing things where it is not available. Progressive approach will be applied.
There is no space between the function name and the following (brackets). There is also no whitespace between the nullable character (question mark - ?) and params or return types or between the function closing brackets and the colon.
function report_participation_get_overviews(string $action, ?int userid): ?array {
// Actual function code goes here.
}
There is an exception for activity modules modules|activity modules]] that still use only plugin name as the prefix for legacy reasons.
function forum_set_display_mode($mode = 0) {
global $USER, $CFG;
// Actual function code goes here.
}