Learning from open source PHP projects - Part 1 - Code formatting
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:
- Code formatting
- Static analysis
- Testing
- Benchmarks
- Automation
- Commit styles
- Automatic dependency update
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:
- EventSauce PHP CS Fixer configuration
- Flow PHP CS Fixer configuration
- Tempest PHP CS Fixer configuration
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:
declare_strict_types
=> making sure all of the code we produce include the statement to ensure that types are effectively enforcedyoda_style
=> Disabled Yoda styles: our friends don't much like Yoda statements. A good idea to disable those it is, as much unreadable the code they make.php_unit_method_casing
=> PHPUnit method casing in snake case. True, when you start adopting this for tests, it's hard to come back to camelCase. The reasoning behind the use of this convention is quite well explained by Kevlin Henney in his talk Structure and Interpretation of Test Cases. And of course I highly recommend watching the whole talk if you want to write more expressive tests.ordered_imports
=> Having all imports statement being ordered alphabetically sure helps you parse the file's dependencies faster.
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:
- Public properties and methods will always rise to the top, while everything private will be last.
- Constructor and destructor grouped together above the public methods.
- In between the two, static public methods, which are often used as named constructors. So you have the guarantee that all construction related methods stay together and rise at the top.
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.