Purpose
The purpose of the design pattern
To encapsulate invocation and decoupling.
We have an Invoker and a Receiver. This pattern uses a "Command" to delegate the method call against the Receiver and presents the same method "execute". Therefore, the Invoker just knows to call "execute" to process the Command of the client. The Receiver is decoupled from the Invoker.
The second aspect of this pattern is the undo(), which undoes the method execute(). Command can also be aggregated to combine more complex commands with minimum copy-paste and relying on composition over inheritance.
Essentially, an invoker is given a command to run. A command delegates method calls to a receiver. As the receiver is decoupled it can be switched out easily in future without making any changes to the command class.
Examples
Examples of how the design pattern can be used
- A text editor : all events are commands which can be undone, stacked and saved.
- Big CLI tools use subcommands to distribute various tasks and pack them in "modules", each of these can be implemented with the Command pattern (e.g. vagrant)
UML
UML design pattern diagram
Code
Code snippets
Command
Command Interface which concrete commands implement. Enforcing the execute method to be present.
namespace DesignPatterns\Behavioral\Command;
interface Command
{
/**
* this is the most important method in the Command pattern,
* The Receiver goes in the constructor.
*/
public function execute();
}
UndoableCommand
UndoableCommand Interface which concrete commands implement. Enforcing the undo and execute methods (as the interface extends another interface) to be present.
namespace DesignPatterns\Behavioral\Command;
interface UndoableCommand extends Command
{
/**
* This method is used to undo change made by command execution
*/
public function undo();
}
AddMessageDateCommand
AddMessageDateCommand implements UndoableCommand (meaning it must contain undo and execute methods). The construct accepts a class which implements the Receiver class. – come back to this.
namespace DesignPatterns\Behavioral\Command;
/**
* This concrete command tweaks receiver to add current date to messages
* invoker just knows that it can call "execute"
*/
class AddMessageDateCommand implements UndoableCommand
{
/**
* Each concrete command is built with different receivers.
* There can be one, many or completely no receivers, but there can be other commands in the parameters.
*/
public function __construct(private Receiver $output)
{
}
/**
* Execute and make receiver to enable displaying messages date.
*/
public function execute()
{
// sometimes, there is no receiver and this is the command which
// does all the work
$this->output->enableDate();
}
/**
* Undo the command and make receiver to disable displaying messages date.
*/
public function undo()
{
// sometimes, there is no receiver and this is the command which
// does all the work
$this->output->disableDate();
}
}
HelloCommand
HelloCommand implements Command (meaning it must contain the execute method). The construct accepts the Receiver class. The command then calls methods from the Receiver class.
namespace DesignPatterns\Behavioral\Command;
/**
* This concrete command calls "print" on the Receiver, but an external
* invoker just knows that it can call "execute"
*/
class HelloCommand implements Command
{
/**
* Each concrete command is built with different receivers.
* There can be one, many or completely no receivers, but there can be other commands in the parameters
*/
public function __construct(private Receiver $output)
{
}
/**
* execute and output "Hello World".
*/
public function execute()
{
// sometimes, there is no receiver and this is the command which does all the work
$this->output->write('Hello World');
}
}
Invoker
The Invoker class sets (via setCommand) and runs a given command (via run).
namespace DesignPatterns\Behavioral\Command;
/**
* Invoker is using the command given to it.
* Example : an Application in SF2.
*/
class Invoker
{
private Command $command;
/**
* in the invoker we find this kind of method for subscribing the command
* There can be also a stack, a list, a fixed set ...
*/
public function setCommand(Command $cmd)
{
$this->command = $cmd;
}
/**
* executes the command; the invoker is the same whatever is the command
*/
public function run()
{
$this->command->execute();
}
}
Receiver
Receiver is a specific service with its own contract (no need for an interface) and can only be concrete.
namespace DesignPatterns\Behavioral\Command;
/**
* Receiver is a specific service with its own contract and can be only concrete.
*/
class Receiver
{
private bool $enableDate = false;
/**
* @var string[]
*/
private array $output = [];
public function write(string $str)
{
if ($this->enableDate) {
$str .= ' [' . date('Y-m-d') . ']';
}
$this->output[] = $str;
}
public function getOutput(): string
{
return join("\n", $this->output);
}
/**
* Enable receiver to display message date
*/
public function enableDate()
{
$this->enableDate = true;
}
/**
* Disable receiver to display message date
*/
public function disableDate()
{
$this->enableDate = false;
}
}
CommandTest
The Invoker invokes a Command. The command delegates method calls to its Receiver.
class CommandTest extends TestCase
{
public function testInvocation()
{
$invoker = new Invoker();
$receiver = new Receiver();
$invoker->setCommand(new HelloCommand($receiver));
$invoker->run();
$this->assertSame('Hello World', $receiver->getOutput());
}
}
UndoableCommandTest
The Invoker invokes a Command. The command delegates method calls to its Receiver.
namespace DesignPatterns\Behavioral\Command\Tests;
use DesignPatterns\Behavioral\Command\AddMessageDateCommand;
use DesignPatterns\Behavioral\Command\HelloCommand;
use DesignPatterns\Behavioral\Command\Invoker;
use DesignPatterns\Behavioral\Command\Receiver;
use PHPUnit\Framework\TestCase;
class UndoableCommandTest extends TestCase
{
public function testInvocation()
{
$invoker = new Invoker();
$receiver = new Receiver();
$invoker->setCommand(new HelloCommand($receiver));
$invoker->run();
$this->assertSame('Hello World', $receiver->getOutput());
$messageDateCommand = new AddMessageDateCommand($receiver);
$messageDateCommand->execute();
$invoker->run();
$this->assertSame("Hello World\nHello World [" . date('Y-m-d') . ']', $receiver->getOutput());
$messageDateCommand->undo();
$invoker->run();
$this->assertSame("Hello World\nHello World [" . date('Y-m-d') . "]\nHello World", $receiver->getOutput());
}
}