Milind Daraniya

Design Patterns in PHP: Simple Guide with Real Examples for Beginners and Junior Developers

Published June 10th, 2026 19 min read

In our earlier discussions, we talked about SOLID, SDLC, and PSR. Those topics help us write better code, follow proper process, and keep our project clean and professional.

Now the next step is Design Patterns.

Design patterns are very important in real software development. They help us solve common programming problems in a smart and reusable way. When you understand design patterns, your PHP code becomes more flexible, readable, and easier to maintain.

In simple words:

A design pattern is a proven way to solve a common software problem.

It is not a framework.
It is not a library.
It is not copy-paste code.

It is a smart coding approach that experienced developers use again and again.


What is a Design Pattern?

A design pattern is like a recipe.

When you want to cook something, you do not start from zero every time. You follow a known method. In the same way, when we solve software problems, we can use known patterns.

For example:

  • If you need one common object for the whole project, you may use Singleton
  • If you need to create objects based on a condition, you may use Factory
  • If you need to switch behavior dynamically, you may use Strategy
  • If you need one action to inform many other parts, you may use Observer

These patterns save time and improve code quality.


Why Design Patterns Matter

A lot of beginners write code that works, but later it becomes hard to change.

That usually happens when code is too tightly coupled, too long, or too mixed together.

Design patterns help us:

  • keep code organized
  • reduce duplication
  • make code easier to extend
  • improve readability
  • separate responsibilities properly
  • make testing easier

As a senior developer, I always think like this:

Code should not only work today. It should also be easy to change tomorrow.

That is where design patterns help.


Types of Design Patterns

Design patterns are usually grouped into three main categories:

1. Creational Patterns

These patterns help us create objects in a better way.
Example: Factory, Singleton

2. Structural Patterns

These patterns help us build class structure and object relationships.
Example: Adapter, Decorator

3. Behavioral Patterns

These patterns help us manage how objects communicate and behave.
Example: Strategy, Observer

In this article, I will explain the most practical patterns with simple PHP examples that a junior developer or student can understand easily.


1) Singleton Pattern

Meaning

Singleton means only one instance of a class should exist in the whole application.

This pattern is useful when you want one shared object, like:

  • configuration
  • logger
  • database connection manager
  • app settings

But one important thing: Singleton should be used carefully. Do not use it everywhere. Use it only when it really makes sense.


Simple Example

 
<?php

class Config
{
    private static ?Config $instance = null;

    private array $settings = [];

    // Private constructor prevents direct object creation
    private function __construct()
    {
        $this->settings = [
            'app_name' => 'My PHP App',
            'version' => '1.0.0'
        ];
    }

    // This method returns the same single instance every time
    public static function getInstance(): Config
    {
        if (self::$instance === null) {
            self::$instance = new Config();
        }

        return self::$instance;
    }

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

// Use the singleton
$config1 = Config::getInstance();
$config2 = Config::getInstance();

echo $config1->get('app_name') . PHP_EOL;
echo $config2->get('version') . PHP_EOL;

// Both are same object
var_dump($config1 === $config2);
 

Explanation

Here:

  • private __construct() stops direct object creation
  • getInstance() gives the only object
  • the same instance is reused everywhere

So if different parts of the application need the same config data, they can use the same object.


When to use

Use Singleton when:

  • one object should exist only once
  • you need a global shared access point
  • the object represents application-wide state

Do not use Singleton just because it looks advanced.


2) Factory Pattern

Meaning

Factory pattern is used when we want to create objects without directly using new everywhere.

Instead of asking the code to know every class name, we let a factory decide which object should be created.

This is very useful when object creation depends on a condition.

For example:

  • email notification
  • SMS notification
  • WhatsApp notification

Simple Example

 
<?php

interface NotificationInterface
{
    public function send(string $message): void;
}

class EmailNotification implements NotificationInterface
{
    public function send(string $message): void
    {
        echo "Sending Email: " . $message . PHP_EOL;
    }
}

class SmsNotification implements NotificationInterface
{
    public function send(string $message): void
    {
        echo "Sending SMS: " . $message . PHP_EOL;
    }
}

class NotificationFactory
{
    public static function make(string $type): NotificationInterface
    {
        return match ($type) {
            'email' => new EmailNotification(),
            'sms'   => new SmsNotification(),
            default  => throw new Exception("Unsupported notification type"),
        };
    }
}

// Client code
$notification = NotificationFactory::make('email');
$notification->send('Your order has been placed.');

$notification2 = NotificationFactory::make('sms');
$notification2->send('Your OTP is 123456.');
 

Explanation

In this example:

  • NotificationInterface defines a common behavior
  • EmailNotification and SmsNotification follow that contract
  • NotificationFactory creates the correct object
  • main code does not need to know internal class details

This makes the code cleaner and easier to extend.


Why Factory is useful

If tomorrow you add:

  • WhatsAppNotification
  • PushNotification
  • TelegramNotification

you only update the factory and add a new class.

That is much better than writing if and switch logic everywhere.


3) Strategy Pattern

Meaning

Strategy pattern is used when you want to change behavior at runtime.

In simple words:

Same task, different methods.

For example, payment can be done by:

  • credit card
  • UPI
  • cash on delivery
  • wallet

The main process stays the same, but the payment method changes.


Simple Example

 
<?php

interface PaymentMethodInterface
{
    public function pay(float $amount): void;
}

class CardPayment implements PaymentMethodInterface
{
    public function pay(float $amount): void
    {
        echo "Paid $" . $amount . " using Card" . PHP_EOL;
    }
}

class UpiPayment implements PaymentMethodInterface
{
    public function pay(float $amount): void
    {
        echo "Paid $" . $amount . " using UPI" . PHP_EOL;
    }
}

class CashPayment implements PaymentMethodInterface
{
    public function pay(float $amount): void
    {
        echo "Paid $" . $amount . " using Cash" . PHP_EOL;
    }
}

class PaymentProcessor
{
    public function __construct(private PaymentMethodInterface $method)
    {
    }

    public function process(float $amount): void
    {
        $this->method->pay($amount);
    }
}

// Use different strategies
$cardPayment = new PaymentProcessor(new CardPayment());
$cardPayment->process(1500);

$upiPayment = new PaymentProcessor(new UpiPayment());
$upiPayment->process(999);

$cashPayment = new PaymentProcessor(new CashPayment());
$cashPayment->process(500);
 

Explanation

Here:

  • PaymentMethodInterface is the common contract
  • each payment class is one strategy
  • PaymentProcessor does not care which payment type is used

This is a very clean design.


Why Strategy is powerful

Without Strategy, people usually write big if-else blocks like this:

  • if payment is card
  • else if payment is UPI
  • else if payment is cash

That works for small code, but later it becomes messy.

Strategy removes that mess and keeps each behavior in its own class.


4) Observer Pattern

Meaning

Observer pattern is used when one object changes and many other objects need to react.

A good real-world example is order placement.

When an order is placed, many things may happen:

  • send email
  • send SMS
  • update stock
  • write log
  • notify admin

Instead of putting all logic inside one class, we can notify observers.


Simple Example

 
<?php

interface ObserverInterface
{
    public function update(string $event): void;
}

class EmailObserver implements ObserverInterface
{
    public function update(string $event): void
    {
        echo "Email notification sent for event: " . $event . PHP_EOL;
    }
}

class SmsObserver implements ObserverInterface
{
    public function update(string $event): void
    {
        echo "SMS notification sent for event: " . $event . PHP_EOL;
    }
}

class OrderService
{
    private array $observers = [];

    public function attach(ObserverInterface $observer): void
    {
        $this->observers[] = $observer;
    }

    public function placeOrder(string $orderId): void
    {
        echo "Order placed: " . $orderId . PHP_EOL;

        $this->notify("order_placed");
    }

    private function notify(string $event): void
    {
        foreach ($this->observers as $observer) {
            $observer->update($event);
        }
    }
}

// Create order service
$orderService = new OrderService();

// Attach observers
$orderService->attach(new EmailObserver());
$orderService->attach(new SmsObserver());

// Place order
$orderService->placeOrder("ORD-1001");
 

Explanation

Here:

  • OrderService is the main subject
  • EmailObserver and SmsObserver listen for events
  • when order is placed, all observers get notified

This keeps the order system clean.


Why Observer is useful

It helps us avoid tight coupling.

The order system should not know too much about email or SMS details.

It should only notify interested parts.

That is a very good real-world design.


5) Adapter Pattern

Meaning

Adapter pattern helps two incompatible parts work together.

Imagine you have old code and new code, but their interfaces do not match. Adapter solves this by wrapping one class into another shape.

This is common in real projects when integrating:

  • old APIs
  • payment gateways
  • third-party services

Simple Example

 
<?php

class OldPaymentGateway
{
    public function makePayment(int $rupees): void
    {
        echo "Paid Rs. " . $rupees . " using old gateway" . PHP_EOL;
    }
}

interface NewPaymentInterface
{
    public function pay(float $amount): void;
}

class OldPaymentAdapter implements NewPaymentInterface
{
    public function __construct(private OldPaymentGateway $gateway)
    {
    }

    public function pay(float $amount): void
    {
        // Convert float amount to integer rupees
        $this->gateway->makePayment((int) $amount);
    }
}

class CheckoutService
{
    public function __construct(private NewPaymentInterface $payment)
    {
    }

    public function checkout(float $amount): void
    {
        $this->payment->pay($amount);
    }
}

$adapter = new OldPaymentAdapter(new OldPaymentGateway());
$checkout = new CheckoutService($adapter);

$checkout->checkout(499.99);
 

Explanation

Here:

  • old gateway has one method
  • new code expects another interface
  • adapter connects both

This is very practical when you cannot change old code but still need to use it.


How I think about Design Patterns in real projects

As a developer, I do not use design patterns just to show knowledge.

I use them only when they solve a real problem.

That is the correct way.

A junior developer often asks:

“Which pattern should I use?”

My answer is:

First understand the problem. Then choose the pattern.

Do not force a pattern into every class.

Patterns are tools, not rules.


Common mistakes beginners make

1. Using patterns too early

Do not add design patterns just because they sound smart.

2. Making code too complex

A simple if-else is fine when the problem is simple.

3. Mixing responsibilities

One class should not do everything.

4. Wrong pattern selection

Factory and Strategy look similar sometimes, but they solve different problems.

5. Not understanding the business need

Always understand the actual use case first.


Easy Summary of Important Patterns

Singleton

One object only.

Factory

Create objects in a smart way.

Strategy

Change behavior at runtime.

Observer

One event notifies many listeners.

Adapter

Make incompatible code work together.


Final Thoughts

Design patterns are one of the most useful topics in software development.

They help us write code that is:

  • cleaner
  • easier to maintain
  • easier to extend
  • easier to test
  • easier for team members to understand

If you are a junior developer or student, do not try to memorize every pattern at once.

First understand the problem each pattern solves.

Then practice small examples like the ones above.

That is the best way to learn.

And if you are writing PHP professionally, design patterns will help you build better software for the long run.