Originally published on phpdesignpatterns.dev — part of the PHP design patterns course. View this lesson on the original site.

Three branch-heavy snippets to refactor

Three exercises. Each gives you a piece of branch-heavy PHP. Refactor it towards Strategy where Strategy fits, and recognise where it doesn't.

This lesson is intentionally hands-on. Work through the exercises in your own editor; don't read on for the solutions until you've made an attempt.

Reference solutions live in the GitHub repo: strategy-chapter-2-guided-practice

Exercise 1 — Tax calculator

<?php

final class TaxCalculator
{
    public function tax(string $region, int $netInPence): int
    {
        if ($region === 'UK') {
            return (int) round($netInPence * 0.20);
        }
        if ($region === 'EU') {
            return (int) round($netInPence * 0.21);
        }
        if ($region === 'US') {
            $state = 'NY';
            return (int) round($netInPence * 0.0875);
        }
        if ($region === 'AU') {
            return (int) round($netInPence * 0.10);
        }
        return 0;
    }
}

Task: Refactor into a Strategy interface and a class per region. Decide what to do about the hard-coded 'NY' — is it part of the contract or a separate concern?

Exercise 2 — Pricing tiers (a trap)

<?php

final class TierPricing
{
    public function priceInPence(string $tier): int
    {
        return match ($tier) {
            'free'    => 0,
            'basic'   => 999,
            'pro'     => 2999,
            'team'    => 9999,
        };
    }
}

Task: Should this be a Strategy? Justify your answer in one paragraph in your README. (If your answer is "no", that is the correct answer — explain why.)

Exercise 3 — Report renderer

<?php

final class ReportRenderer
{
    public function render(string $format, array $rows): string
    {
        if ($format === 'csv') {
            $output = '';
            foreach ($rows as $row) {
                $output .= implode(',', $row) . "\n";
            }
            return $output;
        }

        if ($format === 'json') {
            return json_encode($rows);
        }

        if ($format === 'html') {
            $html = '<table>';
            foreach ($rows as $row) {
                $html .= '<tr>';
                foreach ($row as $cell) {
                    $html .= '<td>' . htmlspecialchars($cell) . '</td>';
                }
                $html .= '</tr>';
            }
            return $html . '</table>';
        }

        throw new RuntimeException("Unknown format: {$format}");
    }
}

Task: Refactor into a Strategy interface (RowsRenderer?), one class per format. Bonus: write one unit test per renderer that doesn't construct the others.

What "done" looks like

For each exercise, your solution should:

  • have a clear interface with a meaningful method name
  • have one strategy per genuine variation
  • have a picker (constructor map, factory, or match) that's testable independently
  • include at least one unit test per strategy
  • explicitly state in the README which exercise needed Strategy and which didn't (and why)

When you're ready, compare with the reference repo above.

From PHP design patterns — Strategy: replacing branching with swappable behaviour