Design Patterns in PHP

PHP Design Patterns
Design patterns are reusable solutions to common problems in software design. They provide a template for how to solve a problem in a way that is both effective and adaptable. In PHP, design patterns can help developers write more maintainable and scalable code.

In this article, we will explore some of the most commonly used design patterns in PHP, focusing on Creational, Structural, and Behavioral patterns. We’ll provide explanations and code examples to illustrate each pattern.

Creational Patterns

Creational patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. They can help make a system independent of how its objects are created, composed, and represented. The three creational patterns we’ll discuss are Factory, Singleton, and Builder.

Factory Pattern

The Factory Pattern is used to create objects without specifying the exact class of object that will be created. This is particularly useful when the creation process is complex or when the exact type of the object may vary based on some condition.

Example: Factory Pattern

				
					interface Shape {
    public function draw();
}

class Circle implements Shape {
    public function draw() {
        echo "Drawing a Circle\n";
    }
}

class Square implements Shape {
    public function draw() {
        echo "Drawing a Square\n";
    }
}

class ShapeFactory {
    public function createShape($shapeType) {
        if ($shapeType === 'circle') {
            return new Circle();
        } elseif ($shapeType === 'square') {
            return new Square();
        }
        return null;
    }
}

// Client code
$factory = new ShapeFactory();

$circle = $factory->createShape('circle');
$circle->draw();

$square = $factory->createShape('square');
$square->draw();

				
			

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 when exactly one object is needed to coordinate actions across the system.

Example: Singleton Pattern

				
					class Database {
    private static $instance = null;
    private $connection;

    private function __construct() {
        $this->connection = new PDO('sqlite::memory:');
    }

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

    public function getConnection() {
        return $this->connection;
    }
}

// Client code
$db1 = Database::getInstance();
$db2 = Database::getInstance();

if ($db1 === $db2) {
    echo "Both instances are the same\n";
}

				
			

Builder Pattern

The Builder Pattern is used to construct a complex object step by step. It separates the construction of a complex object from its representation so that the same construction process can create different representations.

Example: Builder Pattern

				
					class House {
    public $walls;
    public $doors;
    public $windows;
    public $roof;

    public function show() {
        echo "House with {$this->walls} walls, {$this->doors} doors, {$this->windows} windows, and a {$this->roof} roof\n";
    }
}

class HouseBuilder {
    private $house;

    public function __construct() {
        $this->house = new House();
    }

    public function buildWalls($walls) {
        $this->house->walls = $walls;
        return $this;
    }

    public function buildDoors($doors) {
        $this->house->doors = $doors;
        return $this;
    }

    public function buildWindows($windows) {
        $this->house->windows = $windows;
        return $this;
    }

    public function buildRoof($roof) {
        $this->house->roof = $roof;
        return $this;
    }

    public function getHouse() {
        return $this->house;
    }
}

// Client code
$builder = new HouseBuilder();
$house = $builder->buildWalls(4)
                 ->buildDoors(1)
                 ->buildWindows(4)
                 ->buildRoof('shingle')
                 ->getHouse();
$house->show();

				
			

Structural Patterns

Structural patterns are concerned with how classes and objects are composed to form larger structures. These patterns help ensure that if one part of a system changes, the entire system doesn’t need to change. We’ll explore Adapter, Decorator, and Facade patterns.

Adapter Pattern

The Adapter Pattern allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces, allowing them to communicate.

Example: Adapter Pattern

				
					interface MediaPlayer {
    public function play($audioType, $fileName);
}

class MP3Player implements MediaPlayer {
    public function play($audioType, $fileName) {
        if ($audioType === 'mp3') {
            echo "Playing mp3 file: $fileName\n";
        } else {
            echo "Invalid media type\n";
        }
    }
}

interface AdvancedMediaPlayer {
    public function playVlc($fileName);
    public function playMp4($fileName);
}

class VlcPlayer implements AdvancedMediaPlayer {
    public function playVlc($fileName) {
        echo "Playing vlc file: $fileName\n";
    }

    public function playMp4($fileName) {
        // Do nothing
    }
}

class Mp4Player implements AdvancedMediaPlayer {
    public function playVlc($fileName) {
        // Do nothing
    }

    public function playMp4($fileName) {
        echo "Playing mp4 file: $fileName\n";
    }
}

class MediaAdapter implements MediaPlayer {
    private $advancedMusicPlayer;

    public function __construct($audioType) {
        if ($audioType === 'vlc') {
            $this->advancedMusicPlayer = new VlcPlayer();
        } elseif ($audioType === 'mp4') {
            $this->advancedMusicPlayer = new Mp4Player();
        }
    }

    public function play($audioType, $fileName) {
        if ($audioType === 'vlc') {
            $this->advancedMusicPlayer->playVlc($fileName);
        } elseif ($audioType === 'mp4') {
            $this->advancedMusicPlayer->playMp4($fileName);
        }
    }
}

// Client code
$audioPlayer = new MediaAdapter('mp4');
$audioPlayer->play('mp4', 'some_song.mp4');

$audioPlayer = new MediaAdapter('vlc');
$audioPlayer->play('vlc', 'another_song.vlc');

				
			

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. This is useful for adhering to the Single Responsibility Principle as it allows functionality to be divided between classes with unique areas of concern.

Example: Decorator Pattern

				
					interface Coffee {
    public function getCost();
    public function getDescription();
}

class SimpleCoffee implements Coffee {
    public function getCost() {
        return 5;
    }

    public function getDescription() {
        return "Simple coffee";
    }
}

class MilkDecorator implements Coffee {
    protected $coffee;

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

    public function getCost() {
        return $this->coffee->getCost() + 2;
    }

    public function getDescription() {
        return $this->coffee->getDescription() . ", milk";
    }
}

class SugarDecorator implements Coffee {
    protected $coffee;

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

    public function getCost() {
        return $this->coffee->getCost() + 1;
    }

    public function getDescription() {
        return $this->coffee->getDescription() . ", sugar";
    }
}

// Client code
$coffee = new SimpleCoffee();
echo $coffee->getDescription() . " costs " . $coffee->getCost() . "\n";

$coffee = new MilkDecorator($coffee);
echo $coffee->getDescription() . " costs " . $coffee->getCost() . "\n";

$coffee = new SugarDecorator($coffee);
echo $coffee->getDescription() . " costs " . $coffee->getCost() . "\n";

				
			

Facade Pattern

The Facade Pattern provides a simplified interface to a complex subsystem. It defines a higher-level interface that makes the subsystem easier to use.

Example: Facade Pattern

				
					class CPU {
    public function freeze() {
        echo "CPU freezing\n";
    }

    public function jump($position) {
        echo "CPU jumping to $position\n";
    }

    public function execute() {
        echo "CPU executing\n";
    }
}

class Memory {
    public function load($position, $data) {
        echo "Loading $data into memory at position $position\n";
    }
}

class HardDrive {
    public function read($lba, $size) {
        echo "Reading $size from LBA $lba\n";
        return "some_data";
    }
}

class ComputerFacade {
    protected $cpu;
    protected $memory;
    protected $hardDrive;

    public function __construct() {
        $this->cpu = new CPU();
        $this->memory = new Memory();
        $this->hardDrive = new HardDrive();
    }

    public function start() {
        $this->cpu->freeze();
        $this->memory->load(0, $this->hardDrive->read(0, 1024));
        $this->cpu->jump(0);
        $this->cpu->execute();
    }
}

// Client code
$computer = new ComputerFacade();
$computer->start();

				
			

Behavioral Patterns

Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects. These patterns characterize the ways in which classes or objects interact and delegate responsibility. We’ll discuss Observer, Strategy, and Command patterns.

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.

Example: Observer Pattern

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

interface Subject {
    public function attach(Observer $observer);
    public function detach(Observer $observer);
    public function notify();
}

class NewsAgency implements Subject {
    private $observers = [];
    private $news;

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

    public function detach(Observer $observer) {
        $this->observers = array_filter($this->observers, function ($o) use ($observer) {
            return $o !== $observer;
        });
    }

    public function notify() {
        foreach ($this->observers as $observer) {
            $observer->update($this->news);
        }
    }

    public function addNews($news) {
        $this->news = $news;
        $this->notify();
    }
}

class NewsReader implements Observer {
    private $name;

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

    public function update($news) {
        echo "$this->name received news: $news\n";
    }
}

// Client code
$agency = new NewsAgency();
$reader1 = new NewsReader('Reader 1');
$reader2 = new NewsReader('Reader 2');

$agency->attach($reader1);
$agency->attach($reader2);

$agency->addNews('Breaking News: PHP Patterns Explained!');
$agency->detach($reader1);
$agency->addNews('Update: More Patterns in PHP');

				
			

Strategy Pattern

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

Example: Strategy Pattern

				
					interface PaymentStrategy {
    public function pay($amount);
}

class CreditCardPayment implements PaymentStrategy {
    public function pay($amount) {
        echo "Paying $amount using Credit Card\n";
    }
}

class PayPalPayment implements PaymentStrategy {
    public function pay($amount) {
        echo "Paying $amount using PayPal\n";
    }
}

class ShoppingCart {
    private $amount;

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

    public function checkout(PaymentStrategy $paymentMethod) {
        $paymentMethod->pay($this->amount);
    }
}

// Client code
$cart = new ShoppingCart(100);
$cart->checkout(new CreditCardPayment());
$cart->checkout(new PayPalPayment());

				
			

Command Pattern

The Command Pattern turns a request into a stand-alone object that contains all information about the request. This transformation lets you parameterize methods with different requests, delay or queue a request’s execution, and support undoable operations.

Example: Command Pattern

				
					interface Command {
    public function execute();
}

class Light {
    public function on() {
        echo "Light is ON\n";
    }

    public function off() {
        echo "Light is OFF\n";
    }
}

class LightOnCommand implements Command {
    private $light;

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

    public function execute() {
        $this->light->on();
    }
}

class LightOffCommand implements Command {
    private $light;

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

    public function execute() {
        $this->light->off();
    }
}

class RemoteControl {
    private $commands = [];

    public function setCommand($button, Command $command) {
        $this->commands[$button] = $command;
    }

    public function pressButton($button) {
        if (isset($this->commands[$button])) {
            $this->commands[$button]->execute();
        } else {
            echo "No command assigned to button $button\n";
        }
    }
}

// Client code
$light = new Light();
$remote = new RemoteControl();
$remote->setCommand('A', new LightOnCommand($light));
$remote->setCommand('B', new LightOffCommand($light));

$remote->pressButton('A');
$remote->pressButton('B');

				
			

Conclusion

Design patterns are powerful tools in a developer’s arsenal. They provide tested, proven development paradigms, making code more robust and easier to maintain. In PHP, understanding and applying these patterns can significantly improve the quality and scalability of your applications. Whether dealing with object creation, structuring systems, or defining interactions, there’s a design pattern that fits the problem. Use them wisely to create clean, efficient, and adaptable code.

Scroll to Top