Coding standards part 2: .editorconfig, documentation and strict types

{{< toc >}}

In the previous article, we already learned a lot about coding standards, and today I like to discuss three more points: the file .editorconfig, the documentation of your code, and the declaration of strict types.

The file .editorconfig

The .editorconfig file is a text file you create in the root folder of your plugin. This file ensures, that you and your team use the same standards for line breaks, indentations, and character sets throughout the project. Editors like Visual Studio Code can read this file and apply the formatting and settings automatically. Visual Studio Code requires this extension for that purpose.

The extension for Visual Studio code supports these settings:

  • indent_style: Indentation style: spaces or tabs
  • indent_size: Size of indentation when spaces are used (soft tabs)
  • tab_width: Size of indentation when tabs are used
  • end_of_line (applied when saving): Type of line breaks
  • insert_final_newline (applied when saving): Specifies whether an empty line should be added to the end of the file (see PSR-2)
  • trim_trailing_whitespace (applied when saving): Removes any whitespace after the actual code line

In the .editorconfig, you can set the styles for each programming language or file extension.

My file usually looks like this:

1# http://editorconfig.org
2 
3root = true
4 
5[*]
6charset = utf-8
7end_of_line = lf
8indent_size = 2
9indent_style = space
10trim_trailing_whitespace = true
11 
12[*.php]
13indent_size = 4
14insert_final_newline = true
15 
16[*.scss]
17insert_final_newline = true
18 
19[*.md]
20trim_trailing_whitespace = false

With root = true, it is specified that the file is located in the root directory of your plugin. Text editors like VS Code initially search for a .editorconfig file in the folder of the currently opened code file. If none is found, it searches one folder above, and so on. The line root = true means that the search can end here and the file can be used since root has been reached.

With the declarations under [*], the general settings are made, and below the settings for specific file extensions are overwritten. I want the final newline only in PHP and SCSS files, for example.

Even if you don’t work in a team, I recommend using a .editorconfig file. This ensures that you use the same styles in every project and on every device.

More infos: editorconfig.org.

Documentation

Hated by many developers, it is still an important tool for you and your team. I would like to emphasize here that documenting your code is also important when developing your plugin alone. I promise you will love your documentation when you have to go through your code again after two to three months! Code quickly becomes distant, and after just a few months, you won’t remember specific details, like why you implemented something in a particular way and not differently.

When it comes to documentation, we generally distinguish between two types of documentation:

  • Documentation in the code itself.
  • External Documentation.

Documentation inside your code

In PHP, we distinguish not only single-line documentation with a preceding double slash (//) and multi-line documentation in the following form:

1/* This is a comment,
2 that spans
3 over multiple lines
4*/

We also distinguish between documentation at the file, class, and function/method level. I recommend documenting in the PHPDoc format, as used, for example, by phpDocumentor. A PHPDoc block starts not with /*, but with two asterisk symbols: /**. Text editors like Visual Studio Code can quickly create these blocks when typing /**, which reduces typing work.

Here is an example of the various doc blocks.

1<?php
2 
3/**
4 * At the very top appears the file-level documentation. Here you can discuss
5 * what exactly is happening in this file. As with all documentation:
6 *
7 * Document as much as necessary and as little as possible!
8 *
9 * What this means, you can read further below.
10 *
11 * Here you can, for example, specify the author and the version with which
12 * this file was created:
13 *
14 * @author: Marcus Kober
15 * @since: 1.2.1
16 */
17 
18declare(strict_types=1);
19 
20namespace MK\Test\Main;
21 
22use MK\Test\Attributes\Filter;
23 
24/**
25 * DocBlock at the class level
26 *
27 * The DocBlock is created directly before the class to be documented and indicates
28 * what the class is for.
29 */
30class Frontend
31{
32 /**
33 * DocBlock at the variable level
34 *
35 * Here you can explain what this variable does
36 *
37 * @var array
38 */
39 protected array $addedClasses = [];
40 
41 /**
42 * DocBlock at the method level
43 *
44 * Here you can explain what the method does.
45 *
46 * The DocBlock comes before the declaration of attributes, if
47 * any are used.
48 *
49 * @param array $classes
50 * @return array
51 */
52 #[Filter('body_class')]
53 public function addClass(array $classes): array
54 {
55 // Single-line comment, very briefly, explaining something specific
56 $this->addedClasses[] = 'my-class';
57 
58 return array_merge($classes, $this->addedClasses);
59 }
60}

The statements starting with @, such as @author or @since, are called “tags” of the DocBlock syntax. Here you can find a list of possible tags. For variables and methods, you can additionally specify types with @var, or @param and @return.

Commenting your code with DocBlocks not only makes your code more transparent for you and your team. You can also use phpDocumentor to create external API documentation from sufficiently well-documented code. This can be added to your external documentation (if you need it) or even replace it.

###External Documentation

For very extensive plugins, you can create external documentation either publicly or just for yourself and/or your team. External documentation takes place outside of your code, for example, on a website, or you can use services designed for it, such as GitBook or Read the Docs.

External documentation is particularly important for documenting your own hooks. There is also an interesting project that automatically documents your hooks: the Pronamic WordPress Documentor. It automatically generates documentation in various formats from your hook declarations. You can find more about hooks in the upcoming article on Hook-Driven Development.

Declare strict types

Although the corresponding declaration is missing in most examples in the early articles of this series due to space constraints, I strongly recommend enabling PHP’s strict types check.

The declaration is made with declare(strict_types=1). This should be done in every file of your plugin, and it must be this way to ensure that you fundamentally activate strict type checking. The declare statement is placed immediately after the opening PHP tag at the beginning of the file and before the namespace declaration:

1<?php
2 
3declare(strict_types=1);
4 
5namespace MKMyPlugin;
6 
7// use statements
8 
9// ...

Refer to this as well: PSR-12: Declare Statements, Namespace, and Import Statements

What does declare(strict_types=1) do exactly?

The statement declare(strict_types=1) enables strict type-checking in PHP. This leads to improved code quality and stability, as using strict type-checking allows errors to be detected more quickly and seen during development in the text editor.

This checks the so-called scalar types, which are int, float, string, and bool.

Let’s take a look at the following function as an example:

1function add(float $a, float $b): float
2{
3 return $a + $b;
4}

Without enabling strict type checking, we can run the following without any issues and error messages:

1echo add('1.2', 2.4);
2// -> 3.6

PHP silently converts the string('1.2') to float(1.2) and then returns the calculated float(3.6).

Since this is a potential source of errors that can be difficult to find, we enable strict type checking in every PHP file with declare(strict_types=1).

Let’s see this in the example:

1<?php
2 
3declare(strict_types=1);
4 
5function add(float $a, float $b): float
6{
7 return $a + $b;
8}
9 
10echo add('1.2', 2.4);

This leads to the following error:

1Fatal error: Uncaught TypeError: Argument 2 passed to add() must be of the type float, string given

Of course, this also applies to the return value:

1<?php
2 
3declare(strict_types=1);
4 
5function add(float $a, float $b): float
6{
7 return (string) $a + $b;
8}
9 
10echo add(1.2, 2.4);

Here, we correctly pass two floats to the add() function. However, since return type hinting is enabled and we specify that the function should return a float, but at the same time cast the result of the addition as a string, we will encounter the following error message:

1Fatal error: Uncaught TypeError: Return value of add() must be of the type float, string returned

Important: In order to use type checking correctly, your functions and methods must, of course, use type hinting! Type hinting refers to the type declarations, as seen, for example, in the declaration of the function above. There, we explicitly tell $a and $b that they should be of type float, and we indicate to the function that the return value should also be of type float:

1function add(float $a, float $b): float

I strongly recommend equipping all PHP files of your plugin with declare(strict_type=1)!

Conclusion

In this article, you have learned about three ways to make your code cleaner and more maintainable. Two methods deal with the outer form of the code (.editorconfig and documentation), and one with the quality and security of the actual code (Strict Types).