Design Pattern: Prototype Pattern in PHP

Protype pattern defines a prototype instance for object creation, and then generates object by copying/cloning that prototype. The process is to fork an existing object, and then modify/change the properties.

This article demonstrates Prototype pattern implementations in PHP. Check the following examples.

Implementation

For the prototype pattern implementation in PHP, we need to use the “clone” keyword and in addition to that we need to use the “__clone()” method for proper cloning.

Let’s check step-by-step:

Step #1: Use clone keyword

Use “clone” keyword to create a clone/copy of an existing object. Just putting the “clone” before any object will create a copy.

Check the implementation below-

<?php

// Item class (a general class for cloning test)
class Item {
    public function __construct(private string $prop1, private string $prop2) {

    }

    public function setProp1(string $prop1): void {
        $this->prop1 = $prop1;
    }

    public function getProp1(): string {
        return $this->prop1;
    }

    public function setProp2(string $prop2): void {
        $this->prop2 = $prop2;
    }

    public function getProp2(): string {
        return $this->prop2;
    }
}

// Demo
// Create an "Item" object
$item = new Item("first", "second");

echo "Main item props:\n";
print_r($item);

// Clone item
$cloneItem = clone $item;

echo "Clone item props without any change:\n";
print_r($cloneItem);

// Change properties of the clone
$cloneItem->setProp1("Chagned first prop");
$cloneItem->setProp2("Chagned secocnd prop");

echo "Clone item props after change:\n";
print_r($cloneItem);

// Check if the original object properties are changed or not. (props will not change)
echo "Main item props after cloning:\n";
print_r($item);

Output will be as below-

Main item props:
Item Object
(
    [prop1:Item:private] => first
    [prop2:Item:private] => second
)


Clone item props without any change:
Item Object
(
    [prop1:Item:private] => first
    [prop2:Item:private] => second
)


Clone item props after change:
Item Object
(
    [prop1:Item:private] => Chagned first prop
    [prop2:Item:private] => Chagned secocnd prop
)


Main item props after cloning:
Item Object
(
    [prop1:Item:private] => first
    [prop2:Item:private] => second
)

Step #2: Understanding the problems & edge cases of clone

When we clone any object using the “clone” keyword, it performs a shallow copy of the object. Only the internal properties which are primitive, are copied.

If there is any reference of some other value, in the class then that is not copied. In that case, both the original and the clone will have reference to the same external object.

For example, if there is a property in the class that holds a reference to some object of a class, then that is not copied. Check the example below-

<?php

// Some class for testing cloning
class Dummy {
    public function __construct(private string $dummyProp1) {

    }

    public function setProp1(string $dummyProp1) {
        $this->dummyProp1 = $dummyProp1;
    }
}

// General item class
// This has a property, which stores an object(reference to the object) of the "Dummy" class
class Item {
    public function __construct(
        private string $prop1,
        private string $prop2,
        private Dummy $dummyObj) {

    }

    public function setProp1(string $prop1): void {
        $this->prop1 = $prop1;
    }

    public function getProp1(): string {
        return $this->prop1;
    }

    public function setProp2(string $prop2): void {
        $this->prop2 = $prop2;
    }

    public function getProp2(): string {
        return $this->prop2;
    }

    public function setDummy(Dummy $dummy): void {
        $this->dummyObj = $dummy;
    }

    public function getDummy(): Dummy {
        return $this->dummyObj;
    }
}

// Demo
// Create dummy object
$dummyObj = new Dummy("dummy prop one");

// Create item object
$item = new Item("first", "second", $dummyObj);

echo "Main item props:\n";
print_r($item);

// Clone item
$cloneItem = clone $item;

echo "Clone item props without any change:\n";
print_r($cloneItem);

// Change values in the cloned object
$cloneItem->setProp1("Chagned first prop");
$cloneItem->setProp2("Chagned secocnd prop");
$cloneItem->getDummy()->setProp1("this value is changed in clone object");

// Check the clone object
echo "Clone item props after change:\n";
print_r($cloneItem);

// Check the original object
echo "Main item props after cloning:\n";
print_r($item);

Check the output of this. When the dummy object changes in the cloned object, that also changes in the original object. As the dummy object is saved as a reference in the “Item” object.

Here is the output-

Main item props:
Item Object
(
    [prop1:Item:private] => first
    [prop2:Item:private] => second
    [dummyObj:Item:private] => Dummy Object
        (
            [dummyProp1:Dummy:private] => dummy prop one
        )

)


Clone item props without any change:
Item Object
(
    [prop1:Item:private] => first
    [prop2:Item:private] => second
    [dummyObj:Item:private] => Dummy Object
        (
            [dummyProp1:Dummy:private] => dummy prop one
        )

)


Clone item props after change:
Item Object
(
    [prop1:Item:private] => Chagned first prop
    [prop2:Item:private] => Chagned secocnd prop
    [dummyObj:Item:private] => Dummy Object
        (
            [dummyProp1:Dummy:private] => this value is changed in clone object
        )

)


Main item props after cloning:
Item Object
(
    [prop1:Item:private] => first
    [prop2:Item:private] => second
    [dummyObj:Item:private] => Dummy Object
        (
            [dummyProp1:Dummy:private] => this value is changed in clone object
        )

)

Step #3: Use __clone() to Ensure Deep Copy/Clone

Now, we have seen the problem with shallow copy. Let’s see how we can perform a deep copy/clone of any object.

We need to define a method named “__clone()“. In that method implementation-

  • We can set any default value of a property, or we can initialize it somehow, or we can perform any change.
  • For the properties that have references, we have to clone those referenced values too.
<?php

class Dummy {
    public function __construct(private string $dummyProp1) {

    }

    public function setProp1(string $dummyProp1) {
        $this->dummyProp1 = $dummyProp1;
    }
}

class Item {
    public function __construct(
        private string $prop1,
        private string $prop2,
        private Dummy $dummyObj) {

    }

    public function setProp1(string $prop1): void {
        $this->prop1 = $prop1;
    }

    public function getProp1(): string {
        return $this->prop1;
    }

    public function setProp2(string $prop2): void {
        $this->prop2 = $prop2;
    }

    public function getProp2(): string {
        return $this->prop2;
    }

    public function setDummy(Dummy $dummy): void {
        $this->dummyObj = $dummy;
    }

    public function getDummy(): Dummy {
        return $this->dummyObj;
    }

    public function __clone() {
        // Set default value if you prefer
        $this->prop2 = "default prop2 from clone method";

        // Clone any object to avoid referencing to the original
        $this->dummyObj = clone $this->dummyObj;
    }
}

// Demo
$dummyObj = new Dummy("dummy prop one");

$item = new Item("first", "second", $dummyObj);

echo "Main item props:\n";
print_r($item);

// Clone item
$cloneItem = clone $item;

echo "Clone item props without any change:\n";
print_r($cloneItem);

$cloneItem->setProp1("Chagned first prop");
$cloneItem->setProp2("Chagned secocnd prop");
$cloneItem->getDummy()->setProp1("this value is changed in clone object");

echo "Clone item props after change:\n";
print_r($cloneItem);

echo "Main item props after cloning:\n";
print_r($item);

As we have cloned the “Dummy” object in the “__clone” method, so changing any of the items (the original or clone) will not affect the others.

Here is the output-

Main item props:
Item Object
(
    [prop1:Item:private] => first
    [prop2:Item:private] => second
    [dummyObj:Item:private] => Dummy Object
        (
            [dummyProp1:Dummy:private] => dummy prop one
        )

)


Clone item props without any change:
Item Object
(
    [prop1:Item:private] => first
    [prop2:Item:private] => default prop2 from clone method
    [dummyObj:Item:private] => Dummy Object
        (
            [dummyProp1:Dummy:private] => dummy prop one
        )

)


Clone item props after change:
Item Object
(
    [prop1:Item:private] => Chagned first prop
    [prop2:Item:private] => Chagned secocnd prop
    [dummyObj:Item:private] => Dummy Object
        (
            [dummyProp1:Dummy:private] => this value is changed in clone object
        )

)


Main item props after cloning:
Item Object
(
    [prop1:Item:private] => first
    [prop2:Item:private] => second
    [dummyObj:Item:private] => Dummy Object
        (
            [dummyProp1:Dummy:private] => dummy prop one
        )

)

Examples

Here are a few examples of Prototype pattern implementation and usage.

Example #1: Table Cell

In this example, we are considering table cells. We will use the prototype pattern here.

As we need to generate lots of objects of the cell, so we can just clone any existing Cell object and use that with proper initialization.

Cell Class

This is a general class that represents a table cell.

  • Create file “Cell.php”.
  • Create class “Cell”.
  • Define properties – $row(for row number), $column(for column number), $height (cell height), $width(cell width), $content, $background, $textColor. 
  • Create getter and setter for the properties.
  • Define function “__clone”. In the method implementation put some initial value for the properties, so that whenever a clone is created from existing object, the properties from the original object are not contained.
<?php
// Cell.php

namespace BigBoxCode\DesignPattern\Prototype\TableCell;

class Cell {
    private int $row;
    private int $column;
    private int $height = 10;
    private int $width = 100;
    private ?string $content;
    private string $background = "FFFFFF";
    private string $textColor = "000000";

    public function __construct(int $row, int $column) {
        $this->row = $row;
        $this->column = $column;
    }

    public function getRow(): int {
        return $this->row;
    }

    public function setRow(int $row) {
        $this->row = $row;
    }

    public function getColumn(): int {
        return $this->column;
    }

    public function setColumn(int $column) {
        $this->column = $column;
    }

    public function getHeight(): int {
        return $this->height;
    }

    public function setHeight(int $height) {
        $this->height = $height;
    }

    public function getWidth(): int {
        return $this->width;
    }

    public function setWidth(int $width) {
        $this->width = $width;
    }

	public function getContent(): string {
		return $this->content;
	}
	
	public function setContent(string $content) {
		$this->content = $content;
	}

    public function getBackground(): string {
        return $this->background;
    }

    public function setBackground(string $background) {
        $this->background = $background;
    }

    public function getTextColor(): string {
        return $this->textColor;
    }

    public function setTextColor(string $textColor) {
        $this->textColor = $textColor;
    }

    public function __clone() {
        $this->row = 0;
        $this->column = 0;
        $this->height = 10;
        $this->width = 100;
        $this->content = null;
        $this->background = "FFFFFF";
        $this->textColor = "000000";
    }
}

Demo

Create a cell object. Then we can use the “clone” keyword to create a clone of the object.

<?php
// demo.php

require __DIR__ . '/../../vendor/autoload.php';

use BigBoxCode\DesignPattern\Prototype\TableCell\Cell;


// Create Cell object
$cell = new Cell(1, 1);
$cell->setContent("Original cell content");
$cell->setBackground("808080");

echo "Cell object:\n";
print_r($cell);

// Clone Cell object
$cellClone = clone $cell;

echo "Cell Clone:\n";
print_r($cellClone);

// Change values in clone
$cellClone->setContent("Clone cell");
$cellClone->setBackground("008000");
$cellClone->setTextColor("FFFFFF");


echo "Clone object after changing:\n";
print_r($cellClone);

// Check the original cell object
echo "Original cell object:\n";
print_r($cell);

Output

Output will be as below-

Cell object:
Cell Object
(
    [row:Cell:private] => 1
    [column:Cell:private] => 1
    [height:Cell:private] => 10
    [width:Cell:private] => 100
    [content:Cell:private] => Original cell content
    [background:Cell:private] => 808080
    [textColor:Cell:private] => 000000
)


Cell Clone:
Cell Object
(
    [row:Cell:private] => 0
    [column:Cell:private] => 0
    [height:Cell:private] => 10
    [width:Cell:private] => 100
    [content:Cell:private] =>
    [background:Cell:private] => FFFFFF
    [textColor:Cell:private] => 000000
)


Clone object after changing:
Cell Object
(
    [row:Cell:private] => 0
    [column:Cell:private] => 0
    [height:Cell:private] => 10
    [width:Cell:private] => 100
    [content:Cell:private] => Clone cell
    [background:Cell:private] => 008000
    [textColor:Cell:private] => FFFFFF
)


Original cell object:
Cell Object
(
    [row:Cell:private] => 1
    [column:Cell:private] => 1
    [height:Cell:private] => 10
    [width:Cell:private] => 100
    [content:Cell:private] => Original cell content
    [background:Cell:private] => 808080
    [textColor:Cell:private] => 000000
)

Example #2: Plane Prototype & Cloning

Let’s take an example of vehicles like cars, Planes, etc. In this example, we are implementing the cloning process using a copy constructor.

Plane Class

  • Create file “Plane.php”.
  • Create class “Plane”.
  • Define properties – $name, $manufacturer, $model, $length, $height, $wingspan, $seat. These are of primitive type. 
  • Define property $engine of type “PlaneEngine”, which is an object of another class. So this property will hold a reference to the object.
  • Create getter and setter for all the properties.
  • Define function “__clone”. In the method implementation initialize some vale. Also in the value of the $engine property, set the “clone” of that property.
<?php
// Plane.php

namespace BigBoxCode\DesignPattern\Prototype\Transport;

class Plane {
    private string $name;
    private string $manufacturer;
    private string $model;
    private float $length;
    private float $height;
    private float $wingspan;
    private float $seat;

    private PlaneEngine $engine;

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

    public function setName(string $name) {
        $this->name = $name;
    }

    public function getManufacturer(): string {
        return $this->manufacturer;
    }

    public function setManufacturer(string $manufacturer) {
        $this->manufacturer = $manufacturer;
    }

    public function getModel(): string {
        return $this->model;
    }

    public function setModel(string $model) {
        $this->model = $model;
    }

    public function getLength(): float {
        return $this->length;
    }

    public function setLength(float $length) {
        $this->length = $length;
    }

    public function getHeight(): float {
        return $this->height;
    }

    public function setHeight(float $height) {
        $this->height = $height;
    }

    public function getWingspan(): float {
        return $this->wingspan;
    }

    public function setWingspan(float $wingspan) {
        $this->wingspan = $wingspan;

        return $this;
    }

    public function getSeat(): float {
        return $this->seat;
    }

    public function setSeat(float $seat) {
        $this->seat = $seat;
    }

    public function getEngine(): PlaneEngine {
        return $this->engine;
    }

    public function setEngine(PlaneEngine $engine) {
        $this->engine = $engine;

        return $this;
    }

    public function __clone() {
        // Set some prefix for the clone objects
        $this->name = "Clone of " . $this->name;

        // Clone the engine object to avoid referencing to the original
        $this->engine = clone $this->engine;
    }
}

Engine Class

This is a general class, that represents a plane engine. Reference to this class is used in the “Plane” class.

  • Create file “PlaneEngine.php”.
  • Create class “PlaneEngine”.
  • Define properties – $name, $length, $weight, $noOfBlade, $rpm. Also define getter and setter for the properties.
<?php
// PlaneEngine.php

namespace BigBoxCode\DesignPattern\Prototype\Transport;

class PlaneEngine {
    private string $name;
    private float $length;
    private float $weight;
    private int $noOfBlade;
    private int $rpm;

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

    public function setName(string $name) {
        $this->name = $name;
    }

    public function getLength(): float {
        return $this->length;
    }

    public function setLength(float $length) {
        $this->length = $length;
    }

    public function getWeight(): float {
        return $this->weight;
    }

    public function setWeight(float $weight) {
        $this->weight = $weight;
    }

    public function getNoOfBlade(): int {
        return $this->noOfBlade;
    }

    public function setNoOfBlade(int $noOfBlade) {
        $this->noOfBlade = $noOfBlade;
    }

    public function getRpm(): int {
        return $this->rpm;
    }

    public function setRpm(int $rpm) {
        $this->rpm = $rpm;
    }
}

Demo

Create a “PlaneEngine” object first. Then create a “Plane” and set the “$planeEngine” object to the $engine prop of the “Plane” object.

We can now use the “clone” keyword to clone “Plane” object. As we have defined “__clone” method in “Plane” class, that’s why a deep copy will be performed.

That will ensure that, any change in the clone object stays independent of the original object.

<?php
// demo.php

require __DIR__ . '/../../vendor/autoload.php';

use BigBoxCode\DesignPattern\Prototype\Transport\Plane;
use BigBoxCode\DesignPattern\Prototype\Transport\PlaneEngine;


// Prepare the engine object
$planeEngine = new PlaneEngine();
$planeEngine->setName("GE9X");
$planeEngine->setLength(220);
$planeEngine->setWeight(22_000);
$planeEngine->setNoOfBlade(16);
$planeEngine->setRpm(2_510);

// Create a plane object
$plane = new Plane();
$plane->setName("Boing 777");
$plane->setManufacturer("Boing");
$plane->setModel("777-200LR");
$plane->setLength(63.7);
$plane->setHeight(18.6);
$plane->setWingspan(64.8);
$plane->setSeat(317);
$plane->setEngine($planeEngine);

// Print details fo the plane
echo "Original Plane object:\n";
print_r($plane);

// Clone Plane object
$clonePlane = clone $plane;

// Print details just after cloning
echo "Clone Plane object:\n";
print_r($clonePlane);

// Change some value in clone plane
$clonePlane->setModel("777-300ER");
$clonePlane->getEngine()->setName("GE10YYYY");

echo "Clone Plane object after change:\n";
print_r($clonePlane);

// Check the original Plane object
echo "Original Plane object after changes in the clone:\n";
print_r($plane);

Output

Output of the original and cloned “Plane” object as below.

Original Plane object:
Plane Object
(
    [name:Plane:private] => Boing 777
    [manufacturer:Plane:private] => Boing
    [model:Plane:private] => 777-200LR
    [length:Plane:private] => 63.7
    [height:Plane:private] => 18.6
    [wingspan:Plane:private] => 64.8
    [seat:Plane:private] => 317
    [engine:Plane:private] => PlaneEngine Object
        (
            [name:PlaneEngine:private] => GE9X
            [length:PlaneEngine:private] => 220
            [weight:PlaneEngine:private] => 22000
            [noOfBlade:PlaneEngine:private] => 16
            [rpm:PlaneEngine:private] => 2510
        )

)


Clone Plane object:
Plane Object
(
    [name:Plane:private] => Clone of Boing 777
    [manufacturer:Plane:private] => Boing
    [model:Plane:private] => 777-200LR
    [length:Plane:private] => 63.7
    [height:Plane:private] => 18.6
    [wingspan:Plane:private] => 64.8
    [seat:Plane:private] => 317
    [engine:Plane:private] => PlaneEngine Object
        (
            [name:PlaneEngine:private] => GE9X
            [length:PlaneEngine:private] => 220
            [weight:PlaneEngine:private] => 22000
            [noOfBlade:PlaneEngine:private] => 16
            [rpm:PlaneEngine:private] => 2510
        )

)


Clone Plane object after change:
Plane Object
(
    [name:Plane:private] => Clone of Boing 777
    [manufacturer:Plane:private] => Boing
    [model:Plane:private] => 777-300ER
    [length:Plane:private] => 63.7
    [height:Plane:private] => 18.6
    [wingspan:Plane:private] => 64.8
    [seat:Plane:private] => 317
    [engine:Plane:private] => PlaneEngine Object
        (
            [name:PlaneEngine:private] => GE10YYYY
            [length:PlaneEngine:private] => 220
            [weight:PlaneEngine:private] => 22000
            [noOfBlade:PlaneEngine:private] => 16
            [rpm:PlaneEngine:private] => 2510
        )

)


Original Plane object after changes in the clone:
Plane Object
(
    [name:Plane:private] => Boing 777
    [manufacturer:Plane:private] => Boing
    [model:Plane:private] => 777-200LR
    [length:Plane:private] => 63.7
    [height:Plane:private] => 18.6
    [wingspan:Plane:private] => 64.8
    [seat:Plane:private] => 317
    [engine:Plane:private] => PlaneEngine Object
        (
            [name:PlaneEngine:private] => GE9X
            [length:PlaneEngine:private] => 220
            [weight:PlaneEngine:private] => 22000
            [noOfBlade:PlaneEngine:private] => 16
            [rpm:PlaneEngine:private] => 2510
        )

)

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Prototype pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.