
Memento Design Pattern
The memento design pattern is a simple pattern to save and restore an objects state while preserving encapsulation. In simple terms this means you could implement “undo” functionality just like you’d find in a text editor or many other programs.
UML Diagram

There are three classes involved in the Memento Design Pattern. These are the Originator, Memento and CareTaker classes. Of course there is also the fourth part not shown in the diagram; your app.
The Originator Class
The Originator class is the class/model you’re developing which needs to be able to save and restore its state. For instance, if you’re developing a Person class to model people this is your originator.
In the UML diagram above you can see that the class has its own set of attributes (properties) and methods (operations). In addition it has two additional functions: createState(): Memento and restoreState(Memento): void.
As you can see, the Originator class has a Memento Dependency.
When createState is called it passes all of its state information to the Memento, typically in its constructor:
public function createState(): Memento {
return new Memento($this->name, $this->surname, $this->bio);
}
createState returns an instance of Memento (your state object, discussed next).
When restoreState is called it is passed an instance of a previously created Memento which contains the state of your object at a certain point. This state object is used to restore the state of your object.
public function restoreState(Memento $state): void {
$this->name = $state->getName();
$this->surname = $state->getSurname();
$this->bio = $state->getBio();
}
The Memento Class
The Memento Class is where an object’s state is stored. Note that each instance of the Memento stores only one snapshot of the state at a given time. In other words, Memento does not contain a list/array of states, it is itself a single state object.
The Memento class does not allow modifications to the stored state (no setter methods), preventing unintended side effects.
As mentioned previously, Memento typically receives state via its constructor method:
public function __construct(private string $name, private string $surname, private string $bio) {}
Remember our first paragraph above said “… while preserving encapsulation…”. Part of the reason for this is because we store the state in the Memento only through its constructor. There are typically no methods for setting state as this would mean that state could be altered in the ‘history’ and this would break encapsulation.
The Memento then has functions to get the state:
public function getName(): string {
return $this->name;
}
public function getSurname(): string {
return $this->surname;
}
public function getBio(): string {
return $this->bio;
}
That’s about how simple it is! The memento really doesn’t do anything other than store state (receives variables) and return state (has getters).
The Caretaker Class
The Caretaker class stores memento objects in a FILO fashion. The Caretaker class does not modify the stored Mementos, it only keeps them in order. Much like an array or list it has two methods:
- Push(Memento): void
- Pop(): Memento
The Push method takes an instance of Memento which contains our state and pushes it onto our stack (in our case, its just an array). Optionally it may contain some logic to limit the number of items that can be pushed onto the stack, with whatever logic you choose to maintain this max number. In our example we move out the oldest history to make room for the new history.
The Pop method returns the last Memento instance to us and removes it from the stack.
public function push(PersonState $state): void {
if (count($this->list) >= $this->maxSize) {
array_shift($this->list); // Remove oldest history
}
$this->list[] = $state;
}
public function pop(): ?PersonState {
return array_pop($this->list); // Safer than unset()
}
Application
Now that we have our Memento Design Pattern in place we can use this effectively in our application.
Our application creates an instance of our Originator class (in our case, its called Person) as well as our CareTaker (in our case we’ve called it PersonHistory).
Saving State
At some stage according to some business logic our application calls the createState() function on our Person class (Originator). The createState() function returns an instance of a PersonState class (Memento) and then passes that to our PersonHistory classes push() method:
$history = new PersonHistory();
$john = new Person("One", "Simms", "He is a cool guy");
$save = $john->createState();
$history->push($save);
// The above two lines can be condensed to:
// $history->push($john->createState());
That’s all there is to saving state!
Restoring State
If and when your application needs to perform an “undo” it calls the pop() method on the PersonHistory class (CareTaker). The pop() method returns the LAST instance of the PersonHistory class (Memento) in the stack, and removes that instance from the stack.
The state (PersonHistory) object is then passed to our Person object’s (Originator) restoreState() function which performs the restore logic as shown above in the Originator section.
$state = $history->pop();
$john->restoreState($state);
Of course what’s not been shown in this example is how to manage empty stacks etc.
If you need to go back several times you can call the $history->pop() multiple times:
$state = $history->pop();
// $john->restoreState($state); // <-- optional, maybe we know we need to go back 3 places
$state = $history->pop();
// $john->restoreState($state); // <-- optional, maybe we know we need to go back 3 places
$state = $history->pop();
$john->restoreState($state);
Save / Restore Partial Data?
One common question is if we can save or restore only some state instead of all of the state of an object. While the technical answer is yes, the principled answer is no, you should not. The purpose of Memento is to restore an Object back to a previous state as it was at that time. If your application is restoring state partially this could result in a state which is different to what it actually was.
For instance, imagine a situation where we save our state with a name of “John” and a bio which reads: “John is the greatest”. We decide to only store partial data so we store only the bio. Later on we change the name to Peter and then sometime after that we save our partial state.
When we restore our information will be inconsistent. It will have a name property of Peter, but a bio which reads “John is the greatest”.
If you’re concerned about huge state objects you should consider asking yourself if your class needs to be refactored; it could be doing too many things!
Complete PHP Memento Sample Code:
PersonState.php (Memento)
<?php
class PersonState
{
public function __construct(private string $name, private string $surname, private string $bio) {}
public function getName(): string {
return $this->name;
}
public function getSurname(): string {
return $this->surname;
}
public function getBio(): string {
return $this->bio;
}
}
PersonHistory.php (CareTaker)
<?php
declare(strict_types=1);
require_once dirname(__FILE__) . "/PersonState.php";
class PersonHistory
{
private array $list = [];
private int $maxSize = 50; // Optional: Prevent excessive memory usage
public function push(PersonState $state): void {
if (count($this->list) >= $this->maxSize) {
array_shift($this->list); // Remove oldest history
}
$this->list[] = $state;
}
public function pop(): ?PersonState {
return array_pop($this->list); // Safer than unset()
}
}
Person.php (Originator):
<?php
declare(strict_types=1);
require_once dirname(__FILE__) . "/PersonState.php";
class Person
{
public function __construct(private string $name, private string $surname, private string $bio) {}
public function createState(): PersonState {
return new PersonState($this->name, $this->surname, $this->bio);
}
public function restoreState(PersonState $state): void {
$this->name = $state->getName();
$this->surname = $state->getSurname();
$this->bio = $state->getBio();
}
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
public function getSurname(): string {
return $this->surname;
}
public function setSurname(string $surname): void {
$this->surname = $surname;
}
public function getBio(): string {
return $this->bio;
}
public function setBio(string $bio): void {
$this->bio = $bio;
}
public function __toString(): string {
return "{$this->name} {$this->surname}, {$this->bio}";
}
}
Application:
<?php
declare(strict_types=1);
require_once dirname(__FILE__) . "/Person.php";
require_once dirname(__FILE__) . "/PersonHistory.php";
$history = new PersonHistory();
$john = new Person("One", "Simms", "He is a cool guy");
$history->push($john->createState());
print "<p>1: ".$john."</p>";
$john->setName("Two");
$history->push($john->createState());
print "<p>2: ".$john."</p>";
$john->setName("Three");
$john->setSurname("Davids");
$john->setBio("This guy is just the best");
$history->push($john->createState());
print "<p>3: ".$john."</p>";
$john->setName("Four");
$history->push($john->createState());
print "<p>4: ".$john."</p>";
$john->setName("Five");
$john->setSurname("Barry");
$john->setBio("He's ok");
$history->push($john->createState());
print "<p>5: ".$john."</p>";
print "<p><hr>WALK BACK<hr></p>";
while ($state = $history->pop()) {
$john->restoreState($state);
print "<p>Undo: ".$john."</p>";
}

As you can see from our screenshot above our application creates a Person and a PersonHistory object and then as changes are made to the Person object they are pushed to the PersonHistory object.
After some time we start calling the PersonHistory’s pop() method and then calling the Person Object’s restoreState method with the state object.
John, a seasoned Freelance Full Stack Developer based in South Africa, specialises in delivering bespoke solutions tailored to your needs. With expertise in back end languages and frameworks, PHP, Laravel and Golang and Front end frame words Vue3, Nuxt3 as well as Angular, I am equipped to tackle any project, ensuring robust, scalable, and cutting-edge outcomes.
My comprehensive skill set enables me to provide exceptional freelance services both remotely and in person. Whether you’re seeking to develop an innovative application or require meticulous refinement of existing systems, I am dedicated to elevating your digital presence through unparalleled technical prowess and strategic development methodologies. Let’s connect to transform your vision into reality.