Design Patterns

Factory

In this pattern, the Factory class is responsible for instantiating the defined models. If in future you would like to exchange the model or change the way the model is instantiated

Example:

class Book { private $isbn; private $title; private $author;

public function \_\_construct($isbn, $title, $author)
{
    $this->isbn = $isbn;
    $this->title = $title;
    $this->author = $author;
}

}

class BookFactory { public static function create($isbn, $title, $author) { return new Book($isbn, $title, $author); } }

Adapter

The adapter pattern adjusts the interface of one class to match that of another.

Drawback:

  • You’re just hiding a bad design

class NotificationAdapter { protected $username = ‘'; protected $password = ‘';

public function __construct($username, $password) { $this->username = $username; $this->password = $password; }

public function send($to, $from, $body, $subject = ‘') { if (’’ == $subject) { return $this->sendSMS($to, $from, $body); } else { return $this->sendEmail($to, from, $body, $subject); } }

protected function sendSMS($to, $from, $body) { echo ‘Sending SMS Implementation …'; }

protected function sendEmail($to, $from, $body, $subject) { echo ‘Sending Email Implementation …'; } }

$notificationAdapter = new NotificationAdapter();

$notificationAdapter->send(‘test@test.com’, ‘me@test.com’, ‘hello!', ‘test message’);

Active Record

Database table or view will be mapped into an object.

Drawbacks:
* objects are tightly coupled to the database schema
* objects are tightly coupled to the database itself

Example:

class User { protected $connection = null;

public function __construct() { $this->connection = new PDO(“mysql:host=localhost;dbname=development”, ‘root’, ‘root’); }

public function load($id) { $sql = ‘SELECT * FROM users WHERE user_id=’ . (int)$id; $result = $this->connection->query($sql); $row = $result->fetch(PDO::FETCH_ASSOC);

  foreach ($row as $column => $value) {
     $this->column = $value;
  }

} }

$user = new User(); $user->load(2);

print_r($user);

Observer

In this design pattern one object (subject) notifies its dependent objects (observers) about any state changes.

Decorator

Decorator allows you to add specific behaviors to the instances of a class instead of attaching them to the object itself.

Drawbacks:

  • testing can be hard

Repository

Provides a more object-oriented view of the persistence layer. It encapsulates the set of objects persisted in a data store and the operations performed over them. Supports the objective of clean separation between the domain and data mapping layers.

Example:

class Order { /** * @var int */ private $id; /** * @var string */ private $name; /** * @var DateTime */ private $date;

public function __construct(int $id, string $name, DateTime $date) { $this->id = $id; $this->name = $name; $this->date = $date; }

public static function fromState(array $state): Order { return new self($state[‘id’], $state[‘name’], $state[‘date’]); }

/** * @return int */ public function getId(): int { return $this->id; }

/** * @return string */ public function getName(): string { return $this->name; }

/** * @return DateTime */ public function getDate(): DateTime { return $this->date; } }

interface Persistence { public function generateId();

public function persist(array $data);

public function retrieve(int $id);

public function delete(int $id); }

class MemoryPersistence implements Persistence {

private $data = []; public $lastId = 0;

public function generateId() { $this->lastId++; return $this->lastId; }

public function persist(array $data) { $this->data[$this->lastId] = $data; }

public function retrieve(int $id) { if (!isset($data[$id])) { throw new OutOfBoundsException(‘Cannot find the requested record!'); } return $this->data[$id]; }

public function delete(int $id) { if (!isset($data[$id])) { throw new OutOfBoundsException(‘Cannot find the requested record!'); } unset($this->data[$id]); }

}

class OrderRepository { /** * @var Persistence */ private $persistence;

/** * OrderRepository constructor. */ public function __construct(Persistence $persistence) { $this->persistence = $persistence; }

public function generateId(): integer { return $this->persistence->generateId(); }

public function findById(integer $id) { $arrayData = $this->persistence->retrieve($id); return Order::fromState($arrayData); }

public function save(Order $order) { $this->persistence->persist([ ‘id’ => $order->getId(), ‘name’ => $order->getName(), ‘date’ => $order->getDate(), ]); } }

$orderRepository = new OrderRepository(new MemoryPersistence());

$order = Order::fromState([ ‘id'=>12, ‘name'=>'Flowers Order’, ‘date'=>new DateTime() ]);

$orderRepository->save($order); $orderInPersistence = $orderRepository->findById(12);

print_r($order); print_r($orderInPersistence);

Service Locator

This is considered as an anti-pattern.

With service locators services are usually defined globally throughout the app and are instantiated to be used as dependency wherever required. According to symfony docs: “Service locators are a design pattern that encapsulates the processes involved in obtaining a service using a central registry known as service locator”.

Singleton

Singleton pattern means allowing access to a single instance of defined class. For example, Mailer class could be reused several times throughout your application to send emails. It doesn’t have to be instantiated several times and you should be able to access the same instance once you need to use the Mailer class. The same applies to the database class. You would like to use existing database connection instead of constructing and creating a spare connection to your database every time you want to execute a query.

Though singleton pattern guarantee that class has only one instance and can be easily accessed a lot of developers consider it as anti-pattern. Singleton pattern may introduce global state in application, unnecessary restrictions and make your code easier to maintain and reuse. Writing tests can be also more complicated.

Strategy

Another name for this pattern is Policy pattern. In this pattern families of Algorithms are grouped together. This pattern is useful in particularly when several algorithms can perform the same operation and we would like the application to pick up the appropriate algorithm based on specific attributes.

Interfaces serve by providing contracts which must be obeyed by any new concrete implementation.

Example:
In this example there are 2 ways to ship an item: cargo or freight. Based on user’s choice of shipping type the order price will vary.

interface ShippingStrategy { public function getPrice(); }

class ShipByFreight implements ShippingStrategy { public function getPrice() { return 1000; } }

class ShipByCargo implements ShippingStrategy { public function getPrice() { return 2000; } }

class Order {

private $itemCode;
private $itemName;
private $shippingType;

/\*\*
 \* Order constructor.
 \*/
public function \_\_construct($itemCode, $itemName, $shippingType)
{
    $this->itemCode = $itemCode;
    $this->itemName = $itemName;
    $this->shippingType = $shippingType;
}

public function getPrice()
{
    if ($this->shippingType == 'cargo') {
        $shipping = new ShipByCargo();
    } elseif ($this->shippingType == 'freight') {
        $shipping = new ShipByFreight();
    } else {
        throw new Exception('Undefined shipping type: ' . $this->shippingType);
    }

    return $shipping->getPrice();
}

}

$order = new Order(‘10029NA’, ‘iPhone X’, ‘cargo’);

echo $order->getPrice();

Facade

This pattern belongs to the structural patterns. It specifies how the code should be structured to be organized and maintainable.

Repetitive code throughout the system can be contained in facade and called with proper arguments to perform specific action.

Example:

class FriendshipFacade { private $notificationService; private $userService; private $friendshipService;

public function __construct( NotificationService $notificationService, UserService $userService, FriendshipService $friendshipService ) { $this->notificationService = $notificationService; $this->userService = $userService; $this->friendshipService = $friendshipService; }

public function add($userId1, $userId2) { $user1 = $this->userService->get($userId1); $user2 = $this->userService->get($userId2);

  $this->friendshipService->add($user1, $user2);

  $this->notificationService->notify($user1, 'You have a new friend: ' . $user2->name);
  $this->notificationService->notify($user2, 'You have a new friend: ' . $user1->name);

} }

class UserController { public function addFriend() { $friendshipFacade = new FriendshipFacade(); $friendshipFacade->add($userId1, $userId2); } }