Design Patterns in PHP and JavaScript: A Guide for Humans Who Don’t Want to Read a Textbook

Illustration showing PHP and JavaScript design patterns with real code examples including Singleton, Factory, Adapter, Decorator, Facade, Observer, and Strategy.
Learn the most common PHP and JavaScript design patterns through practical code examples and memorable real-world analogies.

Let’s be honest: the moment someone says “design patterns,” a lot of developers mentally check out. It sounds like homework. It sounds like something a professor assigns right before a midterm you didn’t study for.

But here’s the good news — you already understand design patterns. You’ve been using them your whole life. You just didn’t know they had fancy names.

Ever used a vending machine? Congratulations, you understand the Factory pattern. Ever put a hat and scarf on a snowman? You’ve casually implemented the Decorator pattern. Ever said “just give me the chicken nuggets” instead of personally supervising a kitchen? That’s a Facade, my friend, and you nailed it.

Design patterns are just reusable solutions to problems that keep showing up in code. They’re not code you copy-paste directly — think of them more like recipes. The recipe for banana bread doesn’t care whose kitchen you’re in; the same logic applies whether you’re baking in PHP or JavaScript.

So let’s break these down properly — in plain English first, then in actual code, so you can see exactly where the “recipe” shows up in real projects.


Why Should You Even Bother Learning These?

A few honest reasons, no fluff:

  • You’ll understand other people’s code faster. When a senior dev says “just wrap it in a Decorator,” you’ll know exactly what they mean instead of nodding and Googling it later in secret.
  • Your code gets easier to maintain. Patterns exist because smart people got tired of writing messy, tangled code and found cleaner ways to do things.
  • Frameworks are basically built out of these. Laravel, Symfony, React, Express — they’re all just design patterns wearing a nice framework costume.

Alright, let’s get into it.


Creational Patterns: How Things Get Made

Singleton — “There Can Only Be One”

Picture a classroom with exactly one class hamster. Not two. Not five. One hamster, shared by everyone, kept in a cage by the teacher. Every time a kid wants to see the hamster, they ask the teacher — they don’t go build their own hamster in the garage (please don’t).

That’s a Singleton: a class that only ever gets instantiated once, no matter how many times you ask for it.

php

class Config {
    private static ?Config $instance = null;
    private array $data = [];

    private function __construct() {
        $this->data = ['env' => 'production'];
    }

    public static function getInstance(): Config {
        return self::$instance ??= new Config();
    }

    public function get(string $key) {
        return $this->data[$key] ?? null;
    }
}

echo Config::getInstance()->get('env'); // production

javascript

class Config {
  static #instance;
  #data = { env: 'production' };

  static getInstance() {
    return (Config.#instance ??= new Config());
  }

  get(key) {
    return this.#data[key];
  }
}

console.log(Config.getInstance().get('env')); // production

The trick is that private function __construct() (PHP) and the private static field (JS) quietly block anyone from making a second hamster. Everyone gets routed back to the same cage.

Fair warning: Singletons have a bit of a reputation problem in the developer world. They’re handy, but overusing them can make testing a headache because you’re basically introducing sneaky global state. Use them for things that genuinely should only exist once — like a config object or a single database connection — not as your go-to for everything.

Factory — “The Vending Machine”

You walk up to a vending machine, press B4, and out pops a bag of chips. You didn’t design the chips. You didn’t build the machine’s insides. You just asked for what you wanted, and something else handled the “how.”

php

class NotificationFactory {
    public static function create(string $type): Notification {
        return match ($type) {
            'email' => new EmailNotification(),
            'sms'   => new SmsNotification(),
            default => throw new InvalidArgumentException('Unknown type'),
        };
    }
}

NotificationFactory::create('email')->send('Hello!');

javascript

function createNotification(type) {
  const map = { email: EmailNotification, sms: SmsNotification };
  const NotifClass = map[type];
  if (!NotifClass) throw new Error('Unknown type');
  return new NotifClass();
}

createNotification('sms').send('Hello!');

If you ever need to add a new notification type later, you change it in one place — the factory — instead of hunting down every spot in your codebase where new EmailNotification() was typed manually. Future you will send present you a thank-you card.


Infographic explaining PHP and JavaScript design patterns including Singleton, Factory, Adapter, Decorator, Facade, Observer, and Strategy with simple visual examples.
Seven essential design patterns at a glance. This infographic summarizes the purpose of Singleton, Factory, Adapter, Decorator, Facade, Observer, and Strategy with simple visuals and beginner-friendly explanations

Structural Patterns: Making Things Fit Together

Adapter — “The Plug Converter”

You bring a hairdryer home from your trip abroad and the plug doesn’t fit the wall socket. Rather than rewiring your house (extreme) or throwing out the hairdryer (also extreme), you buy a little adapter. It sits in between, translating one shape into another so the two things that were never meant to talk to each other… talk to each other.

php

class StripeAdapter implements PaymentProcessor {
    public function __construct(private StripeGateway $stripe) {}

    public function pay(float $amount): void {
        $this->stripe->charge((int) ($amount * 100));
    }
}

javascript

class StripeAdapter {
  #stripe;
  constructor(stripe) { this.#stripe = stripe; }
  pay(amount) { this.#stripe.charge(Math.round(amount * 100)); }
}

Your app speaks in dollars. Stripe’s SDK speaks in cents. The adapter is the translator standing awkwardly in the middle, making sure nobody has to change how they naturally talk.

Decorator — “Dressing Up a Snowman”

Start with a plain snowman. Add a hat. Add a scarf. Maybe a slightly judgmental carrot nose. Each addition wraps around what’s already there — the snowman underneath never actually changes, he just gets extra stuff piled on.

php

$coffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
echo $coffee->cost(); // 2.75

javascript

const coffee = withSugar(withMilk(simpleCoffee));
console.log(coffee.cost()); // 2.75

Read it from the inside out: plain coffee, wrapped in milk, wrapped in sugar. Each layer adds a little cost and a little flavor, and you can mix and match combos without writing a separate class for “coffee with milk,” “coffee with sugar,” “coffee with milk and sugar,” and so on into infinity.

Facade — “Just Give Me the Nuggets”

Nobody walks into a restaurant, marches into the kitchen, and personally operates the fryer. You say “chicken nuggets, please,” and an entire complicated system of prep, cooking, and plating happens behind a door you never see.

php

class Facade {
    public function convertVideo(string $file): void {
        $decoded = $this->decoder->decode($file);
        $mixed = $this->mixer->mix($decoded);
        $this->writer->write($mixed, 'output.mp4');
    }
}

One method call. Three complicated things happening quietly behind it. This is exactly what Laravel’s Cache::get() or Mail::send() are doing under the hood — you get a friendly waiter, not a tour of the kitchen.


Behavioral Patterns: How Things Communicate

Observer — “Raising Your Hand for Pizza Day”

A teacher says, “Whoever wants to know when pizza day is, raise your hand.” A bunch of kids raise their hands. Later, the teacher makes one announcement, and every single kid who raised their hand hears it at the same moment — no need to track each kid down individually.

php

$event->on(fn($order) => print("Sending confirmation email"));
$event->on(fn($order) => print("Updating inventory"));
$event->fire(['id' => 101]);

javascript

event.on(order => console.log(`Sending confirmation email for order #${order.id}`));
event.on(order => console.log(`Updating inventory for order #${order.id}`));
event.fire({ id: 101 });

This is the exact logic powering addEventListener in the browser, Node’s EventEmitter, and event systems in Symfony and Laravel. Something happens once, and everyone who cares finds out immediately.

Strategy — “Walk, Bike, or Get a Ride”

The goal is the same every morning: get to school. How you get there can change depending on the day — sometimes you walk, sometimes you bike, sometimes you beg for a ride. Same destination, swappable method.

javascript

const standardShipping = weight => weight * 1.5;
const expressShipping = weight => weight * 3.0;

class Order {
  constructor(strategy) { this.strategy = strategy; }
  shippingCost(weight) { return this.strategy(weight); }
}

console.log(new Order(expressShipping).shippingCost(10)); // 30

The Order class doesn’t care how the cost gets calculated. Hand it a different strategy, and its behavior changes completely without touching a single line inside the class itself.

The Module Pattern — “The Locked Toy Box”

This one’s more of a JavaScript specialty. Imagine a toy box that locks, with exactly one little slot on the front where a toy can pop out if you press the button. The mess inside stays hidden. Only that one slot is available to the outside world.

javascript

// counter.js
let count = 0; // stays private — not exported

export function increment() {
  count += 1;
  return count;
}

Nobody outside this file can reach in and set count to a random number. They can only go through the one door you left open. PHP achieves the same privacy goal, just using class visibility (private/protected) instead of file-level exports.


Where You’ve Probably Already Seen These in the Wild

If any of this felt oddly familiar, it’s because you’ve likely been using these patterns without naming them:

  • Laravel Facades (Cache::get(), Mail::send()) — literal Facade pattern, dressed in framework clothing.
  • Symfony’s EventDispatcher and Laravel Events — Observer pattern doing the heavy lifting.
  • Express middleware in Node — a chain of wrapped handlers, very Decorator-flavored.
  • React’s Higher-Order Components — Decorator again, just with a trendier name.
  • Redux — Observer pattern powering the entire “state changed, notify everyone” mechanism.

So… Do You Need to Memorize All of This?

Not really. Nobody sits down and thinks, “Today I shall implement the Strategy pattern.” What actually happens is you notice a smell in your code — too many if/else branches doing similar things, or five different classes that basically do the same job with tiny variations — and then the pattern name pops into your head as the fix.

Learn the shape of the problem first. The pattern name is just the label you slap on afterward, mostly so you sound impressive in code reviews.

If you want a good next step, try picking one small project — a shopping cart, a login flow, a notification system — and rebuild it using two or three of these patterns together. That’s when it actually clicks, far more than reading about hamsters and snowmen ever will (even though, let’s be honest, the hamster really helped).