PHP7 Memento

This commit is contained in:
Dominik Liebler
2016-09-22 10:39:03 +02:00
parent 64e21e8581
commit 01007ec5a8
11 changed files with 1096 additions and 574 deletions

View File

@@ -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();
}
}

View File

@@ -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()
{

View File

@@ -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();
}
}

View File

@@ -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:

View 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;
}
}

View File

@@ -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());
}
}

View 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;
}
}

View 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>

View File

@@ -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