![Prototype](/images/patterns/cards/prototype-mini.png?id=bc3046bb39ff36574c08d49839fd1c8e)
Prototype en PHP
Le Prototype est un patron de conception de création qui permet de cloner des objets - même complexes - sans se coupler à leur classe.
Toutes les classes prototype devraient avoir une interface commune rendant possible la copie des objets, même sans connaître leur classe concrète. Les objets prototype peuvent créer des copies complètes puisqu’ils peuvent accéder aux attributs privés des autres objets de la même classe.
Complexité :
Popularité :
Exemples d’utilisation : Le prototype est [prêt à l’emploi] (http://php.net/manual/en/language.oop5.cloning.php) dans PHP. Vous pouvez utiliser le mot-clef clone
pour créer une copie exacte d’un objet. Pour ajouter la prise en charge du clonage à une classe, vous devez implémenter une méthode __clone
.
Identification : Le prototype peut facilement être reconnu grâce aux méthodes clone
ou copier
, etc.
Exemple conceptuel
Dans cet exemple, nous allons voir la structure du Prototype et répondre aux questions suivantes :
- Que contiennent les classes ?
- Quels rôles jouent-elles ?
- Comment les éléments du patron sont-ils reliés ?
Après avoir étudié la structure du patron, vous pourrez plus facilement comprendre l’exemple suivant qui est basé sur un cas d’utilisation réel en PHP.
index.php: Exemple conceptuel
<?php
namespace RefactoringGuru\Prototype\Conceptual;
/**
* The example class that has cloning ability. We'll see how the values of field
* with different types will be cloned.
*/
class Prototype
{
public $primitive;
public $component;
public $circularReference;
/**
* PHP has built-in cloning support. You can `clone` an object without
* defining any special methods as long as it has fields of primitive types.
* Fields containing objects retain their references in a cloned object.
* Therefore, in some cases, you might want to clone those referenced
* objects as well. You can do this in a special `__clone()` method.
*/
public function __clone()
{
$this->component = clone $this->component;
// Cloning an object that has a nested object with backreference
// requires special treatment. After the cloning is completed, the
// nested object should point to the cloned object, instead of the
// original object.
$this->circularReference = clone $this->circularReference;
$this->circularReference->prototype = $this;
}
}
class ComponentWithBackReference
{
public $prototype;
/**
* Note that the constructor won't be executed during cloning. If you have
* complex logic inside the constructor, you may need to execute it in the
* `__clone` method as well.
*/
public function __construct(Prototype $prototype)
{
$this->prototype = $prototype;
}
}
/**
* The client code.
*/
function clientCode()
{
$p1 = new Prototype();
$p1->primitive = 245;
$p1->component = new \DateTime();
$p1->circularReference = new ComponentWithBackReference($p1);
$p2 = clone $p1;
if ($p1->primitive === $p2->primitive) {
echo "Primitive field values have been carried over to a clone. Yay!\n";
} else {
echo "Primitive field values have not been copied. Booo!\n";
}
if ($p1->component === $p2->component) {
echo "Simple component has not been cloned. Booo!\n";
} else {
echo "Simple component has been cloned. Yay!\n";
}
if ($p1->circularReference === $p2->circularReference) {
echo "Component with back reference has not been cloned. Booo!\n";
} else {
echo "Component with back reference has been cloned. Yay!\n";
}
if ($p1->circularReference->prototype === $p2->circularReference->prototype) {
echo "Component with back reference is linked to original object. Booo!\n";
} else {
echo "Component with back reference is linked to the clone. Yay!\n";
}
}
clientCode();
Output.txt: Résultat de l’exécution
Primitive field values have been carried over to a clone. Yay!
Simple component has been cloned. Yay!
Component with back reference has been cloned. Yay!
Component with back reference is linked to the clone. Yay!
Analogie du monde réel
Le Prototype fournit un moyen pratique de répliquer des objets existants plutôt que d’essayer de reconstruire les objets en copiant directement leurs attributs. L’approche directe va non seulement coupler les classes aux objets clonés, mais de plus elle ne vous permet pas de copier les attributs privés. Le prototype vous permet de cloner l’objet à l’intérieur de la classe clonée, où l’accès aux attributs privés de la classe n’est pas restreint.
Cet exemple vous montre comment cloner une page complexe en utilisant le patron de conception prototype. La classe Page possède beaucoup d’attributs privés qui seront recopiés dans l’objet cloné grâce au prototype.
index.php: Exemple du monde réel
<?php
namespace RefactoringGuru\Prototype\RealWorld;
/**
* Prototype.
*/
class Page
{
private $title;
private $body;
/**
* @var Author
*/
private $author;
private $comments = [];
/**
* @var \DateTime
*/
private $date;
// +100 private fields.
public function __construct(string $title, string $body, Author $author)
{
$this->title = $title;
$this->body = $body;
$this->author = $author;
$this->author->addToPage($this);
$this->date = new \DateTime();
}
public function addComment(string $comment): void
{
$this->comments[] = $comment;
}
/**
* You can control what data you want to carry over to the cloned object.
*
* For instance, when a page is cloned:
* - It gets a new "Copy of ..." title.
* - The author of the page remains the same. Therefore we leave the
* reference to the existing object while adding the cloned page to the list
* of the author's pages.
* - We don't carry over the comments from the old page.
* - We also attach a new date object to the page.
*/
public function __clone()
{
$this->title = "Copy of " . $this->title;
$this->author->addToPage($this);
$this->comments = [];
$this->date = new \DateTime();
}
}
class Author
{
private $name;
/**
* @var Page[]
*/
private $pages = [];
public function __construct(string $name)
{
$this->name = $name;
}
public function addToPage(Page $page): void
{
$this->pages[] = $page;
}
}
/**
* The client code.
*/
function clientCode()
{
$author = new Author("John Smith");
$page = new Page("Tip of the day", "Keep calm and carry on.", $author);
// ...
$page->addComment("Nice tip, thanks!");
// ...
$draft = clone $page;
echo "Dump of the clone. Note that the author is now referencing two objects.\n\n";
print_r($draft);
}
clientCode();
Output.txt: Résultat de l’exécution
Dump of the clone. Note that the author is now referencing two objects.
RefactoringGuru\Prototype\RealWorld\Page Object
(
[title:RefactoringGuru\Prototype\RealWorld\Page:private] => Copy of Tip of the day
[body:RefactoringGuru\Prototype\RealWorld\Page:private] => Keep calm and carry on.
[author:RefactoringGuru\Prototype\RealWorld\Page:private] => RefactoringGuru\Prototype\RealWorld\Author Object
(
[name:RefactoringGuru\Prototype\RealWorld\Author:private] => John Smith
[pages:RefactoringGuru\Prototype\RealWorld\Author:private] => Array
(
[0] => RefactoringGuru\Prototype\RealWorld\Page Object
(
[title:RefactoringGuru\Prototype\RealWorld\Page:private] => Tip of the day
[body:RefactoringGuru\Prototype\RealWorld\Page:private] => Keep calm and carry on.
[author:RefactoringGuru\Prototype\RealWorld\Page:private] => RefactoringGuru\Prototype\RealWorld\Author Object
*RECURSION*
[comments:RefactoringGuru\Prototype\RealWorld\Page:private] => Array
(
[0] => Nice tip, thanks!
)
[date:RefactoringGuru\Prototype\RealWorld\Page:private] => DateTime Object
(
[date] => 2018-06-04 14:50:39.306237
[timezone_type] => 3
[timezone] => UTC
)
)
[1] => RefactoringGuru\Prototype\RealWorld\Page Object
*RECURSION*
)
)
[comments:RefactoringGuru\Prototype\RealWorld\Page:private] => Array
(
)
[date:RefactoringGuru\Prototype\RealWorld\Page:private] => DateTime Object
(
[date] => 2018-06-04 14:50:39.306272
[timezone_type] => 3
[timezone] => UTC
)
)