Someone smart once said - Good coders code, great reuse. 
Developers often find themselves solving the same type of problems repeatedly. Reusing code is truly the holy grail of software development. Also, who doesn’t like to read (and write) well-structured code? Enter - design patterns in PHP.

PHP Design patterns have proven to be extremely useful to developers and are a huge problem solver. Following best practices is crucial to writing efficient code. PHP Design patterns is an Object-Oriented Programming (OOP) concept that is now also used in Drupal 9 projects. With Drupal’s adoption of modern PHP and OOP concepts since version 8, design patterns can be leveraged for cleaner and more robust programming. In this article, we will discuss a few commonly used design patterns in PHP and how to use patterns like dependency injections in Drupal. 

Still on Drupal 7? Read this article to find a handy checklist that will help you prepare for a Drupal 9 migration.

Design Patterns in PHP

What are Design Patterns in PHP?

In software engineering, a Design Pattern is a general repeatable solution to commonly occurring problem in software Design. Good Object-oriented designs should be reusable, maintainable and extensible and Design Patterns in PHP could be very helpful in doing that. It does not only help in solving problems, it implies an optimal way addressing common challenges.

Why use PHP Design Patterns

Some of the most significant benefits of implementing design patterns in PHP are:

  • PHP Design patterns help solve repetitive problems faced during development
  • Using design patterns in PHP makes communication between designers and developers more efficient 
  • You can be confident that other developers will understand your code since it follows design patterns
  • Following best practices helps build more robust applications
  • It helps make development faster and easier

Widely used Design Patterns in PHP

Design Patterns can be used in various situations to solve similar problems. There are more than 30 design patterns that can be broadly categorized into three types - Creational, Structural and Behavioral patterns.

Creational Patterns: Design patterns that are used in object creation mechanisms, to create objects that can be decoupled from the system that implemented them.

Structural Patterns: This eases the design by identifying simple ways to realize relationships between entities

Behavioral Patterns: They are used to manage relationships, responsibilities, and algorithms between objects

Factory Pattern

A factory pattern is used to build an object. That’s right — build an object and not create an object. When we build the object, we first create it and then initialize it. Usually, it requires to apply certain logic and perform multiple steps. With that, it makes sense to have all that in one place and re-use it whenever you need to have a new object built in the same way. Fundamentally, that’s the use of the factory pattern.
It’s a great idea to have an interface for our factory and have our code reliant on it and not on a concrete factory.

interface FamilyFactoryInterface { 
    public function create() : Family 
}

Next, implement factory interface with the following class:

class FamilyFactory implements FamilyFactoryInterface { 
    public function create() : Family { 
        $family = new Family(); 
        // initialize your family 
        return $family;
    }
}

Adapter Pattern

In Adapter Design Pattern, a class transforms the interface of one class to another class. In this example we have a TextBook class that has a getTitle() and getAuthor() methods. The client expects a getTitleAndAuthor() method. To "adapt" SimpleBook for demoAdapter we have an adapter class, BookAdapter, which takes in an instance of TextBook, and uses the TextBook getTitle() and getAuthor() methods in its own getTitleAndAuthor method.

<?php

class TextBook {
    private $title;
    private $author;
    function __construct($title_in, $author_in) {
        $this->title  = $title_in;
        $this->author = $author_in;
    }
    function getTitle() {
        return $this->title;
    }
    function getAuthor() {
        return $this->author;
    }
}

class BookAdapter {
    private $book;
    function __construct(TextBook $book_in) {
        $this->book = $book_in;
    }
    function getTitleAndAuthors() {
        return $this->book->getTitle().' by '.$this->book->getAuthor();
    }
}

  // client

  writeln('BEGIN TESTING ADAPTER PATTERN');
  writeln('');

  $book = new TextBook("Gamma, Helm, Johnson, and Vlissides", "Design Patterns");
  $bookAdapter = new BookAdapter($book);
  writeln('Author and Title: '.$bookAdapter->getTitleAndAuthor());
  writeln('');

  writeln('END TESTING ADAPTER PATTERN');

  function writeln($line_in) {
    echo $line_in."<br/>";
  }

?>

PHP Singleton Pattern

In order to limit the instantiation of a class to a single object, a singleton pattern in PHP is used. This can be useful when only one object is needed across the system. It makes sense to allow access to only one instance of a certain class while designing web applications. In order to prevent the explicit creation of objects from the Singleton pattern class, a private constructor is utilized.

<?php
class Singleton
{
    public static function getInstance()
    {
        static $instance = null;
        if (null === $instance) {
            $instance = new static();
        }
        return $instance;
    }
    protected function __construct()
    {
    }
    private function __clone()
    {
    }
    private function __wakeup()
    {
    }
}
class SingletonChild extends Singleton
{
}
$obj = Singleton::getInstance();
var_dump($obj === Singleton::getInstance());
$obj2 = SingletonChild::getInstance();
var_dump($obj2 === Singleton::getInstance());
var_dump($obj2 === SingletonChild::getInstance());
?>

Observer Pattern in PHP

The PHP Observer pattern is used to alert the rest of the system about particular events in certain places.
For instance, if we need to create a Theater to show movies to the critics. We define the class Theater with the current method. Before presenting the movie, we want to send messages to the critics’ cell phones. Then, in the middle of the movie we want to stop the movie for 5 minutes to let the critics have an interval. Finally, after the end of the movie we want to ask the critics to leave their response. So, in the observer pattern for PHP, the observer object only gets notified when status is changed.

This is how code looks like -

class Theater {
 
    public function current(Movie $movie) : void {
     
        $critics = $movie->getCritics();
        $this->message->send($critics, '...');

        $movie->play();

        $movie->pause(5);
        $this->progress->interval($critics)
        $movie->end();

        $this->response->request($critics);
    }
}

Decorator Pattern for PHP

The Decorator pattern is used when you want to alter the character of an object at run-time, and with that, reduce unnecessary inheritances and the number of classes. Well, it can be explained with examples. Let’s say we have classes Sofa and Bed, and they both implement SleeperInterface.

interface SleeprInterface {
    public function sleep() : void;
}
class Sofa implements SleeperInterface {
    public function sleep() : void {
        // sleeps on sofa
    }
}
class Bed implements SleeperInterface {
    public function sleep() : void {
        // sleeps on bed
    }
}

Both the sofas and the beds have the same behavior to sleep. Now, we need other sofas and beds with additional functionality that will tell the users the sleep tracking when they sleep on the sofas or beds. With inheritance we can solve this problem just like this:

class SmartSofa extends Sofa {
    public function sleep() : void {
        parent::sleep();
        $this->sleepHours();
    }
}
class SmartBed extends Window {
    public function sleep() : void {
        parent::sleep();
        $this->sleepHours();
    }
}


Now we have 4 classes in total. However, we could solve this problem with 3 classes only with the Decorator pattern. Here’s how:

class SmartSleeper implements SleeperInterface  {
   
    private $sleeper;
    public function __construct(SleeperInterface $sleeper) {
        $this->sleeper = $sleeper;
    }
   
    public function sleep() : void {
        $this->sleeper->sleep();
        $this->sleepHours();
    }
}
$sofa = new Sofa();
$bed = new Bed();
$smartSofa = new SmartSleeper($sofa);
$smartBed = new SmartSleeper($bed);

Here, we have introduced a new type of a sleeper that acts like a proxy but with an extra functionality on top of it.

Leveraging Design Patterns in Drupal 9

While there are many design patterns already established within Drupal before Drupal 9, there now are many more patterns that were previously unavailable. Some of these new patterns completely replace older ones, while others introduce some new features to Drupal 9.
Design patterns used in Drupal 9 include:

  • Object-Oriented Programming Pattern (OOP)
  • Dependency Injections
  • Factory Pattern
  • Singleton Pattern

OOP is not really a single pattern, but a completely radical way of conceptualizing and structuring code that goes way beyond just design patterns. It is the basis for a lot of popular software design patterns in use today, including those used in Drupal 9. It was introduced in Drupal 7, but it was not used extensively, and it was not required. The situation in Drupal 9 is now different, it is used widely, and is required.

Dependency Injections

Dependency injection is a software design pattern that would allow you to remove hard-coded dependencies and also make it possible to change them either on runtime or at compile time. Adding dependency injection is easy and it does not meddle with your existing code. Drupal 8 introduced the concept of services in order to decouple the reusable functionalities. core.services.yml is an example of dependency injection in Drupal 9. We have already discussed Factory Pattern and Singleton Pattern in PHP previously.
 
Currently, in Drupal, dependency injection is the preferred method for accessing and using services and should be used whenever possible. Instead of calling out to the global services container, services are rather passed as arguments to a constructor or injected via setter methods. Explicitly passing in the services that an object depends on is called dependency injection. In several cases, dependencies are passed explicitly in class constructors. 

Check out this page to find all the available services in Drupal core. You can read more about services in the Drupal documentation

Let us consider ‘entity_type.manager’ service as an example to get the title for the node with ID=1. In order to inject it into our custom service, we just have to take the service name and pass it as an argument in the my_module_name.services.yml file as shown below:

my_module_name.services.yml

services:
    my_module_name.helper:
    class: Drupal\my_module_name\MyModuleHelper
    arguments: [‘@entity_type.manager’]

and then in our service class we just have to get the service in the __construct method and store it in a variable like this:

MyModuleHelper.php

<?php

namespace Drupal\my_module_name;

use Drupal\Core\Entity\EntityTypeManagerInterface;

/**
 * MyModuleHelper is a simple example of a Drupal 9 service.
 */
class MyModuleHelper {

/**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Part of the DependencyInjection magic happening here.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface
      $entity_type_manager
   *   The entity type manager.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * Returns a title for node_id = 1.
   */
  public function getFirstNodeTitle() {
    $node =
    $this->entityTypeManager->getStorage(‘node’)->load(1);
    
    return $node->getTitle();
  }

}

and then we could use the enitity type manager service and get the title of the node with nid=1 in getFirstNodeTitle method.

Big thanks to Ankitha Shetty for her insights that helped us update the article.

Contact us

LET'S DISCUSS YOUR IDEAS. 
WE'D LOVE TO HEAR FROM YOU.

CONTACT US