Best Practices

This track has walked through the classic Gang of Four patterns one at a time, each in isolation. In a real codebase, the skill that actually matters isn't reciting definitions — it's recognizing which recurring problem you're looking at, and reaching for the pattern that fits it (or reaching for nothing at all). This page is a practical checklist for that.

1. Don't Force a Pattern Where Simple Code Already Works

Patterns solve specific, recurring problems — they aren't a checklist to apply to every class you write. A single, straightforward class with no competing implementations and no risk of accidental duplicate instances doesn't need a Singleton wrapped around it; it just needs to be instantiated normally.

<?php

// Overkill: this class has no reason to ever have more than one instance,
// and nothing about it needs global access — a Singleton adds ceremony for nothing
class InvoiceFormatter {
    public function format(float $amount): string {
        return '$' . number_format($amount, 2);
    }
}

// Just use it directly — no getInstance() ceremony needed
$formatter = new InvoiceFormatter();
  • Ask "what recurring problem does this solve?" before reaching for a pattern — if there isn't one, skip it.
  • Remember the honest criticism from the Singleton page: even a "correct" pattern can introduce global state and testing pain you didn't need.
  • A plain class with clear responsibilities beats a pattern applied out of habit.

2. Prefer Strategy Over Long If/Elseif Algorithm Chains

Whenever a method picks between several ways of computing the same kind of result based on a type or string, that's the shape Strategy was built for.

<?php

// Warning sign: this method grows a new branch every time a new algorithm appears
function calculateDiscount(string $type, float $price): float {
    if ($type === 'vip') { return $price * 0.8; }
    if ($type === 'seasonal') { return $price * 0.9; }
    return $price;
}
  • See this shape? Extract each branch into its own class implementing a shared interface.
  • New algorithm = new class, not an edit to a method that already works.
  • Revisit the Strategy page for the full ShippingStrategyInterface walkthrough.

3. Keep Object-Creation Logic Out of Client Code

If client code needs to know a concrete class name (or several) just to get an object it can use, that construction knowledge belongs behind a Factory Method, or an Abstract Factory when whole families of related objects need to be created consistently together.

<?php

// Client code shouldn't need to know concrete class names to get a working object
$logger = LoggerFactory::create($config->get('log_driver'));
  • One factory place = one place to update when a new type of object is added.
  • Use Abstract Factory when several related objects must be created together and stay compatible (see the Abstract Factory page).

4. Favor Composition Over Deep Inheritance Hierarchies

When behavior needs to be combined in different combinations — logging plus caching plus retrying, in any mix — a deep inheritance tree runs out of names fast. Decorator lets you wrap combinable behaviors around a shared interface instead.

  • If you catch yourself naming a class CachingRetryingLoggingService, that's the signal.
  • Composition means each behavior is its own small, testable class that can be mixed and matched.
  • Full worked example on the Decorator page.

5. Reach for Adapter When You Can't Modify the Other Side

Integrating a third-party library or legacy class whose interface doesn't match what your code expects? Don't rewrite your own interfaces around its quirks — wrap it in an Adapter instead.

  • The adapter absorbs the mismatch in one place, instead of spreading special cases through your codebase.
  • Your application code keeps depending on the interface it expects, not the vendor's actual class.
  • Swapping the third-party library later means writing a new adapter, not rewriting your application.

6. Use a Facade for a Simple Front Door — Not to Hide Everything

When a subsystem has many moving parts that are almost always used together in the same sequence, a Facade gives callers one simple entry point for the common case.

  • A good Facade covers the 90% common-case calls — it doesn't have to be the only way in.
  • Leave the underlying classes public so advanced callers can still reach past the facade when they genuinely need to.
  • Don't let the facade grow into a dumping ground for unrelated helper methods.

7. Recognize These Patterns Already Live Inside Your Framework

Before reaching for a pattern from scratch, check whether Laravel, Symfony, or CakePHP (all covered elsewhere on this site) already give you a working implementation of it. Laravel's service container is Singleton-flavored, its Events/Listeners are Observer, queued Jobs are Command-like. Symfony's EventDispatcher is Observer, its DI container assembles objects the way an Abstract Factory would. Reusing the framework's implementation is almost always better than hand-rolling your own.

  • Search the framework's docs for your problem before writing a new pattern implementation.
  • Recognizing a pattern inside framework source code is often more valuable day-to-day than writing one from scratch.

8. Learn Problems First, Patterns Second

The biggest trap when first learning design patterns is memorizing pattern names and looking for places to use them. That's backwards. The actual skill is recognizing a recurring problem — "I have an if/elseif chain picking algorithms," "I need to notify several decoupled parts of the system," "I need to undo an action" — and only then reaching for the pattern whose shape matches. A pattern applied to a problem it doesn't fit adds complexity without adding value.

Note: If you're ever unsure whether a pattern fits, write the plain, unpatterned version first. If it stays simple, leave it. If it starts growing branches, duplication, or tangled responsibilities, that pain is usually a clear pointer to which pattern actually helps.
You've completed the PHP Design Patterns track. Congratulations — you've now covered all three GoF categories: Creational, Structural, and Behavioral. Recognizing these shapes will make reading through Laravel, Symfony, and CakePHP source code (all covered elsewhere on this site) noticeably easier, since so much of their internals are built directly on the patterns you just learned. The "Next" button below leads back to the PHP hub, where you can pick up another track.