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 exercises in order. Each one targets a different cluster of naming smells from this chapter.

Don't change the behaviour — only the names, constants, file layout, and signatures. A good naming refactor is a no-op for what the code does and a transformation for how it reads.

Exercise 1: variable, boolean, and function names

Rename every variable, boolean, and function so that the names alone tell the story. Extract the magic number into a named constant and name the unit on the amount.

Do not change the business logic, the return shape, or add classes.

<?php

function process(array $o, array $c): bool
{
    $t = $o['amt'] * 1.2;
    $f = $c['vip'] && $c['stat'] === 'a' && $c['age'] >= 18;
    $e = $c['eml'] !== '' && filter_var($c['eml'], FILTER_VALIDATE_EMAIL);

    if ($t > 100 && $f && $e) {
        return true;
    }

    return false;
}

Your refactor should make a reader understand the business rule without opening any referenced code.

Exercise 2: class, namespace, and file location

This file lives at src/Helpers/OrderMgr.php in the App\Helpers namespace. The class does exactly one thing: it calculates the VAT-inclusive total for an order.

Rename the class, choose a better namespace and folder, and give every method a name that matches its real behaviour. Fix anything the existing names lie about.

<?php

namespace App\Helpers;

final class OrderMgr
{
    private $db;

    public function __construct($db)
    {
        $this->db = $db;
    }

    public function getOrder(int $id): array
    {
        $row = $this->db->query("SELECT * FROM orders WHERE id = $id")[0];
        $this->db->update('orders', ['id' => $id, 'last_viewed' => time()]);

        return $row;
    }

    public function calc(array $order): float
    {
        $t = 0;
        foreach ($order['items'] as $i) {
            $t += $i['price'] * $i['qty'];
        }

        return $t * 1.2;
    }

    public function doStuff(array $order): array
    {
        $order['total'] = $this->calc($order);
        $order['processed'] = true;

        return $order;
    }
}

When you're done, the class name, namespace, folder, and every method name should describe exactly what the code does. Things to consider:

  • which method currently lies about its behaviour?
  • which method name is a placeholder?
  • is Helpers the right namespace for billing-shaped code?
  • what unit is the total in, and can the name say so?

Exercise 3: domain language and collection names

This snippet is the kind of code you'd find in a reporting service. Some names use one word, others use synonyms for the same concept. Collection variables don't say what they hold. Loop variables don't match their collections.

Rename everything so each domain concept has exactly one word across the whole file, collections are named plurally with the right qualifier, and loop variables are the singular of their collection.

<?php

final class ReportingService
{
    public function build(array $people, array $txs, array $tplMap): array
    {
        $out = [];

        foreach ($people as $p) {
            $data = [
                'client_name' => $p['name'],
                'customer_id' => $p['id'],
                'user_email' => $p['email'],
            ];

            $relevant = [];
            foreach ($txs as $t) {
                if ($t['buyer_id'] === $p['id']) {
                    $relevant[] = $t;
                }
            }

            $tpl = $tplMap[$p['tier']] ?? $tplMap['default'];

            $data['transactions'] = $relevant;
            $data['template'] = $tpl;

            $out[] = $data;
        }

        return $out;
    }
}

Decide on one word for the main person-concept — customer, client, or user — and use only that word everywhere (in variables, in keys returned to callers, and in collection names). Rename $txs, $out, $relevant, $tpl, $tplMap, and every loop variable to match.

How to approach the exercises

  • read each snippet once cold before touching it
  • list the smells you can see, then fix them in small passes (like the walkthrough in lesson 2.14)
  • preserve behaviour — these are rename-only refactors
  • check that your refactored version would pass the first-read test from lesson 2.13

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 — Naming variables, functions, classes, and files well