Prototype design pattern is used when we want to create one object which can be used as a template (prototype) for creation of some other objects.

Prototype pattern is one of the creational patterns (because it indicates a way of how we create objects).

Creational patterns are: Factory pattern, Abstract factory pattern, Singleton pattern, Prototype pattern, Builder pattern and Object pool pattern.

Table of Contents

Use case example

Imagine for example, having a system that handles large number of user accounts, with different account types (SuperAdministrator, Administrator, Moderator, Member and Guest).

Now, imagine that these classes have a complex system for calculation of permissions. Complex means that there are lots of calculations (expensive and long ones) resulting in array of permissions.

If we would make new object for each user that appears in system (creates, logs in, etc.) we would repeat this expensive action lots of times. On the other hand if we “prepare the prototype” of common classes of users, then we get to quickly clone them and use already prepared values.

Essentially, if used properly prototype design pattern can:

  • Optimize code execution, speeding it up by cloning results of calculations instead of repeating calculations (each time when we do new Object())
  • Make code more readable and prettified
  • Potentially make unit testing more straightforward, because structure is cleaner

Prototype pattern example in PHP

This is one simple example of prototype pattern, written in PHP. As you can see we have a UserPrototype abstract class. Meaning, this class can never be instantiated. Furthermore, this class has all important properties that our prototypes should have.

Moreover, there is an abstract function called __clone() which will be implemented in all classes created from this abstract.

<?php

/**
 * UserPrototype class represents an abstract class to define Users.
 * This class has common values, important setters and getters.
 * This class has one abstract function clone, which will be realized
 * in the "child" classes.
 */
abstract class UserPrototype {

    protected $typeId;
    protected $typeName;
    protected $adminAccess;
    protected $databaseAccess;
    protected $securityLogAccess;
    protected $permissions;
    protected $username;
    protected $firstname;
    protected $lastname;

    abstract function __clone();

    function setUsername($username) {
        $this->username = $username;
    }

    function setFirstname($firstname) {
        $this->firstname = $firstname;
    }

    function setLastname($lastname) {
        $this->lastname = $lastname;
    }

}

class SuperAdminPrototype extends UserPrototype {
    function __construct() {
        $this->typeId = 1;
        $this->type = 'SuperAdministrator';
        $this->adminAcces = true;
        $this->databaseAccess = true;
        $this->securityLogAccess = true;
        $this->permissions = [
            'create-user',
            'delete-user',
            'absolutely-all-permissions',
            'rm rf system',
            'read-logs',
            'delete-admins',
            'moderate-content',
            'create-content',
            'read-content',
        ];
    }
    function __clone() {
    }
}

class AdminPrototype extends UserPrototype {
    function __construct() {
        $this->typeId = 2;
        $this->type = 'Administrator';
        $this->adminAcces = true;
        $this->databaseAccess = true;
        $this->securityLogAccess = true;
        $this->permissions = [
            'create-user',
            'delete-user',
            'read-logs',
            'moderate-content',
            'create-content',
            'read-content',
        ];
    }
    function __clone() {
    }
}

class ModeratorPrototype extends UserPrototype {
    function __construct() {
        $this->typeId = 3;
        $this->type = 'Moderator';
        $this->adminAcces = false;
        $this->databaseAccess = true;
        $this->securityLogAccess = true;
        $this->permissions = [
            'moderate-content',
            'create-content',
            'read-content',
        ];
    }
    function __clone() {
    }
}

class MemberPrototype extends UserPrototype {
    function __construct() {
        $this->typeId = 5;
        $this->type = 'Member';
        $this->adminAcces = false;
        $this->databaseAccess = false;
        $this->securityLogAccess = false;
        $this->permissions = [
            'create-content',
            'read-content',
        ];
    }
    function __clone() {
    }
}

class GuestPrototype extends UserPrototype {
    function __construct() {
        $this->typeId = 6;
        $this->type = 'Guest';
        $this->adminAcces = false;
        $this->databaseAccess = false;
        $this->securityLogAccess = false;
        $this->permissions = [
            'read-content',
        ];
    }
    function __clone() {
    }
}
 
echo "=== Examples of Prototype pattern === \n";

// Firstly, we will create object for each prototype

$superAdminPrototype = new SuperAdminPrototype();
$adminPrototype = new AdminPrototype();
$moderatorPrototype = new ModeratorPrototype();
$memberPrototype = new MemberPrototype();
$guestPrototype = new GuestPrototype();

// Secondly, we will create several objects out of prototype objects

$superAdmin = clone $superAdminPrototype;
$superAdmin->setUsername("fancySuperAdminUsername");
$superAdmin->setFirstname("Super");
$superAdmin->setLastname("Admin");

$admin1 = clone $adminPrototype;
$admin1->setUsername("fancyAdminNameNo1");
$admin1->setFirstname("Admin");
$admin1->setLastname("001");

$admin2 = clone $adminPrototype;
$admin2->setUsername("fancyAdminNameNo2");
$admin2->setFirstname("Admin");
$admin2->setLastname("002");

$moderator = clone $moderatorPrototype;
$moderator->setUsername("fancyModeratorUsername");
$moderator->setFirstname("Moderator");
$moderator->setLastname("The Great");

$plainOldMember = clone $memberPrototype;
$plainOldMember->setUsername("PlainOldMemberUsername");
$plainOldMember->setFirstname("PlainOld");
$plainOldMember->setLastname("Member");

$justGuest = clone $guestPrototype;
$justGuest->setUsername("JaneDoe");
$justGuest->setFirstname("Jane");
$justGuest->setLastname("Doe");

// If we list Super Admin object, we will see that all prototype values are assigned and available.

var_dump($superAdmin);

// If we list Admin objects, we will see that all prototype values are assigned and available.

var_dump($admin1);
var_dump($admin2);

// Same case for moderator, member and guest prototypes

var_dump($moderator);
var_dump($plainOldMember);
var_dump($justGuest);

Firstly, there is a SuperAdminPrototype class, which has some specific configuration. As we can see SuperAdmins will have very wide scope of permissions.

Secondly, there is a AdminPrototype class, with different configuration, but it also implements __clone() function. We continue with ModeratorPrototype, MemberPrototype and UserPrototype classes.

Finally, we create several different objects. Notice that we are using clone function instead of “new” directive.

When we do a var_dump to show the content of the variables all configuration data is properly cloned into them.

This is the result of execution

=== Examples of Prototype pattern === 
object(SuperAdminPrototype)#6 (11) {
  ["typeId":protected]=>
  int(1)
  ["typeName":protected]=>
  NULL
  ["adminAccess":protected]=>
  NULL
  ["databaseAccess":protected]=>
  bool(true)
  ["securityLogAccess":protected]=>
  bool(true)
  ["permissions":protected]=>
  array(9) {
    [0]=>
    string(11) "create-user"
    [1]=>
    string(11) "delete-user"
    [2]=>
    string(26) "absolutely-all-permissions"
    [3]=>
    string(12) "rm rf system"
    [4]=>
    string(9) "read-logs"
    [5]=>
    string(13) "delete-admins"
    [6]=>
    string(16) "moderate-content"
    [7]=>
    string(14) "create-content"
    [8]=>
    string(12) "read-content"
  }
  ["username":protected]=>
  string(23) "fancySuperAdminUsername"
  ["firstname":protected]=>
  string(5) "Super"
  ["lastname":protected]=>
  string(5) "Admin"
  ["type"]=>
  string(18) "SuperAdministrator"
  ["adminAcces"]=>
  bool(true)
}
object(AdminPrototype)#7 (11) {
  ["typeId":protected]=>
  int(2)
  ["typeName":protected]=>
  NULL
  ["adminAccess":protected]=>
  NULL
  ["databaseAccess":protected]=>
  bool(true)
  ["securityLogAccess":protected]=>
  bool(true)
  ["permissions":protected]=>
  array(6) {
    [0]=>
    string(11) "create-user"
    [1]=>
    string(11) "delete-user"
    [2]=>
    string(9) "read-logs"
    [3]=>
    string(16) "moderate-content"
    [4]=>
    string(14) "create-content"
    [5]=>
    string(12) "read-content"
  }
  ["username":protected]=>
  string(17) "fancyAdminNameNo1"
  ["firstname":protected]=>
  string(5) "Admin"
  ["lastname":protected]=>
  string(3) "001"
  ["type"]=>
  string(13) "Administrator"
  ["adminAcces"]=>
  bool(true)
}
object(AdminPrototype)#8 (11) {
  ["typeId":protected]=>
  int(2)
  ["typeName":protected]=>
  NULL
  ["adminAccess":protected]=>
  NULL
  ["databaseAccess":protected]=>
  bool(true)
  ["securityLogAccess":protected]=>
  bool(true)
  ["permissions":protected]=>
  array(6) {
    [0]=>
    string(11) "create-user"
    [1]=>
    string(11) "delete-user"
    [2]=>
    string(9) "read-logs"
    [3]=>
    string(16) "moderate-content"
    [4]=>
    string(14) "create-content"
    [5]=>
    string(12) "read-content"
  }
  ["username":protected]=>
  string(17) "fancyAdminNameNo2"
  ["firstname":protected]=>
  string(5) "Admin"
  ["lastname":protected]=>
  string(3) "002"
  ["type"]=>
  string(13) "Administrator"
  ["adminAcces"]=>
  bool(true)
}
object(ModeratorPrototype)#9 (11) {
  ["typeId":protected]=>
  int(3)
  ["typeName":protected]=>
  NULL
  ["adminAccess":protected]=>
  NULL
  ["databaseAccess":protected]=>
  bool(true)
  ["securityLogAccess":protected]=>
  bool(true)
  ["permissions":protected]=>
  array(3) {
    [0]=>
    string(16) "moderate-content"
    [1]=>
    string(14) "create-content"
    [2]=>
    string(12) "read-content"
  }
  ["username":protected]=>
  string(22) "fancyModeratorUsername"
  ["firstname":protected]=>
  string(9) "Moderator"
  ["lastname":protected]=>
  string(9) "The Great"
  ["type"]=>
  string(9) "Moderator"
  ["adminAcces"]=>
  bool(false)
}
object(MemberPrototype)#10 (11) {
  ["typeId":protected]=>
  int(5)
  ["typeName":protected]=>
  NULL
  ["adminAccess":protected]=>
  NULL
  ["databaseAccess":protected]=>
  bool(false)
  ["securityLogAccess":protected]=>
  bool(false)
  ["permissions":protected]=>
  array(2) {
    [0]=>
    string(14) "create-content"
    [1]=>
    string(12) "read-content"
  }
  ["username":protected]=>
  string(22) "PlainOldMemberUsername"
  ["firstname":protected]=>
  string(8) "PlainOld"
  ["lastname":protected]=>
  string(6) "Member"
  ["type"]=>
  string(6) "Member"
  ["adminAcces"]=>
  bool(false)
}
object(GuestPrototype)#11 (11) {
  ["typeId":protected]=>
  int(6)
  ["typeName":protected]=>
  NULL
  ["adminAccess":protected]=>
  NULL
  ["databaseAccess":protected]=>
  bool(false)
  ["securityLogAccess":protected]=>
  bool(false)
  ["permissions":protected]=>
  array(1) {
    [0]=>
    string(12) "read-content"
  }
  ["username":protected]=>
  string(7) "JaneDoe"
  ["firstname":protected]=>
  string(4) "Jane"
  ["lastname":protected]=>
  string(3) "Doe"
  ["type"]=>
  string(5) "Guest"
  ["adminAcces"]=>
  bool(false)
}