Design Patterns in PHP

Introduction to Design Patterns

Design patterns are tried-and-tested solutions to common programming problems. They help to write clean, maintainable, and scalable code. PHP developers frequently use several design patterns, divided into three main categories:

  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns

 

1.) Creational Patterns

 

i.) Singleton Pattern

The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. This is useful, for example, when managing a single database connection.


class Singleton {
    private static $instance = null;

    private function __construct() { }

    public static function getInstance() {
        if (self::$instance == null) {
            self::$instance = new Singleton();
        }
        return self::$instance;
    }
}

// Usage
$singleton = Singleton::getInstance();

Real-world example: A logging class that writes log entries to a file. Multiple instances could lead to conflicting log entries.

ii.) Factory Pattern

The Factory pattern provides a way to create objects without specifying the exact class of the object that will be created. This is useful when the creation logic is complex or when the type of object depends on the input.


interface Product {
    public function getName();
}

class ConcreteProductA implements Product {
    public function getName() {
        return "Product A";
    }
}

class ConcreteProductB implements Product {
    public function getName() {
        return "Product B";
    }
}

class Factory {
    public static function createProduct($type) {
        if ($type === 'A') {
            return new ConcreteProductA();
        } elseif ($type === 'B') {
            return new ConcreteProductB();
        }
        throw new Exception("Invalid product type");
    }
}

// Usage
$product = Factory::createProduct('A');
echo $product->getName();

Real-world example: A user management system where different types of users (admin, editor, viewer) need to be created based on input.

2.) Structural Patterns

i.) Adapter Pattern

The Adapter pattern allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces.


interface Target {
    public function request();
}

class Adaptee {
    public function specificRequest() {
        return "Specific request.";
    }
}

class Adapter implements Target {
    private $adaptee;

    public function __construct(Adaptee $adaptee) {
        $this->adaptee = $adaptee;
    }

    public function request() {
        return $this->adaptee->specificRequest();
    }
}

// Usage
$adaptee = new Adaptee();
$adapter = new Adapter($adaptee);
echo $adapter->request();

Real-world example: Integrating a payment gateway where the payment processor’s API differs from your application’s standard payment interface.

ii.) Decorator Pattern

The Decorator pattern allows behavior to be added to individual objects, dynamically, without affecting the behavior of other objects from the same class.


interface Component {
    public function operation();
}

class ConcreteComponent implements Component {
    public function operation() {
        return "ConcreteComponent";
    }
}

class Decorator implements Component {
    protected $component;

    public function __construct(Component $component) {
        $this->component = $component;
    }

    public function operation() {
        return $this->component->operation();
    }
}

class ConcreteDecoratorA extends Decorator {
    public function operation() {
        return "ConcreteDecoratorA(" . parent::operation() . ")";
    }
}

class ConcreteDecoratorB extends Decorator {
    public function operation() {
        return "ConcreteDecoratorB(" . parent::operation() . ")";
    }
}

// Usage
$component = new ConcreteComponent();
$decoratorA = new ConcreteDecoratorA($component);
$decoratorB = new ConcreteDecoratorB($decoratorA);
echo $decoratorB->operation();

Real-world example: Adding additional features to a user interface component, such as a button, without modifying the button’s code.

3.) Behavioral Patterns

i.) Observer Pattern

The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.


interface Observer {
    public function update($state);
}

class ConcreteObserver implements Observer {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function update($state) {
        echo "Observer {$this->name} has been notified of state change to {$state}.
";
    }
}

class Subject {
    private $observers = [];
    private $state;

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

    public function setState($state) {
        $this->state = $state;
        $this->notifyAllObservers();
    }

    private function notifyAllObservers() {
        foreach ($this->observers as $observer) {
            $observer->update($this->state);
        }
    }
}

// Usage
$subject = new Subject();
$observer1 = new ConcreteObserver('Observer1');
$observer2 = new ConcreteObserver('Observer2');

$subject.attach($observer1);
$subject.attach($observer2);

$subject.setState('new state');

Real-world example: A newsletter system where subscribers get notified when a new issue is published.

ii.) Strategy Pattern

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.


interface Strategy {
    public function doOperation($num1, $num2);
}

class OperationAdd implements Strategy {
    public function doOperation($num1, $num2) {
        return $num1 + $num2;
    }
}

class OperationSubtract implements Strategy {
    public function doOperation($num1, $num2) {
        return $num1 - $num2;
    }
}

class Context {
    private $strategy;

    public function __construct(Strategy $strategy) {
        $this->strategy = $strategy;
    }

    public function executeStrategy($num1, $num2) {
        return $this->strategy->doOperation($num1, $num2);
    }
}

// Usage
$context = new Context(new OperationAdd());
echo "10 + 5 = " . $context->executeStrategy(10, 5) . "
";

$context = new Context(new OperationSubtract());
echo "10 - 5 = " . $context->executeStrategy(10, 5);

Real-world example: A shopping cart where different discount strategies (e.g., percentage off, fixed amount off) can be applied.

Conclusion

Design patterns provide powerful solutions to common problems in software design. By using them, you can make your PHP applications more robust, maintainable, and scalable. The examples provided here give a glimpse into how these patterns can be implemented in PHP to solve real-world problems.