Web Info and IT Lessons Blog...

Tuesday 13 July 2021

Design Patterns in PHP


There are a few dozen of design patterns in PHP. The most beneficial and commonly used patterns are discussed below in detail.

Simple Factory 

Factory pattern or Simple Factory is one of the most commonly used design patterns in PHP. Factory pattern is used to define a runtime interface for creating an object.

It is called a factory because it creates various types of objects without necessarily knowing what kind of object it creates or how to create it. Factory pattern is generally used in the following situations:

  • A class cannot tell the type of objects it needs to create beforehand. 
  • A class requires its subclasses to specify the objects it creates. 
  • To localize the logic of instantiating a complex object. 

Anatomy of Simple or Static Factory: 

  • A factory class must have a static method, this is called a factory method. 
  • The factory method must return a class instance. 
  • Only one object should be created and returned at a time.  

// Factory class
class AutomobileFactory{ 

    // Factory method
    public static function create($make, $model){ 

        // Class instance returned
        return new Automobile($make, $model);
    }
}

class Automobile{
    private $automobileType;
    private $automobileCompany;

    public function __construct($type, $company)
    {
        $this->automobileType = $type;
        $this->automobileCompany = $company;
    }
    
    public function getAutomobile()
    {
        return $this->automobileType . ' ' . $this->automobileCompany;
    }
}

$toyota = AutomobileFactory::create('Car', 'Toyota');
print_r($toyota->getAutomobile());

The above class i.e AutomobileFactory returns an instance of a product class i.e Automobile inside the factory method i.e ::create( ).

Factory Method 

Factory method is very close to a simple or static factory. Simple Factory provides an interface to create objects, while the Factory method does the same thing but in addition it allows a subclass to make a decision on which class to instantiate.

In factory method, instead of having a single factory, we have multiple factories. This pattern is useful when we have to deal with a large number of product classes and want group them. Like for example, instead of having a single Automobile class that is used for every type of automobile in the world, we can have multiple classes for automobile groups like HeavyAutomobileFactory, LightAutomobileFactory etc

Anatomy of Factory Method: 

  • A factory class must have a factory method which will return a class instance.
  • A factory class must implement a factory interface.
  • A product class is the one whose object is returned by the factory class and it must implement a product interface or an abstract class.

// Factory interface
interface AutomobileFactory 
{
    public static function factory($vehicle);
}

// Factory class
class HeavyAutomobileFactory implements AutomobileFactory
{

    // Factory method
    public static function factory($automobile) 
    {
     switch ($automobile) {
      case 'Truck':

          // Instance of product class
          return new $automobile;
          break;
        }
    }
}

// Factory class
class LightAutomobileFactory implements AutomobileFactory
{

    // Factory method
    public static function factory($automobile) 
    {
     switch ($automobile) {
      case 'Car':

          // Instance of product class
          return new $automobile;
          break;
        }
    }
}

// Product interface or abstract class
abstract class AutomobileAbstract 
{
    protected $name;
 
    public function getName() {
        return $this->name;
    }
}

// Product interface or abstract class
class Truck extends AutomobileAbstract 
{
    protected $name = 'Truck';
}

// Product class
class Car extends AutomobileAbstract 
{
    protected $name = 'Car';
}

$truck = HeavyAutomobileFactory::factory('Truck'); 
echo $truck->getName();

In the above code, the interface AutomobileFactory is just a template for the factory classes i.e HeavyAutomobileFactory and LightAutomobileFactory. The factory classes HeavyAutomobileFactory and LightAutomobileFactory return an instance of the product class (i.e Truck or Car) in the factory method.

One thing to be noted here is that both factories can not be used interchangeably. HeavyAutomobileFactory is meant to make heavy vehicles and LightAutomobileFactory is meant to make light vehicles. LightAutomobileFactory can not make a Truck and similarly HeavyAutomobileFactory can not make a car.

The product classes Car and Truck extends an abstract class i.e AutomobileAbstract making them polymorphic.

Abstract Factory

Abstract factory is relatively more complex to understand than the other factory patterns discussed above.

Anatomy of Abstract Factory:

  • Each product factory must extend the same abstract class or implement the same interface.
  • Each product factory must have multiple methods, one for each class it wants to initialise.
  •  Each product factory class must have the same methods. 

// This is factory interface
abstract class AbstractAutomobileFactory
{
    abstract public function HeavyAutomobile();
    abstract public function LightAutomobile();
}

/* This product factory can create all types of automobiles */

class AmericanAutomobileFactory extends AbstractAutomobileFactory
{
    public function HeavyAutomobile()
    {
        return new AmericanHeavyAutomobile();
    }
 
    public function LightAutomobile()
    {
        return new AmericanLightAutomobile();
    }
}

/* This product factory can create all types of automobiles */

class CanadianAutomobileFactory extends AbstractAutomobileFactory
{
 public function HeavyAutomobile()
    {
        return new CanadianHeavyAutomobile();
    }

    public function LightAutomobile()
    {
        return new CanadianLightAutomobile();
    }
}

abstract class AbstractHeavyAutomobile
{
    abstract public function getHeavyAutomobile();
}

abstract class AbstractLightAutomobile
{
    abstract public function getLightAutomobile();
}

/* These are all the product classes below */

class AmericanHeavyAutomobile extends AbstractHeavyAutomobile
{
    public function getHeavyAutomobile()
    {
        return 'This is an american heavy automobile.';
    }
}

class AmericanLightAutomobile extends AbstractLightAutomobile
{
    public function getLightAutomobile()
    {
        return 'This is an american light automobile.';
    }
}

class CanadianHeavyAutomobile extends AbstractHeavyAutomobile
{
    public function getHeavyAutomobile()
    {
        return 'This is a canadian heavy automobile.';
    }
}

class CanadianLightAutomobile extends AbstractLightAutomobile
{
    public function getLightAutomobile()
    {
        return 'This is a canadian light automobile.';
    }
}

$canadianAutomobile = new CanadianAutomobileFactory();

$canadianHeavyAutomobile = $canadianAutomobile->HeavyAutomobile();

echo $canadianHeavyAutomobile->getHeavyAutomobile();

In the above code, the abstract class i.e AbstractAutomobileFactory is a basic template for it's child classes. All of it's child classes must implement the methods declared in it i.e HeavyAutomobile and LightAutomobile.

Then we have two factory classes i.e AmericanAutomobileFactory and CanadianAutomobileFactory, both inheriting the same abstract class i.e AbstractAutomobileFactory. So this means both factory classes have to implement the same methods of parent abstract class AbstractAutomobileFactory and return an object of the product class (CanadianHeavyAutomobile etc).

Singleton Pattern

When designing web applications, it makes sense conceptually and architecturally to allow access to one and only one instance of a particular class. It is important when we want to ensure only one instance of a class for the entire request lifecycle in a web application.

Anatomy of Singleton Pattern:

  • A private constructor is used to prevent direct creation of objects from the class.
  • Expensive process like database connection is performed inside the private constructor.
  • An object of class is only created if it is not already created usign a static method. 

class ConnectDb {
  private static $instance = null;
  private $conn;
  
  private $host = 'localhost';
  private $user = 'db user-name';
  private $pass = 'db password';
  private $name = 'db name';
   
  private function __construct()
  {
    $this->conn = new PDO("mysql:host={$this->host};
    dbname={$this->name}", $this->user,$this->pass,
    array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'"));
  }
  
  public static function getInstance()
  {
    if(!self::$instance)
    {
      self::$instance = new ConnectDb();
    }
   
    return self::$instance;
  }
  
  public function getConnection()
  {
    return $this->conn;
  }
}

$instance = ConnectDb::getInstance();
$conn = $instance->getConnection();

Singleton pattern solve the problems like having multiple database connections by creating multiple objects of database connection class. Creating multiple connections also slows down the system as each new connection costs time. It is good to use in cases where we want to save the system resources by restricting the number of instances of a class.

One of the major drawback of this pattern is that it is considered an anti-spam because it creates global variables that can be accessed and changed from anywhere in the code.

Strategy Pattern

Strategy pattern is basically a behavioral design pattern. In strategy pattern we encapsulate specific algorithms, allowing the client class (which is responsible for instantiating a particular algorithm) to have no knowledge of the actual implementation.

The importance of strategy pattern is taken into consideration when we need to choose between similar classes that are different only in their implementation. Like for example we have several ways to load an array data i.e serialize it or json encode it or load raw data. All the three methods does the same thing i.e load array data but are different in their implementation.

The first thing we need to do in strategy pattern is to create an interface that the classes can implement and then encapsulate startegies in the classes implementing the interface.

interface OutputData
{
    public function load();
}

class SerializedArray implements OutputData
{
    public function load()
    {
        return serialize($arrayData);
    }
}

class JsonString implements OutputData
{
    public function load()
    {
        return json_encode($arrayData);
    }
}

class ArrayData implements OutputData
{
    public function load()
    {
        return $arrayData;
    }
}

Now let us say we have a client that uses one of our algorithms from the above pattern in his client class given below.

class Client
{
    private $output;

    public function setOutput(OutputData $outputType)
    {
        $this->output = $outputType;
    }

    public function loadOutput()
    {
        return $this->output->load();
    }
}

The client class above has a private property i.e $output which must be set at runtime and must be of the type OutputData. This means that it has to be an object of a class implementing the interface OutputData.

Now let us see how to call load method from the Client class at run time.

$client = new Client();

$client->setOutput(new JsonString());

$data = $client->loadOutput();

No comments:

Post a Comment