Learn how to write readable PHP that is a joy to maintain
I'm proud to announce that our new premium course on writing readable PHP is now available. It's called Writing Readable PHP.
This course contains a collection of bite-size tips (both in written form and videos) that make your code a joy to read for your co-workers and future self. These tips are aimed towards developers who know the basics of PHP and want to improve their craft. As a bonus, you'll learn to use static analysis to ensure that your code is understandable and correct.
Writing Readable PHP has been created by our team and Christoph Rumpel. It contains our combined knowledge on how to write the best PHP possible.
Why we created this course
I still remember coding up my first bits of PHP around the beginning of the century. That makes me sound very old, but let me assure you that I'm still young at heart. My coding was done in Notepad, without any code highlighting or autocompletion. The thing I remember most is the magical feeling that I managed to output some HTML in the browser using PHP.
The code itself, how it was written and structured, didn't feel too important to me. What was important was that the code did what I intended: rendering a page, and that's that. I would write a lot of code but rarely reread it. This became problematic after a while: updating existing projects became cumbersome because I couldn't understand my code.
Nowadays at Spatie, things have vastly changed. Coding is not done on my own but with an entire team. Now I believe that how you accomplish something is as important as the accomplishment itself. This is probably true for most things in life. But let's not get into old-mans-talk-territory and stick to code. How the code is written is at least as important as what the code does. At Spatie, we often say that if you write code that works, you're only halfway there.
You might already have heard that code is read a lot more often than it is written. Through experience, we know that is true.
Over the years, our team and I have learned a lot of patterns, conventions and tips that make code more readable. Most of these things are very small, but using all those tips together makes an incredible difference in code readability. Together with Christoph Rumpel, a fantastic teacher and programmer in his own right, we put all of our knowledge on code readability in a course.
A few examples from the course
The course contains is split in these sections:
- Visuals
- Naming
- Code Structure
- Modern PHP
- Static Analysis
- Laravel
Let's take a look at some of the tips themselves.
Optimize readability for the happy path
One of our favourite techniques for ordering a function's steps is by first handling all edge cases, and keeping the last step in a function for the most important part: the "happy path".
Here's an example where the happy path is handled first. Try to read it: you'll notice that, besides some code overhead, it's also more confusing to understand what this code does from a first read.
// core functionality comes first, special cases handled at the end
public function sendMail(User $user, Mail $mail)
{
if ($user->hasSubscription() && $mail->isValid()) {
$mail->send();
}
if (! $user->hasSubscription()) {
// throw exception
}
if (! $mail->isValid()) {
// throw exception
}
}
Instead, let's first check and handle all edge cases followed by the happy path as the last step of the function. Conveniently, it doesn't need to be wrapped in a condition anymore if all edge cases have already been handled.
// special cases handled first, core functionality comes later
public function sendMail(User $user, Mail $mail)
{
if (! $user->hasSubscription()) {
// throw exception
}
if (! $mail->isValid()) {
// throw exception
}
$mail->send();
}
How to refactor complex conditionals
The course doesn't only contain written examples, but also high quality 4K videos.
Here's one where we explain how to refactor complex conditionals.
Use the null coalescing operator
In PHP 7.4, the null coalescing assignment (or equal) operator was introduced. This is what it looks like: ??= and as its appearance seems to suggest, it allows you to combine the null coalescing operator from the previous chapter with an assignment.
// traditional way of handling null for an optional argument
function myFunction(MyClass $object = null)
{
if (is_null($object)) {
$object = new MyClass(); // assign a default value
}
// ...
}
You might improve it like this:
// using the null coalescing operator
function myFunction(MyClass $object = null)
{
// set $object to its own value, unless it's `null`,
// then fall back to a new instance of MyClass
$object = $object ?? new MyClass();
}
We're down from three lines of code to set a default value to just one. A significant improvement, but using the null coalescing assignment operator, we can do even better.
// using the null coalescing operator
function myFunction(MyClass $object = null)
{
// only set $object to a new instance of MyClass if it's `null`
$object ??= new MyClass();
}
Discovering static analysis
PHPStan is a wonderful static analysis tool that can detect many types of errors. In this course, you'll learn how to use this tool to eliminate entire categories of bugs. Let's take a look at a small example.
When using arrays, you can hint at specific keys and their type. Imagine you have an array containing a person’s properties. You could type-hint the properties like this:
/**
* @param array{first_name: string, last_name: string} $personProperties
* return string
*/
function fullName(array $personProperties): string
{
// ...
}
Should we try to use $person[‘non-existing’]
then PHPStan would warn us with:
Offset 'non-existing' does not exist on array{first_name: string, last_name: string}.
And here's a very nice bonus: when using this type docblock modern IDEs can provide autocompletion when working with the array keys.
Ain't that great? We just made it far less likely that you'll use a non-existing array key.
Readable Laravel
Most of the content in the course is framework agnostic. Because Christoph and I have a background with Laravel, we thought it would be nice to include a few of our favourite Laravel readability tips as well. Let's take a look at one of them.
In your project, you might need to build up queries conditionally.
$postsQuery = Posts::query();
// adding a condition by hand
if ($latestFirst) {
$postsQuery->latest();
}
$posts = $postsQuery->get();
You can clean this up with the when method. The query will only be modified if the first argument is truthy.
use Illuminate\Database\Eloquent\Builder;
$posts = Post::query()
->when($latestFirst, fn(Builder $query) => $query->latest())
->get();
A new comments package for Laravel
Some of the tips that we give in the course will lead to objectively better code. But there are also tips which are very much opinionated. We decided very early on that we wanted a way that people who follow the course can share their opinion with us and others following the course.
We took a look at existing commenting solutions, and to make a long story short, most of them sucked. Most looked ugly, weren't customisable, or injected an unholy amount of JavaScript.
That's why we've built a custom comments component using Livewire. This component comes with batteries included:
- beautifully rendered comments
- nested comments
- reactions 🥳 👍 🚀
- syntax highlighting
- approval flow for admins
- notifications for new comments for all participants
- optional read-only mode
- optional reverse ordering (so the newest comments are displayed first)
You can see and use the component underneath each page of our course.
I'm also already using this comments component on the very blog you are reading. So if you want to see a live example of the component, just go to the bottom of this post.
We've decided that we'll release this component as a separate package. In fact, it will become two packages:
- spatie/laravel-comments: contains the core logic and notifications. You can use this to quickly build any commenting UI
- spatie/laravel-comments-livewire: a Livewire powered UI that builds upon spatie/laravel-comments.
The documentation is already available. The component is already very stable, but we want to make it even more customisable than it is now. We aim to release a stable version in the next few weeks.
In closing
Should you decide to pick up Writing Readable PHP, we hope you'll like it!
Our course is a living document: we will add and change things in the content based on feedback on new insights. These tips work well for us, and we hope they will improve your code as well!
This is the real comment component. Hope you'll like it! You'll find the documentation here. We'll release a stable the package in the next few weeks.
You can use markdown to format things, and backticks to add a code snippet, just like you're used to on GitHub.
Feel free to use one of the emoji's below to react, post a reply to this comment, or create a new comment.
Is there a similar option for
@return
, like you showed in@param array{first_name: string, last_name: string}
?Let's say your method returns array of values of different types for example.
Can we type hint so types are known when we do the following?
Yes, you can use this syntax at
@return
as well 👍Depending on your application, you can test features such as Bluetooth pairing, communication with other devices, Drive Mad or even peripheral role behaviors like HID, Battery Service, etc.