In my example, it is an elevator that can have the following states:
- Open (Open)
- Closed (Close)
- In Motion (Move)
- Stand (Stop)
From this, the following interface can be derived
<?php
namespace Elevator;
use Elevator\State\Close;
use Elevator\State\Move;
use Elevator\State\Open;
use Elevator\State\Stop;
interface ElevatorStateInterface
{
public function open(): Open;
public function close(): Close;
public function move(): Move;
public function stop(): Stop;
}
Now we need a class ElevatorState that implements the interface
<?php
namespace Elevator;
use Elevator\Exception\IllegalStateTransitionException;
use Elevator\State\Close;
use Elevator\State\Move;
use Elevator\State\Open;
use Elevator\State\Stop;
class ElevatorState implements ElevatorStateInterface
{
public function close(): Close
{
throw new IllegalStateTransitionException();
}
public function move(): Move
{
throw new IllegalStateTransitionException();
}
public function open(): Open
{
throw new IllegalStateTransitionException();
}
public function stop(): Stop
{
throw new IllegalStateTransitionException();
}
}
By default, all methods throw an exception. In my case it is an IllegalStateTransitionException which inherits from LogicException.
Now we can implement the individual states. In this example, the Move state.
<?php
namespace Elevator\State;
use Elevator\ElevatorState;
class Move extends ElevatorState
{
public function move(): Move
{
return new Move();
}
public function stop(): Stop
{
return new Stop();
}
}
As you can see, not all methods are implemented from ElevatorState. Exactly this, which are not allowed for the current state.
The class Elevator
<?php
namespace Elevator;
use Elevator\State\Open;
use Elevator\State\Stop;
class Elevator
{
protected $state;
public function getState(): ElevatorState
{
return $this->state;
}
public function setState(ElevatorStateInterface $state): void
{
$this->state = $state;
}
public function __construct()
{
$this->setState(new Stop());
}
public function isOpen(): bool
{
return $this->state instanceof Open;
}
public function open(): void
{
$this->setState($this->state->open());
}
public function close(): void
{
$this->setState($this->state->close());
}
public function move(): void
{
$this->setState($this->state->move());
}
public function stop(): void
{
$this->setState($this->state->stop());
}
}
Now, you can create a new Elevator instance.
$elevator = new Elevator\Elevator();
$elevator->open();
$elevator->close();
$elevator->move();
$elevator->stop();
This works, because each step is allowed by its predecessor as the next step.
$elevator = new Elevator\Elevator();
$elevator->open();
$elevator->move();
PHP Fatal error: Uncaught Elevator\Exception\IllegalStateTransitionException
It doesn’t work because “Move” is not allowed when the elevator is “Open”.
The complete source of this example is available on GitHub.
Check out the source code and run
composer install && ./vendor/bin/phpunit tests