Originally published on decentphp.dev — part of the Writing decent PHP course. View this lesson on the original site.

This lesson is intentionally hands-on.

Complete all three sandbox exercises below in order.

Exercise 1: flatten nested logic

Refactor this function to use early returns so the happy path is obvious.

<?php

function sendReceiptIfNeeded(array $order, array &$sentEmails): void
{
    if (($order['is_paid'] ?? false) === true) {
        if (($order['customer_email'] ?? '') !== '') {
            $sentEmails[] = $order['customer_email'];
        }
    }
}

Exercise 2: remove hidden dependencies

Refactor this function so it does not rely on $_ENV or globals.
All required values should be explicit function parameters.

<?php

$_ENV['MAIL_ENABLED'] = '1';

function canSendDigest(array $user): bool
{
    global $currentHour;
    $currentHour = $currentHour ?? 9;

    if (($_ENV['MAIL_ENABLED'] ?? '0') !== '1') {
        return false;
    }

    if (($user['email'] ?? '') === '') {
        return false;
    }

    return $currentHour >= 9;
}

Exercise 3: avoid unnecessary abstraction

Simplify this over-engineered export flow.
Behaviour to preserve: keep only active users with a non-empty email address.

<?php

interface UserSelectionStrategy
{
    public function include(array $user): bool;
}

final class ActiveUserSelectionStrategy implements UserSelectionStrategy
{
    public function include(array $user): bool
    {
        return ($user['active'] ?? false) === true;
    }
}

final class UserExportPipeline
{
    public function __construct(private UserSelectionStrategy $strategy) {}

    public function run(array $users): array
    {
        $emails = [];

        foreach ($users as $user) {
            if (! $this->strategy->include($user)) {
                continue;
            }

            $email = (string) ($user['email'] ?? '');
            if ($email === '') {
                continue;
            }

            $emails[] = $email;
        }

        return $emails;
    }
}

How to approach the exercises

  • keep each change small
  • preserve behaviour before improving structure
  • optimise for readability and local reasoning
  • avoid adding abstractions unless there is a current, concrete need

Reference solutions

Reference implementations for all three exercises are available here:

Try each exercise yourself first before comparing against the reference.

From Writing decent PHP — What decent PHP looks like