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