PHP Design Patterns — Prototype

Photo by Phil Shaw on Unsplash

Let’s suppose you are a developer working on an eCommerce, and you’re given the responsibility for this feature:

In the Cart screen, user should be able to add more of an existing product there, and change its color.

One intuitive solution in this context could be instantiating a new product object, and set all attributes to be equal the original product. But this seems too verbose, and you also have to know all the internals of the product code, which seems like breaking the encapsulation.

Prototype to the rescue!

From refactoring.guru:

Prototype is a creational design pattern that lets you copy existing objects without making your code dependent on their classes.

That means you don’t need to use the new operator and worry about how to setup a perfect copy instance. Just use the built-in clone operator and PHP will deal with everything.

Sample application

You check the final code on Github.

The Cart class doesn't make part of the design pattern but it's here just to demonstrate how it fits in a real world application:

final class Cart
{
/**
* @var ProductPrototype[]
*/
private array $products;

public function addProduct(ProductPrototype $product): void
{
$this->products[] = $product;
}

/**
* @return ProductPrototype[]
*/
public function getProducts(): array
{
return $this->products;
}
}

As you can see, we are able to add a new product and see which products are already there.

abstract class ProductPrototype
{
protected int $id;
protected string $name;
protected string $color;

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

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

public function getColor(): string
{
return $this->color;
}

public function setColor(string $color): void
{
$this->color = $color;
}
}

PHP already implements the Prototype design pattern natively because every object already has a built-in clone operator support. In this example, I created the ProductPrototype class just to illustrate a real use case: for Cart class is doesn't matter which specific kind of product it has, as long as it has the attributes id, name and color.

Just to simplify this example, I will add only two types of product: Smartphone and Laptop:

final class Smartphone extends ProductPrototype
{
public function __construct()
{
$this->id = 1;
$this->name = 'Smartphone';
$this->color = 'Default color';
}
}

final class Laptop extends ProductPrototype
{
public function __construct()
{
$this->id = 2;
$this->name = 'Smartphone';
$this->color = 'Default color';
}
}

Explaining Prototype with Unit test

What does the clone operator really do? It creates a copy of an instance with the same attributes. So in our example the cloned product will contain the same id, name and color as the original.

public function testSmartphoneClone(): void
{
$smartphone = new Smartphone();

$clonedSmartphone = clone $smartphone;

self::assertEquals($clonedSmartphone->getId(), $smartphone->getId());
self::assertEquals($clonedSmartphone->getName(), $smartphone->getName());
self::assertEquals($clonedSmartphone->getColor(), $smartphone->getColor());
self::assertEquals($clonedSmartphone, $smartphone);
self::assertNotSame($clonedSmartphone, $smartphone);
}

As you can see, if we were to manually clone an object, we would have to know all the properties to be copied, and how to set them.

Please notice this test shouldn’t be written in a real application because we can assume the clone method already works since its provided by PHP itself.

Back to our use case

We finally can enable the Cart to increase the quantity of an existing product and change its color. Cart will be even happier because it doesn't matter if it's an Laptop or Smartphone.

Just make use of clone and add the new cloned product:

public function testCartCanAddClonedProducts(): void
{
$laptop = new Laptop();
$cart = new Cart();

$cart->addProduct($laptop);

$clonedLaptop = clone $laptop;
$clonedLaptop->setColor('White');
$cart->addProduct($clonedLaptop);

self::assertCount(2, $cart->getProducts());
self::assertEquals($cart->getProducts()[1]->getId(), $cart->getProducts()[0]->getId());
self::assertEquals($cart->getProducts()[1]->getName(), $cart->getProducts()[0]->getName());
self::assertEquals('White', $cart->getProducts()[1]->getColor());
self::assertEquals('Default color', $cart->getProducts()[0]->getColor());
}

Thanks for reading and hope you could get a better picture of this design pattern and will remember to use it whenever the opportunity appears!

--

--

--

fabiothiroki.github.io

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Proxy VS Reverse Proxy (less theory)

Reading a file directory with Python

Heading image

Getting Started with Go

Compose Your Android Navigation with Custom Arguments

Const Behind The Scenes

Dexstream.io — First user guide

How to perform REST API Testing

What is a CSR file and how to create it?

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Fabio Hiroki

Fabio Hiroki

fabiothiroki.github.io

More from Medium

Singleton Pattern — PHP Implementation

Extending PHP 8.1 Enums

Master PHP Iterators

Calculate PHP cache time with ease