mirror of
https://github.com/DesignPatternsPHP/DesignPatternsPHP.git
synced 2025-07-31 04:00:18 +02:00
PHP7 Memento
This commit is contained in:
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Memento;
|
||||
|
||||
class Caretaker
|
||||
{
|
||||
protected $history = array();
|
||||
|
||||
/**
|
||||
* @return Memento
|
||||
*/
|
||||
public function getFromHistory($id)
|
||||
{
|
||||
return $this->history[$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Memento $state
|
||||
*/
|
||||
public function saveToHistory(Memento $state)
|
||||
{
|
||||
$this->history[] = $state;
|
||||
}
|
||||
|
||||
public function runCustomLogic()
|
||||
{
|
||||
$originator = new Originator();
|
||||
|
||||
//Setting state to State1
|
||||
$originator->setState('State1');
|
||||
//Setting state to State2
|
||||
$originator->setState('State2');
|
||||
//Saving State2 to Memento
|
||||
$this->saveToHistory($originator->getStateAsMemento());
|
||||
//Setting state to State3
|
||||
$originator->setState('State3');
|
||||
|
||||
// We can request multiple mementos, and choose which one to roll back to.
|
||||
// Saving State3 to Memento
|
||||
$this->saveToHistory($originator->getStateAsMemento());
|
||||
//Setting state to State4
|
||||
$originator->setState('State4');
|
||||
|
||||
$originator->restoreFromMemento($this->getFromHistory(1));
|
||||
//State after restoring from Memento: State3
|
||||
|
||||
return $originator->getStateAsMemento()->getState();
|
||||
}
|
||||
}
|
@@ -4,19 +4,21 @@ namespace DesignPatterns\Behavioral\Memento;
|
||||
|
||||
class Memento
|
||||
{
|
||||
/* @var mixed */
|
||||
/**
|
||||
* @var State
|
||||
*/
|
||||
private $state;
|
||||
|
||||
/**
|
||||
* @param mixed $stateToSave
|
||||
* @param State $stateToSave
|
||||
*/
|
||||
public function __construct($stateToSave)
|
||||
public function __construct(State $stateToSave)
|
||||
{
|
||||
$this->state = $stateToSave;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @return State
|
||||
*/
|
||||
public function getState()
|
||||
{
|
||||
|
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Memento;
|
||||
|
||||
class Originator
|
||||
{
|
||||
/* @var mixed */
|
||||
private $state;
|
||||
|
||||
// The class could also contain additional data that is not part of the
|
||||
// state saved in the memento..
|
||||
|
||||
/**
|
||||
* @param mixed $state
|
||||
*/
|
||||
public function setState($state)
|
||||
{
|
||||
// you must check type of state inside child of this class
|
||||
// or use type-hinting for full pattern implementation
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Memento
|
||||
*/
|
||||
public function getStateAsMemento()
|
||||
{
|
||||
// you must save a separate copy in Memento
|
||||
$state = is_object($this->state) ? clone $this->state : $this->state;
|
||||
|
||||
return new Memento($state);
|
||||
}
|
||||
|
||||
public function restoreFromMemento(Memento $memento)
|
||||
{
|
||||
$this->state = $memento->getState();
|
||||
}
|
||||
}
|
@@ -6,8 +6,8 @@ Purpose
|
||||
|
||||
It provides the ability to restore an object to it's previous state (undo
|
||||
via rollback) or to gain access to state of the object, without revealing
|
||||
it's implementation (i.e., the object is not required to have a functional
|
||||
for return the current state).
|
||||
it's implementation (i.e., the object is not required to have a function
|
||||
to return the current state).
|
||||
|
||||
The memento pattern is implemented with three objects: the Originator, a
|
||||
Caretaker and a Memento.
|
||||
@@ -66,9 +66,9 @@ Originator.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
Caretaker.php
|
||||
Ticket.php
|
||||
|
||||
.. literalinclude:: Caretaker.php
|
||||
.. literalinclude:: Ticket.php
|
||||
:language: php
|
||||
:linenos:
|
||||
|
||||
|
48
Behavioral/Memento/State.php
Normal file
48
Behavioral/Memento/State.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Memento;
|
||||
|
||||
class State
|
||||
{
|
||||
const STATE_CREATED = 'created';
|
||||
const STATE_OPENED = 'opened';
|
||||
const STATE_ASSIGNED = 'assigned';
|
||||
const STATE_CLOSED = 'closed';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $state;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static $validStates = [
|
||||
self::STATE_CREATED,
|
||||
self::STATE_OPENED,
|
||||
self::STATE_ASSIGNED,
|
||||
self::STATE_CLOSED,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string $state
|
||||
*/
|
||||
public function __construct(string $state)
|
||||
{
|
||||
self::ensureIsValidState($state);
|
||||
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
private static function ensureIsValidState(string $state)
|
||||
{
|
||||
if (!in_array($state, self::$validStates)) {
|
||||
throw new \InvalidArgumentException('Invalid state given');
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
}
|
@@ -2,161 +2,30 @@
|
||||
|
||||
namespace DesignPatterns\Behavioral\Memento\Tests;
|
||||
|
||||
use DesignPatterns\Behavioral\Memento\Caretaker;
|
||||
use DesignPatterns\Behavioral\Memento\Memento;
|
||||
use DesignPatterns\Behavioral\Memento\Originator;
|
||||
use DesignPatterns\Behavioral\Memento\State;
|
||||
use DesignPatterns\Behavioral\Memento\Ticket;
|
||||
|
||||
/**
|
||||
* MementoTest tests the memento pattern.
|
||||
*/
|
||||
class MementoTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testUsageExample()
|
||||
public function testOpenTicketAssignAndSetBackToOpen()
|
||||
{
|
||||
$originator = new Originator();
|
||||
$caretaker = new Caretaker();
|
||||
$ticket = new Ticket();
|
||||
|
||||
$character = new \stdClass();
|
||||
// new object
|
||||
$character->name = 'Gandalf';
|
||||
// connect Originator to character object
|
||||
$originator->setState($character);
|
||||
// open the ticket
|
||||
$ticket->open();
|
||||
$openedState = $ticket->getState();
|
||||
$this->assertEquals(State::STATE_OPENED, (string) $ticket->getState());
|
||||
|
||||
// work on the object
|
||||
$character->name = 'Gandalf the Grey';
|
||||
// still change something
|
||||
$character->race = 'Maia';
|
||||
// time to save state
|
||||
$snapshot = $originator->getStateAsMemento();
|
||||
// put state to log
|
||||
$caretaker->saveToHistory($snapshot);
|
||||
$memento = $ticket->saveToMemento();
|
||||
|
||||
// change something
|
||||
$character->name = 'Sauron';
|
||||
// and again
|
||||
$character->race = 'Ainur';
|
||||
// state inside the Originator was equally changed
|
||||
$this->assertAttributeEquals($character, 'state', $originator);
|
||||
// assign the ticket
|
||||
$ticket->assign();
|
||||
$this->assertEquals(State::STATE_ASSIGNED, (string) $ticket->getState());
|
||||
|
||||
// time to save another state
|
||||
$snapshot = $originator->getStateAsMemento();
|
||||
// put state to log
|
||||
$caretaker->saveToHistory($snapshot);
|
||||
// no restore to the opened state, but verify that the state object has been cloned for the memento
|
||||
$ticket->restoreFromMemento($memento);
|
||||
|
||||
$rollback = $caretaker->getFromHistory(0);
|
||||
// return to first state
|
||||
$originator->restoreFromMemento($rollback);
|
||||
// use character from old state
|
||||
$character = $rollback->getState();
|
||||
|
||||
// yes, that what we need
|
||||
$this->assertEquals('Gandalf the Grey', $character->name);
|
||||
// make new changes
|
||||
$character->name = 'Gandalf the White';
|
||||
|
||||
// and Originator linked to actual object again
|
||||
$this->assertAttributeEquals($character, 'state', $originator);
|
||||
}
|
||||
|
||||
public function testStringState()
|
||||
{
|
||||
$originator = new Originator();
|
||||
$originator->setState('State1');
|
||||
|
||||
$this->assertAttributeEquals('State1', 'state', $originator);
|
||||
|
||||
$originator->setState('State2');
|
||||
$this->assertAttributeEquals('State2', 'state', $originator);
|
||||
|
||||
$snapshot = $originator->getStateAsMemento();
|
||||
$this->assertAttributeEquals('State2', 'state', $snapshot);
|
||||
|
||||
$originator->setState('State3');
|
||||
$this->assertAttributeEquals('State3', 'state', $originator);
|
||||
|
||||
$originator->restoreFromMemento($snapshot);
|
||||
$this->assertAttributeEquals('State2', 'state', $originator);
|
||||
}
|
||||
|
||||
public function testSnapshotIsClone()
|
||||
{
|
||||
$originator = new Originator();
|
||||
$object = new \stdClass();
|
||||
|
||||
$originator->setState($object);
|
||||
$snapshot = $originator->getStateAsMemento();
|
||||
$object->new_property = 1;
|
||||
|
||||
$this->assertAttributeEquals($object, 'state', $originator);
|
||||
$this->assertAttributeNotEquals($object, 'state', $snapshot);
|
||||
|
||||
$originator->restoreFromMemento($snapshot);
|
||||
$this->assertAttributeNotEquals($object, 'state', $originator);
|
||||
}
|
||||
|
||||
public function testCanChangeActualState()
|
||||
{
|
||||
$originator = new Originator();
|
||||
$first_state = new \stdClass();
|
||||
|
||||
$originator->setState($first_state);
|
||||
$snapshot = $originator->getStateAsMemento();
|
||||
$second_state = $snapshot->getState();
|
||||
|
||||
// still actual
|
||||
$first_state->first_property = 1;
|
||||
// just history
|
||||
$second_state->second_property = 2;
|
||||
$this->assertAttributeEquals($first_state, 'state', $originator);
|
||||
$this->assertAttributeNotEquals($second_state, 'state', $originator);
|
||||
|
||||
$originator->restoreFromMemento($snapshot);
|
||||
// now it lost state
|
||||
$first_state->first_property = 11;
|
||||
// must be actual
|
||||
$second_state->second_property = 22;
|
||||
$this->assertAttributeEquals($second_state, 'state', $originator);
|
||||
$this->assertAttributeNotEquals($first_state, 'state', $originator);
|
||||
}
|
||||
|
||||
public function testStateWithDifferentObjects()
|
||||
{
|
||||
$originator = new Originator();
|
||||
|
||||
$first = new \stdClass();
|
||||
$first->data = 'foo';
|
||||
|
||||
$originator->setState($first);
|
||||
$this->assertAttributeEquals($first, 'state', $originator);
|
||||
|
||||
$first_snapshot = $originator->getStateAsMemento();
|
||||
$this->assertAttributeEquals($first, 'state', $first_snapshot);
|
||||
|
||||
$second = new \stdClass();
|
||||
$second->data = 'bar';
|
||||
$originator->setState($second);
|
||||
$this->assertAttributeEquals($second, 'state', $originator);
|
||||
|
||||
$originator->restoreFromMemento($first_snapshot);
|
||||
$this->assertAttributeEquals($first, 'state', $originator);
|
||||
}
|
||||
|
||||
public function testCaretaker()
|
||||
{
|
||||
$caretaker = new Caretaker();
|
||||
$memento1 = new Memento('foo');
|
||||
$memento2 = new Memento('bar');
|
||||
$caretaker->saveToHistory($memento1);
|
||||
$caretaker->saveToHistory($memento2);
|
||||
$this->assertAttributeEquals(array($memento1, $memento2), 'history', $caretaker);
|
||||
$this->assertEquals($memento1, $caretaker->getFromHistory(0));
|
||||
$this->assertEquals($memento2, $caretaker->getFromHistory(1));
|
||||
}
|
||||
|
||||
public function testCaretakerCustomLogic()
|
||||
{
|
||||
$caretaker = new Caretaker();
|
||||
$result = $caretaker->runCustomLogic();
|
||||
$this->assertEquals('State3', $result);
|
||||
$this->assertEquals(State::STATE_OPENED, (string) $ticket->getState());
|
||||
$this->assertNotSame($openedState, $ticket->getState());
|
||||
}
|
||||
}
|
||||
|
49
Behavioral/Memento/Ticket.php
Normal file
49
Behavioral/Memento/Ticket.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace DesignPatterns\Behavioral\Memento;
|
||||
|
||||
/**
|
||||
* Ticket is the "Originator" in this implementation
|
||||
*/
|
||||
class Ticket
|
||||
{
|
||||
/**
|
||||
* @var State
|
||||
*/
|
||||
private $currentState;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->currentState = new State(State::STATE_CREATED);
|
||||
}
|
||||
|
||||
public function open()
|
||||
{
|
||||
$this->currentState = new State(State::STATE_OPENED);
|
||||
}
|
||||
|
||||
public function assign()
|
||||
{
|
||||
$this->currentState = new State(State::STATE_ASSIGNED);
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
$this->currentState = new State(State::STATE_CLOSED);
|
||||
}
|
||||
|
||||
public function saveToMemento(): Memento
|
||||
{
|
||||
return new Memento(clone $this->currentState);
|
||||
}
|
||||
|
||||
public function restoreFromMemento(Memento $memento)
|
||||
{
|
||||
$this->currentState = $memento->getState();
|
||||
}
|
||||
|
||||
public function getState(): State
|
||||
{
|
||||
return $this->currentState;
|
||||
}
|
||||
}
|
21
Behavioral/Memento/uml/Memento.uml
Normal file
21
Behavioral/Memento/uml/Memento.uml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Diagram>
|
||||
<ID>PHP</ID>
|
||||
<OriginalElement>\DesignPatterns\Behavioral\Memento\Memento</OriginalElement>
|
||||
<nodes>
|
||||
<node x="0.0" y="0.0">\DesignPatterns\Behavioral\Memento\State</node>
|
||||
<node x="313.0" y="252.0">\DesignPatterns\Behavioral\Memento\Memento</node>
|
||||
<node x="0.0" y="252.0">\DesignPatterns\Behavioral\Memento\Ticket</node>
|
||||
</nodes>
|
||||
<notes />
|
||||
<edges />
|
||||
<settings layout="Hierarchic Group" zoom="1.0" x="150.0" y="130.0" />
|
||||
<SelectedNodes />
|
||||
<Categories>
|
||||
<Category>Fields</Category>
|
||||
<Category>Constants</Category>
|
||||
<Category>Methods</Category>
|
||||
</Categories>
|
||||
<VISIBILITY>private</VISIBILITY>
|
||||
</Diagram>
|
||||
|
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Diagram>
|
||||
<ID>PHP</ID>
|
||||
<OriginalElement>\DesignPatterns\Behavioral\Memento\Caretaker</OriginalElement>
|
||||
<nodes>
|
||||
<node x="182.0" y="153.0">\DesignPatterns\Behavioral\Memento\Caretaker</node>
|
||||
<node x="0.0" y="0.0">\DesignPatterns\Behavioral\Memento\Originator</node>
|
||||
<node x="0.0" y="153.0">\DesignPatterns\Behavioral\Memento\Memento</node>
|
||||
</nodes>
|
||||
<notes />
|
||||
<edges />
|
||||
<settings layout="Hierarchic Group" zoom="1.0" x="131.0" y="121.0" />
|
||||
<SelectedNodes />
|
||||
<Categories>
|
||||
<Category>Fields</Category>
|
||||
<Category>Constants</Category>
|
||||
<Category>Constructors</Category>
|
||||
<Category>Methods</Category>
|
||||
</Categories>
|
||||
<VISIBILITY>private</VISIBILITY>
|
||||
</Diagram>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 62 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 164 KiB |
Reference in New Issue
Block a user