The Command Pattern Explained
Definition
The Command pattern encapsulates a request as an object, letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
Components
- Command: Interface with
execute()method - ConcreteCommand: Implements command logic
- Invoker: Triggers commands
- Receiver: The object that performs the actual work
Example: Product Import Command
namespace Vendor\Import\Command;
interface CommandInterface
{
public function execute(): void;
public function undo(): void;
}
namespace Vendor\Import\Command\Concrete;
class ImportProductCommand implements \Vendor\Import\Command\CommandInterface
{
private ?int $importedProductId = null;
public function __construct(
private \Magento\Catalog\Api\ProductRepositoryInterface $productRepo,
private \Magento\Catalog\Api\Data\ProductInterfaceFactory $productFactory,
private array $productData
) {}
public function execute(): void
{
$product = $this->productFactory->create();
$product->setSku($this->productData['sku']);
$product->setName($this->productData['name']);
$product->setPrice($this->productData['price']);
$savedProduct = $this->productRepo->save($product);
$this->importedProductId = $savedProduct->getId();
}
public function undo(): void
{
if ($this->importedProductId !== null) {
$product = $this->productRepo->getById($this->importedProductId);
$this->productRepo->delete($product);
$this->importedProductId = null;
}
}
}
// Invoker: command queue with undo support
class ImportProcessor
{
private array $history = [];
public function execute(CommandInterface $command): void
{
$command->execute();
$this->history[] = $command;
}
public function undoLast(): void
{
if (!empty($this->history)) {
$command = array_pop($this->history);
$command->undo();
}
}
public function undoAll(): void
{
while (!empty($this->history)) {
$this->undoLast();
}
}
}
// Usage
$processor = new ImportProcessor();
$processor->execute(new ImportProductCommand($repo, $factory, ['sku' => 'P1', 'name' => 'Product 1']));
$processor->execute(new ImportProductCommand($repo, $factory, ['sku' => 'P2', 'name' => 'Product 2']));
// Undo last import
$processor->undoLast(); // Removes Product 2
Each command encapsulates: what to do (execute), how to undo it (undo), and the data needed.
Magento Console Commands
Magento CLI Commands as Command Pattern
Magento's CLI commands (bin/magento) follow the Command pattern:
namespace Vendor\Module\Console;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
class CleanCacheCommand extends Command
{
protected function configure(): void
{
$this->setName('vendor:clean-cache')
->setDescription('Clean vendor-specific cache')
->addOption('store', null, InputOption::VALUE_REQUIRED, 'Store ID');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$storeId = $input->getOption('store');
try {
$this->cacheCleaner->clean($storeId);
$output->writeln('<info>Cache cleaned successfully</info>');
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln('<error>' . $e->getMessage() . '</error>');
return Command::FAILURE;
}
}
}
Register in di.xml:
<type name="Symfony\Component\Console\Command\Command">
<plugin name="vendor_console_command" type="Vendor\Module\Plugin\ConsolePlugin"/>
</type>
The command object encapsulates the request (CLI arguments), the execution logic, and can be queued or logged.
Action Controllers as Commands
Magento Front Controllers and Action Controllers
Each HTTP request maps to a Command via the front controller:
// The route determines which command (controller) handles the request
// Vendor/Module/Controller/Adminhtml/Entity/Save.php
namespace Vendor\Module\Controller\Adminhtml\Entity;
class Save extends \Magento\Backend\App\Action
{
public function __construct(
\Magento\Backend\App\Action\Context $context,
private \Vendor\Module\Api\EntityRepositoryInterface $repo,
private \Vendor\Module\Api\Data\EntityInterfaceFactory $entityFactory
) {
parent::__construct($context);
}
public function execute()
{
$data = $this->getRequest()->getPostValue();
try {
$entity = $this->entityFactory->create();
$entity->setName($data['name']);
$this->repo->save($entity);
$this->messageManager->addSuccessMessage(__('Entity saved successfully.'));
} catch (\Exception $e) {
$this->messageManager->addErrorMessage($e->getMessage());
}
return $this->_redirect('*/*/index');
}
}
The action controller is a command that:
- Receives the request (command parameters)
- Validates input
- Executes business logic via services
- Returns a response
This separation allows different "invokers" (CLI, API, web) to use the same business logic through different command interfaces.
Quiz
1. What is the main benefit of the Command pattern?
2. In the Command pattern, who triggers the command?
3. Magento CLI commands (bin/magento) follow which pattern?
Flashcards
Question
What does the Command pattern encapsulate?
Click to reveal answer
Answer
Requests as objects, enabling queuing, undo, logging, and parameterization
Question
What are the Command pattern components?
Click to reveal answer
Answer
Command (interface), ConcreteCommand, Invoker (triggers), Receiver (does work)
Question
Magento CLI commands are examples of?
Click to reveal answer
Answer
Command pattern — each command encapsulates a CLI request
Revision Notes
Key Takeaways
- 1. Command pattern encapsulates requests as objects
- 2. Enables undo/redo, queuing, logging, and parameterization
- 3. Components: Command interface, ConcreteCommand, Invoker, Receiver
- 4. Magento CLI commands and action controllers follow the Command pattern
- 5. Each command knows how to execute and optionally undo its action
Interview Tips
- • Explain undo/redo: each command stores state to reverse its action
- • Give Magento examples: CLI commands, action controllers, import/export
- • Discuss when commands add unnecessary complexity (simple CRUD)
Cheat Sheet
Command Pattern:
Command → interface { execute(); undo(); }
Invoker → triggers commands (queue, history)
Receiver → does the actual work
Client → creates commands with receiver + data
Magento:
CLI: bin/magento vendor:command
Web: Controller\Action\Entity\Save
Both encapsulate request → execute → response