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

Class, closure, or config map: pick the lightest fit

Strategy doesn't have to be one interface and N classes. Three common shapes show up in PHP, and picking the right one keeps the pattern from feeling heavyweight.

Variant 1 — Class per strategy (the canonical shape)

The version we built in lesson 2.4. Best when:

  • each strategy has any non-trivial logic
  • strategies need their own constructor dependencies
  • strategies will grow over time

Cost: one file per strategy.

Variant 2 — Closures as strategies

If each strategy is a one-liner, classes are overkill:

<?php

$discounts = [
    'guest'         => fn (int $total) => 0,
    'standard'      => fn (int $total) => $total > 5000 ? (int) round($total * 0.05) : 0,
    'loyalty'       => fn (int $total) => (int) round($total * 0.10),
    'employee'      => fn (int $total) => (int) round($total * 0.40),
];

$discount = $discounts[$customerType] ?? fn () => 0;
$amount = $discount($totalInPence);

Best when the rules are genuinely simple, the set is small, and you're inside one file. As soon as a rule grows beyond a few lines or needs dependencies, promote it to a class.

Variant 3 — Config-driven strategies

When the variation is purely numeric, a class per rule is overkill — a data structure does the job:

<?php

const DISCOUNT_RATES = [
    'guest'         => 0.00,
    'standard'      => 0.05,
    'loyalty'       => 0.10,
    'loyalty_plus'  => 0.15,
    'employee'      => 0.40,
];

$rate = DISCOUNT_RATES[$customerType] ?? 0.00;
$discount = (int) round($totalInPence * $rate);

This isn't really Strategy any more — it's a lookup. That's the point. If your variation is just a number that changes by case, you don't need a pattern; you need a map.

How to choose

Signal Use
Rules are non-trivial (multiple lines, conditions, dependencies) Class per strategy
Rules are one-liners with no setup Closures
Rules differ only in a value (a rate, a limit, a flag) Config map
Mix of trivial and non-trivial Class per strategy — keep it consistent

The mistake to avoid is starting with config-map for simplicity, then bolting on logic when the rules grow. The config map silently becomes a mini interpreter. Promote it to classes the moment a rule needs branching of its own.

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