1.6. Memento¶
1.6.1. Intent¶
According to the Gang of Four, the Memento pattern is defined like that: “Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later” (Design Patterns: Elements of Reusable Object-Oriented Software, 2013, p. 283).
1.6.2. When to use it?¶
Memento pattern should be used to implement undo / redo mechanisms (you save a snapshot of an object’s state and you restore or not that state later).
1.6.3. Diagram¶
Created using PhpStorm and yFiles.
1.6.4. Implementation¶
Order.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | <?php
namespace Phpatterns\Behavioral\Memento;
/**
* Class Order
* Used as the Originator (part of the Memento pattern).
* It's the state of that object that we want to save and restore.
*/
class Order
{
/** @var string */
private $customerName;
/** @var OrderState */
private $state;
public function __construct($customerName, OrderState $state)
{
$this->customerName = $customerName;
$this->state = $state;
}
/**
* @return Memento
*/
public function exportState()
{
return new Memento(clone $this->state);
}
/**
* @param Memento $memento
*/
public function restoreState(Memento $memento)
{
$this->state = $memento->getState();
}
/**
* @return OrderState
*/
public function getState()
{
return $this->state;
}
/**
* @param OrderState $state
*/
public function setState(OrderState $state)
{
$this->state = $state;
}
}
|
OrderState.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <?php
namespace Phpatterns\Behavioral\Memento;
class OrderState
{
const ORDER_STATUS_PENDING = 1; //default
const ORDER_STATUS_PAID = 2;
const ORDER_STATUS_PREPARED = 3;
const ORDER_STATUS_SHIPPED = 4;
const ORDER_STATUS_DELIVERED = 5;
const ORDER_STATUS_CANCELED = 6;
/** @var int */
private $status;
/** @var \DateTime */
private $created;
public function __construct($status = self::ORDER_STATUS_PENDING)
{
$this->status = $status;
$this->created = new \DateTime('now');
}
}
|
Memento.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <?php
namespace Phpatterns\Behavioral\Memento;
class Memento
{
/** @var OrderState */
private $state;
public function __construct(OrderState $state)
{
$this->state = $state;
}
/**
* @return OrderState
*/
public function getState()
{
return $this->state;
}
}
|
History.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <?php
namespace Phpatterns\Behavioral\Memento;
/**
* Class History
* Used as the Caretaker (part of the Memento pattern)
*/
class History
{
/** @var Memento[] */
private $mementos;
public function __construct()
{
$this->mementos = [];
}
/**
* Add a Memento to the history stack
* @param Memento $memento
*/
public function addMemento(Memento $memento)
{
$this->mementos[] = $memento;
}
/**
* Retrieve the last Memento inserted
* @return Memento
*/
public function popMemento()
{
return array_pop($this->mementos);
}
}
|
1.6.5. Tests¶
MementoTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <?php
namespace Test\Phpatterns\Behavioral\Memento;
use Phpatterns\Behavioral\Memento;
class MementoTest extends \PHPUnit_Framework_TestCase
{
/** @var Memento\Order */
private $order;
protected function setUp()
{
//the Originator
$this->order = new Memento\Order(
'Mike Tyson',
new Memento\OrderState(Memento\OrderState::ORDER_STATUS_PENDING)
);
}
public function testUndoMechanism()
{
//the Caretaker
$history = new Memento\History();
$history->addMemento($this->order->exportState());
//storing current state and changing Order's state
$previousState = $this->order->getState();
$this->order->setState(new Memento\OrderState(Memento\OrderState::ORDER_STATUS_PAID));
$this->assertNotEquals($previousState, $this->order->getState());
//restoring previous state
$this->order->restoreState($history->popMemento());
$this->assertEquals($previousState, $this->order->getState());
}
public function testMementoStateInstanceIsDifferentFromOrderState()
{
//the Caretaker
$history = new Memento\History();
$history->addMemento($this->order->exportState());
$this->assertNotSame(
$history->popMemento()->getState(),
$this->order->getState()
);
}
}
|