Common Design Pattern in PHP

Lejiend7
9 min readJan 7, 2023

--

Photo by Christopher Gower on Unsplash

What is Design Pattern ?

Design patterns are tried and tested solutions to common problems that arise in software design. They are reusable solutions that can be applied in different contexts to solve similar problems.

Design patterns are a way of codifying best practices and capturing the collective knowledge and experience of the software development community. They provide a common vocabulary and a set of standardized approaches for solving common problems, which makes it easier for developers to communicate and collaborate.

There are several different types of design patterns:

  • Creational patterns (focus on object creation).
  • Structural patterns (focus on object composition).
  • Behavioral patterns (focus on communication between objects).

Design patterns are not a one-size-fits-all solution and should be applied with care. It is important to understand the problem that the design pattern is trying to solve and how it can be applied in the specific context of the project.

List of common design pattern in PHP

  1. Decorator Pattern
  2. Adapter Pattern
  3. Template Method Pattern
  4. Strategy Pattern
  5. The Chain of Responsibility Pattern
  6. Specification Pattern
  7. Observer Pattern

1.Decorator Pattern

The decorator pattern is a design pattern that allows you to add new behavior to an existing object dynamically, without changing its implementation. It is a flexible alternative to subclassing, which allows you to extend the functionality of an object by wrapping it with a decorator object that adds the new behavior.

Here’s an example of how the decorator pattern might be used in a code framework:

Imagine you have a class called Shape that represents a basic shape and has a draw() method. You want to add the ability to draw shapes with different colors, so you create a ColorDecorator class that wraps a Shape object and adds the ability to set and draw the color of the shape.

Here is the implementation of the Shape and ColorDecorator classes:

class Shape {
public function draw() {
// draw the shape
}
}

class ColorDecorator {
private $shape;
private $color;

public function __construct(Shape $shape, string $color) {
$this->shape = $shape;
$this->color = $color;
}

public function draw() {
// set the color
$this->shape->draw();
// reset the color
}
}

Now, you can use the ColorDecorator class to draw a shape in a specific color like this:

$shape = new Shape();
$coloredShape = new ColorDecorator($shape, 'red');
$coloredShape->draw();

The decorator pattern is useful because it allows you to add new behavior to an object without modifying its implementation, making it more flexible and easier to maintain. It is also a good way to avoid the overhead of subclassing, as it allows you to add behavior on an as-needed basis rather than creating a new subclass for each combination of behaviors.

2. Adapter Pattern

The adapter pattern is a design pattern that allows you to adapt the interface of one class to match the interface expected by another class, allowing classes with incompatible interfaces to work together. It does this by wrapping the adaptee (the class with the incompatible interface) in an adapter class that translates the requests made by the client class into a form that the adaptee can understand.

Here’s an example of how the adapter pattern might be used in a code framework:

Imagine you have a class called LegacyCalculator that performs calculations, but it expects the calculations to be specified as strings and returns the results as strings. You want to use this class in a new application that expects calculations to be specified as objects and returns the results as integers.

To enable the LegacyCalculator class to work with the new application, you can create an adapter class called CalculatorAdapter that translates between the old and new interfaces.

Here is the implementation of the LegacyCalculator and CalculatorAdapter classes:

class LegacyCalculator {
public function calculate(string $expression) {
// perform the calculation and return the result as a string
}
}

class CalculatorAdapter {
private $calculator;

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

public function calculate(Calculation $calculation) {
$expression = $calculation->getExpression();
$result = $this->calculator->calculate($expression);
return (int) $result;
}
}

Now, you can use the CalculatorAdapter class to use the LegacyCalculator class with the new application like this:

$calculator = new LegacyCalculator();
$adapter = new CalculatorAdapter($calculator);
$result = $adapter->calculate($calculation);

The adapter pattern is useful when you want to use an existing class in a new context, but its interface is incompatible with the context. It allows you to reuse the existing class without modifying its code, making it more flexible and easier to maintain.

3. Template Method Pattern

The template method pattern is a design pattern that defines the skeleton of an algorithm as an abstract class, allowing its subclasses to provide concrete behavior. It is a way of defining the steps of an algorithm and allowing subclasses to provide specific implementation details for some or all of the steps.

Here’s an example of how the template method pattern might be used in a code framework:

Imagine you have a class called ShapeDrawer that defines an algorithm for drawing a shape. The algorithm consists of several steps, such as setting the pen color, drawing the outline of the shape, and filling the shape with color.

To allow subclasses to provide specific implementation details for some or all of the steps, you can define the algorithm as a template method that calls abstract methods for each of the steps. The subclasses can then override these abstract methods to provide their own implementation details.

Here is the implementation of the ShapeDrawer class:

abstract class ShapeDrawer {
public function drawShape() {
$this->setPenColor();
$this->drawOutline();
$this->fillShape();
}

abstract protected function setPenColor();
abstract protected function drawOutline();
abstract protected function fillShape();
}

Now, you can create subclasses of ShapeDrawer to provide specific implementation details for each of the steps. For example, the CircleDrawer class might override the setPenColor, drawOutline, and fillShape methods like this:

class CircleDrawer extends ShapeDrawer {
protected function setPenColor() {
// set the pen color to blue
}

protected function drawOutline() {
// draw the outline of a circle
}

protected function fillShape() {
// fill the shape with blue
}
}

4. Strategy Pattern

The strategy pattern is a design pattern that defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows you to define a set of algorithms as a separate class hierarchy and switch between them at runtime, depending on the needs of the application.

Here’s an example of how the strategy pattern might be used in a code framework:

Imagine you have a class called Sorter that sorts a list of items using different sorting algorithms. You want to allow the user to select the sorting algorithm to use, so you create a SortStrategy interface that defines a sort() method and several concrete strategy classes that implement the SortStrategy interface with different sorting algorithms.

Here is the implementation of the SortStrategy interface and some of the concrete strategy classes:

interface SortStrategy {
public function sort(array $items);
}

class BubbleSortStrategy implements SortStrategy {
public function sort(array $items) {
// implement bubble sort algorithm
}
}

class QuickSortStrategy implements SortStrategy {
public function sort(array $items) {
// implement quick sort algorithm
}
}

Now, you can use the Sorter class to sort a list of items using a specific sorting algorithm like this:

$sorter = new Sorter();
$sorter->setSortStrategy(new QuickSortStrategy());
$sortedItems = $sorter->sort($items);

5. The Chain of Responsibility Pattern

The Chain of Responsibility pattern is a design pattern that allows you to pass requests along a chain of objects, avoiding the need for the sender of a request to know which object can handle the request. It consists of a source of command objects and a series of processing objects, each of which can handle a request or pass it along to the next processing object in the chain.

Here’s an example of how the Chain of Responsibility pattern might be used in a code framework:

Imagine you have a class called Request that represents a request for a specific action, and you want to create a system for handling these requests that allows you to add new types of requests and handling logic dynamically.

To implement this system, you can create an Handler interface that defines a handleRequest() method and several concrete handler classes that implement the Handler interface with specific handling logic. You can then link these handlers together in a chain, with each handler having a reference to the next handler in the chain.

Here is the implementation of the Request, Handler interface, and some of the concrete handler classes:

class Request {
private $action;

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

public function getAction() {
return $this->action;
}
}

interface Handler {
public function setNext(Handler $handler): Handler;
public function handleRequest(Request $request);
}

class EmailHandler implements Handler {
private $next;

public function setNext(Handler $handler): Handler {
$this->next = $handler;
return $handler;
}

public function handleRequest(Request $request) {
if ($request->getAction() === 'email') {
// handle email request
} elseif ($this->next) {
$this->next->handleRequest($request);
}
}
}

class PrintHandler implements Handler {
private $next;

public function setNext(Handler $handler): Handler {
$this->next = $handler;
return $handler;
}

public function handleRequest(Request $request) {
if ($request->getAction() === 'print') {
// handle print request
} elseif ($this->next) {
$this->next->handleRequest($request);
}
}
}

Now, you can create a chain of handlers and pass requests through the chain like this:

$emailHandler = new EmailHandler();
$printHandler = new PrintHandler();

$emailHandler->setNext($printHandler);

$emailHandler->handleRequest(new Request('email'));
// the email handler will handle the request

$emailHandler->handleRequest(new Request('print'));
// the print handler will handle the request

6. Specification Pattern

The Specification pattern is a design pattern that allows you to encapsulate a complex business rule or calculation as a self-contained object. It consists of a boolean-valued function that defines a rule or calculation, and a set of classes that use these functions to specify the criteria for filtering or selecting a group of objects.

Here is an example of how the Specification pattern might be used in a code framework to select a group of products based on various criteria:

class Product {
private $price;
private $category;
private $available;

public function __construct(float $price, string $category, bool $available) {
$this->price = $price;
$this->category = $category;
$this->available = $available;
}

public function getPrice() {
return $this->price;
}

public function getCategory() {
return $this->category;
}

public function isAvailable() {
return $this->available;
}
}

interface Specification {
public function isSatisfiedBy(Product $product): bool;
}

class PriceSpecification implements Specification {
private $minPrice;
private $maxPrice;

public function __construct(float $minPrice, float $maxPrice) {
$this->minPrice = $minPrice;
$this->maxPrice = $maxPrice;
}

public function isSatisfiedBy(Product $product): bool {
return $product->getPrice() >= $this->minPrice && $product->getPrice() <= $this->maxPrice;
}
}

class CategorySpecification implements Specification {
private $category;

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

public function isSatisfiedBy(Product $product): bool {
return $product->getCategory() === $this->category;
}
}

class AvailabilitySpecification implements Specification {
public function isSatisfiedBy(Product $product): bool {
return $product->isAvailable();
}
}

class AndSpecification implements Specification {
private $specifications;

public function __construct(Specification ...$specifications) {
$this->specifications = $specifications;
}

public function isSatisfiedBy(Product $product): bool {
foreach ($this->specifications as $specification) {
if (!$specification->isSatisfiedBy($product)) {
return false;
}
}
return true;
}
}

class OrSpecification implements Specification {
private $specifications;

public function __construct(Specification ...$specifications) {
$this->specifications = $specifications;
}

public function isSatisfiedBy(Product $product): bool {
foreach ($this->specifications as $specification) {
if ($specification->isSatisfiedBy($product)) {
return true;
}

7. Observer Pattern

The Observer pattern is a design pattern that allows an object to publish changes to its state to a set of subscribed objects, without the objects being aware of each other or the details of the change. It consists of a subject (also known as the observable) that maintains a list of its dependents (observers), and notifies them automatically of any state changes, usually by calling one of their methods.

Here is an example of how the Observer pattern might be implemented in Laravel:

<?php

namespace App\Observers;
use App\Models\Stock;
class StockObserver
{
public function updating(Stock $stock)
{
// perform some action before the stock price is updated
}
public function updated(Stock $stock)
{
// perform some action after the stock price is updated
}
}

To register the observer, you can add it to the $observers property of the Stock model:

<?php

namespace App\Models;
use App\Observers\StockObserver;
use Illuminate\Database\Eloquent\Model;
class Stock extends Model
{
protected $observers = [
StockObserver::class,
];
// ...
}

Now, whenever the updating or updated events are fired for the Stock model, the corresponding methods on the StockObserver will be called.

--

--