Categories
Coding

Coding standards with PHP_CodeSniffer

Every programming language, framework, or library has (or should have) its own coding standards. The benefits of using a standard are well known in our industry.

Coding standards avoid common errors, improves the readability of the code, reduces the entry barrier to start working on a project, and the most important key:

The code looks like it had been written by a single person.

I’m not going to talk about which standard is better (Tabs or Spaces). As good developers, it’s our responsibility to adapt our code to the conventions and guidelines for each project we work on.

But coding standards are much more than formatting the code. It’s about how to structure the code, naming conventions, security, readability, inline documentation, and many other good practices.

WordPress is not an exception. As the most popular CMS on the Internet, it has its own coding standards too. In the codex, you can find detailed documentation about how to properly write PHP, JS, HTML, CSS, and of course, how to document your code.

As you might be thinking, you don’t have to memorize every rule of a standard. Probably, your IDE allows you to import a config file to format the code properly, and although this kind of code assistance it’s very helpful when writing code, it only takes care of the code style, like tabs and indents, spaces, blank lines, opening/closing braces, etc.

Fortunately, it exists a more advanced tool that provides better assistance and it will help us to improve the quality of our code. I’m talking about PHP_CodeSniffer.

What is PHP_CodeSniffer?

PHP_CodeSniffer, as its own documentation says:

It’s an essential development tool that ensures your code remains clean and consistent.

It contains two scripts, the main (phpcs) detects violations of a defined coding standard, and the second one (phpcbf), fixes these inconsistencies in your code automatically.

In this post, I’m going to focus on the main script, which will help us to detect errors in our code. But before that, just a short notice about the second script: Although the idea of automatically fixing the invalid code sounds good, depending on the error we’re trying to fix, the resulting code might not be as good as we expect. That’s why I recommend using this tool very carefully and only for errors very easy to fix.

Enough of theory, let’s start with the practice.

WordPress coding standards

In order to use the WordPress coding standards, we need to add the following dependencies to our composer.json file:

"require-dev": {
    "wp-coding-standards/wpcs": "2.2.1",
    "dealerdirect/phpcodesniffer-composer-installer": "0.6.2",
    "phpcompatibility/phpcompatibility-wp": "2.1.0"
}

Install them and after that, execute the command:

./vendor/bin/phpcs -i

The output should be something like this:

The installed coding standards are PEAR, Zend, PSR2, MySource, Squiz, PSR1, PSR12, PHPCompatibility, PHPCompatibilityParagonieRandomCompat, PHPCompatibilityParagonieSodiumCompat, PHPCompatibilityWP, WordPress, WordPress-Extra, WordPress-Docs and WordPress-Core

You can see a list of all the available rulesets. We are going to use the ruleset “WordPress“. For additional info about the different WordPress rulesets, check its documentation.

Now, it’s time to define our phpcs.xml config file:

<?xml version="1.0"?>
<ruleset name="WordPress Coding Standards">
   <description>PHP_CodeSniffer ruleset.</description>

   <!-- Exclude paths -->
   <exclude-pattern>*/node_modules/*</exclude-pattern>
   <exclude-pattern>*/vendor/*</exclude-pattern>

   <!-- Configs -->
   <config name="minimum_supported_wp_version" value="4.4" />
   <config name="testVersion" value="5.6-" />

   <!-- Rules -->
   <rule ref="WordPress" />

   <rule ref="WordPress.WP.I18n">
      <properties>
         <property name="text_domain" type="array" value="my-plugin-textdomain" />
      </properties>
   </rule>
</ruleset>

In this file, we have defined some interesting configurations:

  • The minimum supported WordPress and PHP versions.
  • The text-domain for the translatable strings.
  • Excluded the folders node_modules/ and vendors/ from the validation.

You can add more rules or disable some of them on specific files or directories. The config file is quite powerful and it allows you to customize the coding standards of your project. If you want to learn more about all its possibilities, take a look at its documentation.

It’s time to try our new tool. Let’s create a test file called demo.php with the following code:

$foo = ['bar'];

Quite simple isn’t it? Now execute the command:

./vendor/bin/phpcs -s -p ./demo.php

The output will be the following:

FOUND 3 ERRORS AFFECTING 1 LINE

1 | ERROR | [x] Missing space after array opener.
| | (WordPress.Arrays.ArrayDeclarationSpacing.NoSpaceAfterArrayOpener)
1 | ERROR | [x] Short array syntax is not allowed (Generic.Arrays.DisallowShortArraySyntax.Found)
1 | ERROR | [x] Missing space before array closer.
| | (WordPress.Arrays.ArrayDeclarationSpacing.NoSpaceBeforeArrayCloser)

The code validation will throw some errors, each one with its own description and the rule key which failed.

After fixing the errors, our code looks like this:

$foo = array( 'bar' );

Execute the previous command again and check that our code passes all the validations.

Let’s seen another example:

$action = $_GET['action'];

In a first look, we don’t see anything strange in the code, but as I mentioned before, coding standards are much more than formatting the code. Let’s execute our tool again:

FOUND 3 ERRORS AND 1 WARNING AFFECTING 1 LINE

 1 | WARNING | Processing form data without nonce verification. (WordPress.Security.NonceVerification.Recommended)
 1 | ERROR   | Detected usage of a possibly undefined superglobal array index: $_GET['action']. Use isset() or empty()
   |         | to check the index exists before using it
   |         | (WordPress.Security.ValidatedSanitizedInput.InputNotValidated)
 1 | ERROR   | $_GET data not unslashed before sanitization. Use wp_unslash() or similar
   |         | (WordPress.Security.ValidatedSanitizedInput.MissingUnslash)
 1 | ERROR   | Detected usage of a non-sanitized input variable: $_GET['action']
   |         | (WordPress.Security.ValidatedSanitizedInput.InputNotSanitized)

Wow, these are too many errors for a so simple piece of code. But what the tool is complaining of? Let’s see:

  • We have not checked the index 'action' exists before using it, which might throw a warning.
  • We have not sanitized the value of this argument, which might break our code if the value is not valid, or even worst, it opens the door to a possible MySQL Injection attack if this value is stored in the database.
  • We have not performed a validation of the GET request with a nonce field. So, the code might be executed by a non-authorized user.

Understood, these are good points to improve our code.

if ( wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['nonce'] ) ), 'my-nonce-action' ) ) {
	$action = ( isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : '' );
}

Much better, now our code is more secure and less error-prone.

These are just a few examples of how PHP_CodeSniffer works, but this tool has more to offer. In combination with the WordPress coding standards, it will detect:

  • PHP syntax you should no use if you want to keep compatibility with older versions.
  • Usage of not available functions or methods on older WordPress versions.
  • Usage of non-sanitized variables.
  • The output of content which hasn’t been escaped properly.
  • Unsafe redirects.
  • Processing posted data without nonce verification.
  • Naming convention issues.
  • and many more.

Adding exceptions

Sometimes, the coding standards may throw an error over a block of code you consider it’s ok or that you want to ignore. For example, the output of a non-sanitized variable. If you are sure this output is safe, you can add a phpcs:ignore comment to ignore this piece of code.

$foo = '<p><strong>WPCS:</strong> Valid output.</p>';

echo $foo; // phpcs:ignore WordPress.Security.EscapeOutput

In the next example, we didn’t add a nonce verification, let’s suppose we don’t need that validation so, how can we ignore this issue? Easy:

$action = ( isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification

You can also ignore multiple coding standard rules for a single line o code doing the following:

$action = ( ! empty( $_GET['action'] ) ? wp_unslash( $_GET['action'] ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

On the other hand, if you have a block of code where you need to ignore the same error in multiple lines, you can opt to disable the error over the whole block of code.

// phpcs:disable WordPress.Security.EscapeOutput
$foo = '<p><strong>WPCS:</strong> Valid output.</p>';

// ...

echo $foo;
// phpcs:enable WordPress.Security.EscapeOutput

Note: Don’t forget to enable the error validation again and be sure you only disable the errors you want to ignore and not all of them.

WooCommerce coding standards

If you are working on a WooCommerce extension, you should know that WooCommerce also has its own coding standards. To make use of them, install the dependency as follows:

composer require woocommerce/woocommerce-sniffs

The WooCommerce coding standards are an extended version of the WordPress coding standards. So, the dependencies mentioned before are no longer necessary in your composer.json file.

Now, update your phpcs.xml config file by replacing the ruleset “WordPress” by “Woocommerce-Core“.

<!-- Rules -->
<rule ref="WooCommerce-Core" />

After enabling the WooCommerce coding standards, quite commonly used functions like wc_clean, wc_format_decimal, etc. won’t throw an error during validation because these functions have been marked as safe.

// This line doesn't throw a non-sanitized error with the WC coding standards.
$action = ( isset( $_POST['action'] ) ? wc_clean( wp_unslash( $_POST['action'] ) ) : '' );

Conclusion

The usage of the WordPress and WooCommerce coding standards is one of the many good practices that as a developer we should introduce in our development workflow.

Thanks to PHP_CodeSniffer, our code will be much more clean, readable, robust, and secure.

In another post, we will learn how to integrate PHP_CodeSniffer in PHPStorm. This way, the IDE will assist us to follow the coding standards while we write the code.

Juan José

By Juan José

Building amazing products for WordPress & WooCommerce since 2012.