Just Launched!

Spatie and Christoph Rumpel present:

Writing
Readable

Video still
Play intro video

Are you...

Stop the madness! Let’s learn how to write readable PHP in this hands-on course.

Get the entire course and start writing clean code for just:

149 USD

VAT will be calculated during checkout by Paddle.
We support Purchasing Power Parity.

Free coding tips from the full course

Subscribe to our complimentary newsletter
to receive handy code readability tips from the premium course.

Subscribe

You get a weekly email containing a clean code tip and
occasional course updates for the next five weeks.

What will the course
get me?

  • A collection of bite size tips that make your code a joy to read for you, your co-workers, and future self. These tips are aimed towards developers who know the basics of PHP and want to improve their craft.
  • Learn how to use static analysis to ensure that your code is understandable and correct.
  • Get access to our learning platform to track progress and discuss good coding habits with other course participants and the authors.

Code Examples

In general, code is more frequently read than written. That’s why you should optimize for readability, which is easier than you would think. It’s a matter of taking care of the details. Here are some examples from each of the six chapters of the course:

Optimize readablity for the happy path

In a previous part we've mentioned that grouping code in paragraphs is good for readability. There's another advantage: it visualises the steps a function takes to get to a certain results. It makes sense to order those steps in a way that's natural to you, the programmer.

Our favorite technique of ordering a function's steps is by first handling all edge cases, and keep 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
    }
}

So 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();
}

Adding metrics to names

Consider adding the unit to the name whenever you work with something measurable.

// bad: we don't know what that 100 represents
$averageTime = 100;

// good: we now know that it is 100ms
$averageTimeInMs = 100;

Another way of dealing with this is by creating dedicated objects. Imagine that you need to work with a percentage. Which of these is correct?

$percentage = 0.5;
$percentage = 50;

You can’t tell what your application expects. Let’s now use an object with a static constructor, one for each possibility.

class Percentage
{
    public static function fromInt(int $percentage): self
    {
        return new self($percentage);
    }

    public static function fromFloat(float $percentage): self
    {
        return new self($percentage * 100);
    }

private function __construct(
        public int $value;
    ) {};
}

Using a `Percentage` class clarifies that an integer is expected.

$percentage = Percentage::fromInt(50);

Use the null coalescing assignment operator

In PHP 7.4 the null coalescing assigment (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 could 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();
}
Video still
Play Sample

Using array shapes with PHPStan

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 with 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 an non-existing array key.

Conditionally building up queries

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();

What others have to say

"Writing Readable PHP" will give you some amazing tips on how to write clean and modern PHP applications. It's full of tips I learned myself over the years as a programmer and it's great to see them all come together in this book.

Dries Vints
Dries Vints
Laravel Core Team Member

Freek and Christoph bring a ton of experience to the table. They make sure you have all the tools you need to keep your code readable for years to come.

Bobby Bouwmann
Bobby Bouwmann
Laravel Evangelist & Author

Having spent more than 10 years writing PHP, I thought I knew everything I needed. I learned a lot more than I expected as an experienced developer, and can see a lot of benefit in this course for developers of any level.

Steve King
Steve King
Laravel Developer & Community Advocate

“Clean code” covers a wide spectrum, but Freek and Christoph manage to boil the essence of beautiful programming down to a very useful set of guidelines. I especially recommend the section on static analysis for programmers looking to take their skills to the next level.

Luke Downing
Luke Downing
Laravel Developer & Enthusiast

About the authors

Other courses
from Spatie

Get the entire course and start writing clean code for just:

149 USD

VAT will be calculated during checkout by Paddle.
We support Purchasing Power Parity.