1.2. Command

1.2.1. Intent

According to the Gang of Four, the Command pattern is a way to “encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations” (Design Patterns: Elements of Reusable Object-Oriented Software, 2013, p. 233).

1.2.2. When to use it?

First of all, the Command pattern will help you to decouple the Invoker from the object that is invoked and knows how to perform an action (the Receiver).

It should be used when you need your application to parameterize objects by an action to perform (it’s a way to implement ‘callbacks’ in object-oriented programming) or if you have to develop a rollback mechanism (undo support).

The Command pattern also lets you queue and execute requests at different times.

1.2.3. Diagram

Created using PhpStorm and yFiles.

Class diagram of the Command pattern

1.2.4. Implementation

Door.php (the Receiver)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php

namespace Phpatterns\Behavioral\Command;

/**
 * Class Door used as the Receiver (part of the Command pattern)
 */
class Door
{
    public function unlock()
    {
        echo 'Door unlocked!';
    }

    public function lock()
    {
        echo 'Door locked!';
    }
}

ConnectedHomeCommandInterface.php (the Command Interface)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php

namespace Phpatterns\Behavioral\Command;

interface ConnectedHomeCommandInterface
{
    /**
     * This is the place where we will deal with the Receiver
     * (calling one or several methods, etc.)
     */
    public function execute();
}

LockDoorCommand.php (a concrete Command)

 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
<?php

namespace Phpatterns\Behavioral\Command\ConnectedHomeCommand;

use Phpatterns\Behavioral\Command;

class LockDoorCommand implements Command\ConnectedHomeCommandInterface
{
    /** @var Command\Door */
    private $door;

    /**
     * @param Command\Door $door
     */
    public function __construct(Command\Door $door)
    {
        $this->door = $door;
    }

    /**
     * Tell the Receiver (the Door) to lock itself
     */
    public function execute()
    {
        $this->door->lock();
    }
}

UnlockDoorCommand.php (another concrete Command)

 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
<?php

namespace Phpatterns\Behavioral\Command\ConnectedHomeCommand;

use Phpatterns\Behavioral\Command;

class UnlockDoorCommand implements Command\ConnectedHomeCommandInterface
{
    /** @var Command\Door */
    private $door;

    /**
     * @param Command\Door $door
     */
    public function __construct(Command\Door $door)
    {
        $this->door = $door;
    }

    /**
     * Tell the Receiver (the Door) to unlock itself
     */
    public function execute()
    {
        $this->door->unlock();
    }
}

ControlStation.php (the Invoker)

 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
<?php

namespace Phpatterns\Behavioral\Command;

/**
 * Class ControlStation used as the Invoker (part of the Command pattern)
 */
class ControlStation
{
    /** @var ConnectedHomeCommandInterface */
    private $command;

    /**
     * If you need a list of commands, you should use a 'addCommand' method
     * instead of the constructor
     * @param ConnectedHomeCommandInterface $command
     */
    public function __construct(ConnectedHomeCommandInterface $command)
    {
        $this->command = $command;
    }

    /**
     * Here, we execute an action through the a Command object
     * (ControlStation and Door objects are decoupled)
     */
    public function invoke()
    {
        $this->command->execute();
    }

    /**
     * @param ConnectedHomeCommandInterface $command
     */
    public function setCommand($command)
    {
        $this->command = $command;
    }
}

1.2.5. Tests

CommandTest.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
<?php

namespace Test\Phpatterns\Behavioral\Command;

use Phpatterns\Behavioral\Command;

class CommandTest extends \PHPUnit_Framework_TestCase
{
    /** @var Command\Door */
    private $door; //the Receiver

    protected function setUp()
    {
        $this->door = new Command\Door();
    }

    public function testLockCommand()
    {
        //the Invoker
        $controlStation = new Command\ControlStation(
            new Command\ConnectedHomeCommand\LockDoorCommand($this->door)
        );

        $this->expectOutputString('Door locked!');
        $controlStation->invoke();
    }

    public function testUnlockCommand()
    {
        //the Invoker
        $controlStation = new Command\ControlStation(
            new Command\ConnectedHomeCommand\UnlockDoorCommand($this->door)
        );

        $this->expectOutputString('Door unlocked!');
        $controlStation->invoke();
    }
}