Design Pattern: Command Pattern in PHP

Command pattern encapsulates a full request in an object. So, the object can be passed as a parameter or can be queued or saved in log. Later the object can be revived and we get the full request, which can be executed and/or undoned at any point in time.

This article demonstrates Command pattern implementations in PHP. Check the following examples.

Implementation

Follow the steps below for command pattern implementation-

  • Define interface for the command classes. Declare methods for the process(es) execution and undoing the execution.
  • Define command classes, and implement the interface for the classes. Define the declared method for the execution.
  • Create command controller class. Declare an array for holding list of the executed command.
  • In the command controller class define methods for execution and undoing. In the execution implementation call the execution form the command and then add the command to the list. In the undoing process implementation call the undoing method from the command then remove the command from list.

Here is a simple example of Command pattern implementation in PHP-

<?php
// Command pattern implementation in PHP

// Command interface
interface CommandI {
    function execute(): void;
    function undo(): void;
}

// First command class
// Implement CommandI.
// Implementation of the class will be according to our requirement.
class Command1 implements CommandI {
    public function execute(): void {
        echo "Command1: Executing\n";
    }

    public function undo(): void {
        echo "Command1: Undoing\n";
    }
}

// Second command class
// Implement CommandI.
// Implementation of the class will be according to our requirement.
class Command2 implements CommandI {
    public function __construct(private string $prop1) {

    }

    public function execute(): void {
        echo "Command2: executing -- Prop1 value: " . $this->prop1 . "\n";
    }

    public function undo(): void {
        echo "Command2: undoing\n";
    }
}

// Command control
class CommandControl {
    private array $commandList = [];

    public function executeCommand(CommandI $command): void {
        // Call the command execution method
        $command->execute();

        // Add command to list to keep track of the command
        array_push($this->commandList, $command);
    }

    public function removeCommand(CommandI $command): void {
        // Call the undoing process of the command
        $command->undo();

        // Find the command and remove command from list
        foreach ($this->commandList as $key => $currentCommand) {
            if ($command === $currentCommand) {
                unset($this->commandList[$key]);
                break;
            }
        }
    }

    // Undo the last executed command
    public function undo(): void {
        $lastCommand = end($this->commandList);
        $this->removeCommand($lastCommand);
    }
}

// Demo
$commandControl = new CommandControl();

$command1 = new Command1();
$commandControl->executeCommand($command1);

$command2 = new Command2("my first prop");
$commandControl->executeCommand($command2);

$command3 = new Command1();
$commandControl->executeCommand($command3);

$command4 = new Command2("your command 2 prop");
$commandControl->executeCommand($command4);

// Undo specific command
$commandControl->removeCommand($command1);

// Undo last command
$commandControl->undo();

Output of the code will be as below-

Command1: Executing
Command2: executing -- Prop1 value: my first prop
Command1: Executing
Command2: executing -- Prop1 value: your command 2 prop
Command1: Undoing
Command2: undoing

Examples

Here are a few examples of Command pattern implementations.

Example #1: UI Elements

In this example we have some classes for UI Elements. In each class, we have a print method and a method for undoing.

We will finally implement a command controller for the command pattern implementation. Let’s check step-by-step:

Command Interface

  • Create file “UiCommand.php”.
  • Define interface “UiCommand”. This will ensure a common interface for all the UI command classes.
  • Declare methods – “print”, “remove”.
<?php
// UiCommand.php

namespace BigBoxCode\DesignPattern\Command\UiElement;


interface UiCommand {
    function print();
    function remove();
}

Button Concrete Class

  • Create file “ButtonUi.php”.
  • Create class “ButtonUi”.
  • Implement interface “UiCommand” for the class.
  • Define method “print” for printing the button.
  • Define method “undo” for removing the button element.
<?php
// ButtonUi.php

namespace BigBoxCode\DesignPattern\Command\UiElement;

class ButtonUi implements UiCommand {
    public function __construct(private string $name) {
    }

    public function print(): void {
        echo "Printing " . $this->name . " Button\n";
    }

    public function remove(): void {
        echo "Removing " . $this->name . "  Button\n";
    }
}

Input Element Concrete Class

  • Create file “InputUi.php”.
  • Create class “InputUi”.
  • Implement “UiCommand” interface for the class. Define method “print” and “undo” for the interface implementation.
<?php
// InputUi.php

namespace BigBoxCode\DesignPattern\Command\UiElement;


class InputUi implements UiCommand {
    public function print(): void {
        echo "Printing Input\n";
    }

    public function remove(): void {
        echo "Removing Input\n";
    }
}

Table Element Concrete Class

  • Create file “TableUi.php”.
  • Create class “TableUi” and implement “UiCommand” interface for the class.
<?php
// TableUi.php

namespace BigBoxCode\DesignPattern\Command\UiElement;


class TableUi implements UiCommand {
    public function print(): void {
        echo "Printing Table\n";
    }

    public function remove(): void {
        echo "Removing Table\n";
    }
}

UI Element Control Class

  • Create file “UiControl.php”.
  • Define class “UiControl”.
  • Define a private array property named “$commandList” to store the list of executed commands.
  • Define method “addElement”. Accept a param of type “UiCommand”. In the implementation of the method call the “print” method from the command and then add the command in the array “$commandList”.
  • Define method “removeElement”. Accept a command as param, of the “UiCommand”. In the implementation call the “undo” method of the command object and then remove the command from list “$commandList”.
  • Define an “undo” method, which undo the last executed command.
<?php
// UiControl.php

namespace BigBoxCode\DesignPattern\Command\UiElement;


class UiControl {
    private array $commandList = [];

    public function addElement(UiCommand $command) {
        $command->print();

        array_push($this->commandList, $command);
    }

    public function removeElement(UiCommand $command) {
        $command->remove();

        foreach ($this->commandList as $key => $currentCommand) {
            if ($command === $currentCommand) {
                unset($this->commandList[$key]);
                break;
            }
        }
    }

    // Remove the last command
    public function undo() {
        $command = end($this->commandList);

        $this->removeElement($command);
    }
}

Demo

In the client, create an object of “UiControl”. Then create some object of the “ButtonUi”, “TableUi”, “InputUi”, then add those command to the UI controller using “addElement” method. That will print the elemetns.

Once added, then the commands can be removed/undone after that.

<?php
// demo.php

require __DIR__ . '/../../vendor/autoload.php';

use BigBoxCode\DesignPattern\Command\UiElement\ButtonUi;
use BigBoxCode\DesignPattern\Command\UiElement\InputUi;
use BigBoxCode\DesignPattern\Command\UiElement\TableUi;
use BigBoxCode\DesignPattern\Command\UiElement\UiControl;

$uiControl = new UiControl();

$inputUi = new InputUi();
$tableUi = new TableUi();
$buttonUi = new ButtonUi("Submit");

$uiControl->addElement($inputUi);
$uiControl->addElement($tableUi);
$uiControl->addElement($buttonUi);

$uiControl->removeElement($tableUi);

$uiControl->addElement(new ButtonUi("Cancel"));
$uiControl->addElement(new TableUi());
$uiControl->addElement(new InputUi());
$uiControl->addElement(new ButtonUi("Wrong button"));

// Undo commands
$uiControl->undo();
$uiControl->undo();

Output

The following output will be generated.

Printing Input
Printing Table
Printing Submit Button

Removing Table

Printing Cancel Button
Printing Table
Printing Input
Printing Wrong button Button

Removing Wrong button  Button
Removing Input

Example #2: Database Migration

Here we are considering a database migration process. We have some DB migration files and we want those migrations to be executed.

Each of the migrations is a self-contained command. So we can implement the command pattern to perform the migration process.

Migration Interface

  • Create file “Migration.php”.
  • Define interface “Migration”. 
  • Declare methods – “up”, and “down”.
<?php
// Migration.php

namespace BigBoxCode\DesignPattern\Command\DbMigration;


interface Migration {
    function up(): void;
    function down(): void;
}

Migration Files

Let’s create a directory named “migrations“. All our migration field will be in this directory. The filenames can be determined by us.

Each of the migration file will return a migration object, which implements the “Migration” interface.

Let’s create a few migration files in the “Migrations” directory.

Migration File #1 [Command Class]

  • Create file “1234_create_user_table.php” inside the “migrations” directory.
  • Create a class and return an object of the class.
  • Implement interface “Migration” for the class.
  • Define method “up” for the migration process.
  • Define method “down” for migration rollback.
<?php
// migrations/1234_create_user_table.php

use BigBoxCode\DesignPattern\Command\DbMigration\Migration;

return new class implements Migration {
    public function up(): void {
        // dummy migration
        echo "Creating User table\n";

        // put actual migration code here
    }

    public function down(): void {
        // dummy migration

        echo "Dropping User table\n";

        // put actual migration code here
    }
};

Migration File #2 [Command Class]

  • Create file “2345_create_permission_table.php” inside “migrations” directory.
  • Create a class and return an object of the class. Implement interface “Migration” for the class. Define method “up” and “down”.
<?php
// migrations/2345_create_permission_table.php

use BigBoxCode\DesignPattern\Command\DbMigration\Migration;

return new class implements Migration {
    public function up(): void {
        // dummy migration

        echo "Creating Permission table\n";

        // put actual migration code here
    }

    public function down(): void {
        // dummy migration

        echo "Dropping Permission table\n";

        // put actual migration code here
    }
};

Migration File #3 [Command Class]

  • Create file “9921_add_token_to_user_table.php” inside “migrations” directory.
  • Create a class and return an object of the class. Implement interface “Migration” for the class. Define method “up” and “down”.
<?php
// migrations/9921_add_token_to_user_table.php

use BigBoxCode\DesignPattern\Command\DbMigration\Migration;

return new class implements Migration {
    public function up(): void {
        // dummy migration

        echo "Adding Token column from user table\n";

        // put actual migration code here
    }

    public function down(): void {
        // dummy migration

        echo "Dropping Token column from user table\n";

        // put actual migration code here
    }
};

Migration Controller

  • Create file “MigrationControl.php”.
  • Define class “MigrationControl”.
  • Define a private property “$executedMigrationList” array, which is used to store all executed migrations.
  • Define method “migrate” which accept a migration object. In the meothd, check if migration is already ran or not, if not then call the “up” of the migration. Then add the migration object to the “$executedMigrationList”.
  • Define method “rollback”. In the implementation call the “down” method of the migration object passed to the method.
<?php
// MigrationControl.php

namespace BigBoxCode\DesignPattern\Command\DbMigration;

class MigrationControl {
    private array $executedMigrationList = [];

    public function migrate(Migration $migration) {
        // Check if migration is in the list (already executed)
        if (array_search($migration, $this->executedMigrationList) !== false) {
            return;
        }

        // Call 'up' of the migration
        $migration->up();

        // Add migration to the list
        array_push($this->executedMigrationList, $migration);
    }

    public function rollback(Migration $migration) {
        // Check if migration exist in the list or not
        $migrationKey = array_search($migration, $this->executedMigrationList);
        if ($migrationKey === false) {
            return;
        }

        // Call 'down' of the migration
        $migration->down();

        // Remove migration from list
        unset($this->executedMigrationList[$migrationKey]);
    }
}

Demo

First, we are getting all the migration objects. This is not part of the command pattern implementation, these are a general operation that gets the list of files in the “migrations” directory and then generates the object of that migration.

For using the command pattern implementation create a migration controller.

Then we can pass a migration object to the “migrate” or “rollback” method of the controller for running the migration process or rollback.

<?php
// demo.php

require __DIR__ . '/../../vendor/autoload.php';

use BigBoxCode\DesignPattern\Command\DbMigration\FileLoadUtil;
use BigBoxCode\DesignPattern\Command\DbMigration\MigrationControl;

// Load migration classes
// These are general operation, not related to the Command pattern implementation
const MIGRATION_FILE_PATH = __DIR__ . "/migrations";
$migrationFileList = scandir(MIGRATION_FILE_PATH, SCANDIR_SORT_ASCENDING);
$migrations = [];

foreach ($migrationFileList as $fileName) {
    $fullPath = MIGRATION_FILE_PATH . "/" . $fileName;

    if (is_file($fullPath)) {
        $migrations[] = require($fullPath);
    }
}
// Migration loading end

// Create a command controller (MigrationControl) object
$migrationController = new MigrationControl();

// Run migration "up" for all migration
foreach ($migrations as $migration) {
    $migrationController->migrate($migration);
}


// Rollback all migrations
$reverseMigration = array_reverse($migrations);

foreach ($reverseMigration as $migration) {
    $migrationController->rollback($migration);
}

Output

The following output will be generated.

Creating User table
Creating Permission table
Adding Token column from user table


Dropping Token column from user table
Dropping Permission table
Dropping User table

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Command pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.