Quentin Delcourt

Learning from open source PHP projects - Part 1 - Code formatting

This is part 1 in a series where I gather software projects best practices by looking at popular PHP projects open source repositories.

I have a confession to make: I never really participated a lot in open source. Being an introvert, it's difficult for me to join a project. And being a father doesn't help, as not a lot of energy remains in the evenings to get involved properly.

I did work on some friends projects, though, and wrote some open source code myself, so I'm not a complete stranger to this world. And of course, as many other developers in the world, I rely heavily on open source software for my everyday work.

I think we can learn a great deal from open source projects, just as any painter starts to work by taking inspiration from the masters.

In this blog series I'm going to take a look at a few popular PHP open source projects and their best practices.

Here are the projects I selected:

Those repositories are open source and made by masters of the craft.

We'll take a look at several best practices and how they're used in those repositories:

To kick off this series, let's look at code formatting.

Code formatting

When working on a project where people you've never teamed with are going to contribute, you don't want to waste time on arguing on things as silly (and yet important) as code formatting. Nowadays most languages come with their automatic formatting system, which settles any debate. The only "issue" is that you need to decide on a format early on of course.

So how can we implement this and make sure our code is always formatted the same way? There are two big tools in this area: PHP CS Fixer and PHP_CodeSniffer.

There seems to be a tendency to use PHP CS Fixer in most projects, hence I will first take a look at how these projects implement the tool's configuration.

EventSauce, Flow, and Tempest are all using PHP CS Fixer.

Here are the links to these projects CS Fixer configurations:

What they all share

The following statements seems to be common to all of them:


'declare_strict_types' => true,
'yoda_style' => [
    'equal' => false, 
    'identical' => false, 
    'less_and_greater' => false
],
'php_unit_method_casing' => ['case' => 'snake_case'],
'ordered_imports' => [
    'sort_algorithm' => 'alpha',
],

Let's break them down one by one:

Looking further, we can see that some projects are using @Symfony presets, while others choose @PSR12 presets. Again, although all these projects have slight different preferences, they rely on very popular projects to inherit all kinds of best practices and normalisation techniques.

Tests style consistency

If we look at the individual properties of the configurations, we can see that Tempest has a quite extensive set of rules for PHPUnit:


'php_unit_data_provider_name' => [
    'prefix' => 'provide_',
    'suffix' => '_cases',
],
'php_unit_data_provider_return_type' => true,
'php_unit_data_provider_static' => [
    'force' => true,
],
'php_unit_expectation' => [
    'target' => 'newest',
],
'php_unit_mock' => [
    'target' => 'newest',
],
'php_unit_mock_short_will_return' => true,
'php_unit_set_up_tear_down_visibility' => true,
'php_unit_size_class' => false,
'php_unit_test_annotation' => [
    'style' => 'prefix',
],
'php_unit_test_case_static_method_calls' => [
    'call_type' => 'this',
],

These rules will bring harmony in all the project's test classes, and I think that it's quite welcome, seeing as each developer can have very different ways of implementing test classes. I'd probably wouldn't use the "prefix" for php_unit_test_annotation as I like to avoid the redundancy of "test" being included in every method's name.

Elements ordering

The particularity of Flow's PHP CS Fixer configuration is the detailed ordering of the class elements. Let's take a look:


'ordered_class_elements' => [
    'order' => [
        'use_trait',
        'constant_public',
        'constant_protected',
        'constant_private',
        'case',
        'property_public_static',
        'property_protected_static',
        'property_private_static',
        'property_public',
        'property_protected',
        'property_private',
        'construct',
        'method_public_static',
        'destruct',
        'magic',
        'phpunit',
        'method_public',
        'method_protected',
        'method_private',
        'method_protected_static',
        'method_private_static',
    ],
    'sort_algorithm' => 'alpha'
],

First, I didn't know this ordering system existed, I only knew about the imports. And seeing this configuration, I quite like it. Let's break it down:

Alternatives

Other systems are in use and I quickly had a look at them. BetterReflection uses PHP_CodeSniffer and psl uses Mago.

PHP_CodeSniffer

For BetterReflection, they are actually using the Doctrine Coding Standard, which is itself based on the Slevomat coding standard. Again the idea is to stand on the shoulders of giants. The only remaining work is to exclude some of the rules that do not fit the project in the configuration file of the tool. I'm a bit confused still about the differences between PHP_CodeSniffer and PHP CS Fixer, but Sebastian Bergmann explains it in this article.

Mago

Last but not least, Mago is, according to the project's Github description:

[...] a toolchain for PHP that aims to provide a set of tools to help developers write better code.

Here the configuration rules are written in a mago.toml file and the tool is written in Rust, which gives it a speed advantage.

The project seems to be in its early days, but I'll surely keep an eye on it.


That's all for now!

In the next article, we'll take a look at static analysis.