PHP Design Patterns: Prototype Pattern
This is the 16th post in a series of articles that will walk you through how to implement design patterns in PHP.
In this part, we’re going to take a look at how you can implement the Prototype Pattern.
The Prototype Pattern is used for creating new objects by means of cloning. Since you no longer have to create individual classes, this effectively replaces inheritance with composition.
To implement the Prototype Pattern, we make use of the clone
keyword to make shallow copies of objects. The clone will then have the same properties as its source.
Here we have a Ninja
class. This is the class whose instance we will be cloning later on. When instantiating a new object, you pass in the name, chakra level, and the techniques of the ninja. We also have the setChakra
and setTechniques
methods which we’ll be using later on to modify the cloned object. Lastly, we can specify the cloning behavior via the __clone
magic method. Here we’re simply changing the name to include “Copy of " plus the name that was given to the original:
<?php
// prototypepattern/app/Ninja.php
namespace PrototypePattern\App;
use PrototypePattern\App\NinjaInterface;
class Ninja implements NinjaInterface
{
public function __construct(public string $name, public int $chakra, public array $techniques)
{
}
public function setChakra(int $chakra)
{
$this->chakra = $chakra;
}
public function setTechniques(array $techniques)
{
$this->techniques = $techniques;
}
public function __clone()
{
$this->name = "Copy of " . $this->name;
}
}
We’re implementing a NinjaInterface
because we’ll be creating a factory for the Ninja
class:
<?php
// prototypepattern/app/NinjaInterface.php
namespace PrototypePattern\App;
interface NinjaInterface
{
}
Here’s the NinjaFactory
which allows us to create copies of the original object that was passed. We’re implemeting the prototype pattern through these three methods: emptySlateNinja
, ninjaWithChakra
, and ninjaWithChakraAndTechniques
which is basically returning the same properties as the original:
<?php
// prototypepattern/app/Factories/NinjaFactory.php
namespace PrototypePattern\App\Factories;
use PrototypePattern\App\NinjaInterface;
class NinjaFactory
{
public function __construct(private NinjaInterface $ninja)
{
}
public function emptySlateNinja()
{
$ninja = clone $this->ninja;
$ninja->setChakra(0);
$ninja->setTechniques([]);
return $ninja;
}
public function ninjaWithChakra()
{
$ninja = clone $this->ninja;
$ninja->setTechniques([]);
return $ninja;
}
public function ninjaWithChakraAndTechniques()
{
return clone $this->ninja;
}
}
To use it, simply create a new instance of the Ninja
class and pass it to the NinjaFactory
. From there, you can call any of the three methods I mentioned earlier to create a clone of the original $ninja
object with different options:
<?php
// prototypepattern/PrototypeTest.php
require_once __DIR__ . '/../vendor/autoload.php';
use PHPUnit\Framework\TestCase;
class PrototypeTest extends TestCase
{
public function test_prototype() : void
{
$ninja = new PrototypePattern\App\Ninja('Naruto', 100, ['rasengan', 'kage bunshin'], false, false);
$ninjaFactory = new PrototypePattern\App\Factories\NinjaFactory($ninja);
$this->assertEquals($ninjaFactory->emptySlateNinja()->chakra, 0);
$this->assertEquals($ninjaFactory->emptySlateNinja()->techniques, []);
$this->assertEquals($ninjaFactory->ninjaWithChakra()->chakra, 100);
$this->assertEquals($ninjaFactory->ninjaWithChakra()->techniques, []);
$this->assertEquals($ninjaFactory->ninjaWithChakraAndTechniques()->chakra, 100);
$this->assertEquals($ninjaFactory->ninjaWithChakraAndTechniques()->techniques, ['rasengan', 'kage bunshin']);
}
}
To run this. Be sure to add the following on your composer.json
file then run composer dump-autoload
:
"autoload": {
"psr-4": {
"ProtoTypePattern\\App\\": "prototypepattern/app/"
}
},
Also install phpunit (composer require phpunit/phpunit
) if you haven’t done so already.
You can then run it by executing the following:
vendor/bin/phpunit prototypepattern/app/PrototypeTest.php
That’s pretty much what the Prototype pattern is all about. It’s simply a way to bypass having to create separate objects and passing in different options to it. Instead, we just clone existing objects and modify them accordingly. It’s commonly used in tandem with the Factory pattern.
You can find the source code used in this tutorial on this GitHub repo.