Maintaining consistent coding style in PHP projects

Have you ever wondered how it's done that Magento core code—or Zend, Symfony or many other PHP projects for that matter—just looks good, especially when compared to custom or 3rd-party code? The key factor here is maintaining consistent coding style. And while there's no substitute for a thorough code review, in this article I'd like to show you a few tools that will help you with that.

The problem of language flexibility

In PHP (and other languages) there are different ways of doing the same thing. Take creating an array. Historically you'd use the array() function to do that, but as of PHP 5.4 you can go for short syntax: []. The latter would probably feel more familiar if you're also working with JavaScript.

Back in the days the only way of creating an array in PHP would be the following:

<?php
return array(
    'backend' => array(
        'frontName' => 'my_secret_admin_link'
    )
);
An old-school array in PHP

Now you can use a more concise syntax:

<?php
return [
    'backend' => [
        'frontName' => 'my_secret_admin_link'
    ]
];
A new-school array in PHP

Now, you’d probably be right thinking that this isn’t big deal. That’s until someone starts mixing the two like so:

<?php
return [
    'backend' => array(
        'frontName' => 'my_secret_admin_link'
    )
];
A mixed-syntax array in PHP

Still, a bit annoying at most, but hardly a blocker.

Let’s try a more complex example: imagine the number of ways there are to write a simple function with a single if statement:

public function isDayOrNight(Sun $sun)
{
    if ($sun->isShining() === true) {
        return 'day';
    } else {
        return 'night';
    }
}
A simple method in PHP

You can omit else without changing the function's behaviour. Then you can also remove the curly braces and have:

public function isDayOrNight(Sun $sun)
{
    if ($sun->isShining() === true)
        return 'day';
 
    return 'night';
}
A more concise version of a simple PHP method

Or maybe you'd like to drop if altogether and use a ternary operator instead:

public function isDayOrNight(Sun $sun)
{
    return ($sun->isShining() === true) ? 'day' : 'night';
}

A simple PHP method using ternary operator

And how about Yoda conditions and replacing $sun->isShining() === true with true === $sun->isShining()?

This gives us 6 different ways of writing one of the simplest possible pieces of code imaginable. Not considering placement of curly braces or spacing around parentheses and operators.

Of course, any project sooner or later will need some more complex logic. In that case using multiple ways to express a single concept will make the code hard to read and understand.

Readability first

“Indeed, the ratio of time spent reading vs. writing is well over 10:1. We are constantly reading old code as part of the effort to write new code.

Because this ratio is so high, we want the reading of code to be easy, even if it makes the writing harder. Of course there's no way to write code without reading it, so making it easy to read actually makes it easier to write.”

Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship

As developers we spend lots of time reading source code. One way we could make this task easier for our teammates and for ourselves is to make sure the code is written in a consistent way.

A side benefit of maintaining consistent codebase is that it shows authors’ attention to detail and is a good indicator of high quality of a project.

Coding Standards: consistent coding style, codified

Coding standards are all about making the code easier to read and understand. While most coding standards I’ve seen focus mainly on formatting, in this article I use this term (interchangeably with “coding style”) in a broader meaning which also encompasses some design patterns and general best practices.

Okay, but how to define a coding standard for a project? My best advice is to use whatever the framework uses. This way the code should feel familiar to any developer who has experience with given platform. And the bonus is that you'll avoid endless discussions on whether tabs or spaces are better for indentation.

As the author of JSLint put it:

“The place to express yourself in programming is in the quality of your ideas and the efficiency of their execution. The role of style in programming is the same as in literature: It makes for better reading. A great writer doesn't express herself by putting the spaces before her commas instead of after, or by putting extra spaces inside her parentheses. A great writer will slavishly conform to some rules of style, and that in no way constrains her power to express herself creatively.”

Once you decide on which coding standard to use, the next step would be to make sure the code complies with it—ideally in an automated way. With a couple of simple tools and well-defined rules you'll have one less thing to think about when working on the code. Although there are many others, in this article I’ll focus on PHP_CodeSniffer and PHP Mess Detector (and EditorConfig), as those are the ones I use everyday. Here's how to set everything up for a Magento-based project.

The common part

I almost always start configuring the project with creating an .editorconfig file:

; This file is for unifying the coding style for different editors and IDEs.
; More information at http://editorconfig.org

root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.bat]
end_of_line = crlf

[*.{json,yml}]
indent_style = space
indent_size = 2
An example .editorconfig file

This little file instructs your IDE to:

  • Use 4 spaces for indentation—both PSR-2 and the old Zend coding standard suggest this,
  • Use Unix-like newline character—since the code will most likely run on some flavour of Linux,
  • Leave an empty line at the end of the file—to avoid complaints from different Unix tools (including git),
  • Remove any extra whitespace (spaces or tabs) at the end of each line—it's almost never there on purpose and can be annoying.

Some IDEs support EditorConfig out-of-the-box, for others you'll need a plugin. I find it to be a sane starting point for just about any project, but of course you can extend this config to match any preference or need.

The interesting part

With EditorConfig taking care of basic formatting, we can now focus on some more advanced stuff and configure PHP-specific tools to keep our coding style in check.

We’ll use PHP_Codesniffer and PHP Mess Detector. Additionally the following packages will come in handy:

You can install both PHPMD and PHPCS with its plugins via Composer. Though everything can be installed globally, I suggest adding your tools to project’s composer.json to avoid compatibility issues.

Note: If you encounter an issue with any of the rulesets (or PHPCS itself) please refer to appropriate project’s repository for help.

Magento 1.x: how it used to be

Having everything installed, let’s get to configuration. We’ll start with PHP_CodeSniffer, which accepts XML configuration files.

An example of dev/phpcs/ruleset.xml would be:

<?xml version="1.0"?>
<ruleset name="Kiwee M1">
    <description>
        Coding Standard for Magento 1.x
    </description>
 
    <!-- Skip core and vendor code -->
    <exclude-pattern>app/code/core/*</exclude-pattern>
    <exclude-pattern>lib/*</exclude-pattern>
    <exclude-pattern>vendor/*</exclude-pattern>
 
    <!-- Magento Extension Quality Program -->
    <rule ref="MEQP1"/>
 
    <!-- Magento Expert Consulting Group -->
    <rule ref="Ecg"/>
 
    <!-- PHP Compatibility -->
    <rule ref="PHPCompatibility"/>
 
    <!-- Keep the code compatible with PHP5.6 and newer (including 7.x) -->
    <config name="testVersion" value="5.6-"/>
</ruleset>

An example annotated PHPCS ruleset for Magento 1

All that’s left to do is to point PHPCS to the ruleset.xml file and it’ll automatically warn you about not only formatting, but also security, performance, and maintainability issues.

Note that MEQP1 ruleset references the whole Zend coding standard, so there’s no need to include it separately.

Good, we now have our code check against some best practices for Magento. But there’s no reason to stop here! Let’s take our coding standard one level higher with the following PHPMD ruleset:

<?xml version="1.0"?>
<ruleset name="Kiwee M1"
        xmlns="http://pmd.sf.net/ruleset/1.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
                    http://pmd.sf.net/ruleset_xml_schema.xsd"
        xsi:noNamespaceSchemaLocation="
                    http://pmd.sf.net/ruleset_xml_schema.xsd">
   <!--
       Source: https://gist.github.com/sandermangel/520cd4b6a03beebee14ee44c551de50e
   -->
   <description>
       Coding Standard for Magento 1.x
   </description>
 
   <rule ref="rulesets/codesize.xml/CyclomaticComplexity">
       <properties>
           <property name="reportLevel" value="15"/>
       </properties>
   </rule>
   <rule ref="rulesets/codesize.xml/NPathComplexity"/>
   <rule ref="rulesets/codesize.xml/ExcessiveMethodLength">
       <properties>
           <property name="minimum" value="150"/>
       </properties>
   </rule>
   <rule ref="rulesets/codesize.xml/ExcessiveClassLength"/>
   <rule ref="rulesets/codesize.xml/ExcessiveParameterList"/>
   <rule ref="rulesets/codesize.xml/ExcessivePublicCount"/>
   <rule ref="rulesets/codesize.xml/TooManyFields"/>
   <rule ref="rulesets/codesize.xml/TooManyMethods"/>
   <rule ref="rulesets/codesize.xml/ExcessiveClassComplexity"/>
 
   <rule ref="rulesets/design.xml"/>
 
   <rule ref="rulesets/naming.xml/ShortMethodName"/>
   <rule ref="rulesets/naming.xml/ConstructorWithNameAsEnclosingClass"/>
   <rule ref="rulesets/naming.xml/ConstantNamingConventions"/>
 
</ruleset>

An example annotated PHPMD ruleset for Magento 1

While PHPCS focuses primarily on formatting and framework-specific patterns, PHPMD covers some more general good practices, like class and method size and complexity, proper naming, and detects some hard-to-read patterns.

Unfortunately, it’s virtually impossible to follow all best practices that PHPMD can check, hence the proposed configuration focuses on the basics. It’s a good starting point and—possibly after minor tuning—will prove itself valuable for maintenance of even old Magento 1 projects.

Magento 2.x: how it is now

Even though the coding standard is pretty well outlined in Magento 1, it’s Magento 2 where you get full benefit of having PHPCS and PHPMD properly setup. Let’s dive straight into the rulesets!

An example of dev/phpcs/ruleset.xml would be:

<?xml version="1.0"?>

<ruleset name="Kiwee M2">

<description>
Coding Standard for Magento 2.x
</description>

<!-- Skip core and vendor code -->
<exclude-pattern>vendor/*</exclude-pattern>

<!-- Magento Extension Quality Program -->
<rule ref="MEQP2"/>

<!-- Providing path to Magento 2 unlock additional abilities of MEQP2 ruleset -->
<config name="m2-path" value="."/>

<!-- Magento Expert Consulting Group -->
<rule ref="EcgM2"/>

<!-- Magento Extension Developers Network -->
<rule ref="ExtDN"/>

<!-- PHP Compatibility -->
<rule ref="PHPCompatibility"/>

<!-- Keep the code compatible with PHP7.1 -->
<config name="testVersion" value="7.1-"/>
</ruleset>
An example PHPCS ruleset for Magento 2

At first glance this ruleset is not too unlike the one we’ve just seen for Magento 1. Don’t be fooled by superficial similarity though—with Magento 2 coding standards and best practices being better defined than previously, both EcgM2 and MEQP2 include some additional checks. Moreover a new player has entered the game: Magento Extension Developers Network (ExtDN) who aim to provide even more rules you can use on your code.

Note that MEQP2 ruleset already includes PSR2 coding standard, so there’s no need to include it separately.

Going further, an example of dev/phpmd/ruleset.xml would be:

<?xml version="1.0"?>

<ruleset name="Kiwee M2"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="
http://pmd.sf.net/ruleset_xml_schema.xsd">

<description>
Super-strict Coding Standard for Magento 2.x
</description>

<rule ref="rulesets/cleancode.xml"/>
<rule ref="rulesets/codesize.xml"/>
<rule ref="rulesets/design.xml"/>
<rule ref="rulesets/naming.xml"/>
<rule ref="rulesets/unusedcode.xml"/>

</ruleset>
An example PHPMD ruleset for Magento 2

With Magento 2 it’s finally possible to go all-in on PHPMD. This means checking all the stuff that Magento 1 ruleset would check and more—such as  possible SRP violations, static access or unused code.

You may find the proposed ruleset to be a bit too strict for your particular project and coding style, yet I’d strongly suggest starting with this configuration and relaxing it when necessary—as opposed to starting with a pretty lax ruleset for Magento 1 and adding more checks later.

Caveats

As often with tools—your mileage may vary.

When introducing a formalized coding standard on a project, consider the impact it may have. To benefit fully from using the tools discussed here, you need to make sure everyone on the team is on board and understands what the standards are for.

Also bear in mind that while you can (and certainly should in some cases) use comments to suppress both PHPCS and PHPMD warnings, but overly strict rulesets will likely result in you and your teammates marking whole blocks or even files as ignored, which defies the purpose of having a ruleset in the first place.

Conclusion

Maintaining consistent coding style makes developing software much easier. Not having to worry about how to write the code frees your mind to let you focus on solving the problem at hand.

Even though some standards are difficult to get used to, some are controversial and some outright weird, the benefit of having a single, well-defined way of expressing yourself as a developer is hard to overvalue.

If you and your team don’t have a defined coding style—it’s worth adopting one. And if you do, it’s worth revising it from time to time. Standards evolve (luckily in PHP realm not as fast as in JS world) but the right tools make it a breeze to follow them.

Have fun coding!

FacebookTwitterPinterest

Maciej Lewkowicz

Full Stack Developer

I believe there's love in new things and in old things there is wisdom. I love exploring all sorts of systems, especially putting enterprise-grade open source tools to use in small-scale projects, all the while being on a personal mission to make the Internet a little better for everyone.