Commands and Command Handlers
Commands and Command Handlers
Commands and Command Handlers are core-supported objects for structuring actions or events.
From Wikipedia on the Command Pattern:
An object-oriented design pattern where an object encapsulates necessary information for performing an action or triggering an event later. This includes the method name, owning object, and method parameters.
In Concrete CMS, Command (or Message
) objects hold all data for a command, while handlers automatically execute these commands when triggered. Benefits include the ability to activate code from multiple sources (like Dashboard controllers or console commands) and improved code clarity.
Creating and Executing Commands
Commands in Concrete CMS are PHP classes. The terms Command
and Message
are interchangeable, though 'Command' is a specific term in CQRS. These are referred to as Command classes in this context.
Examples of Core Commands
Core commands include:
Concrete\Core\Cache\Command\ClearCacheCommand
Concrete\Core\Page\Command\ReindexPageCommand
Concrete\Core\File\Command\RescanFileCommand
ClearCacheCommand has no properties. ReindexPageCommand stores a page ID, and RescanFileCommand is used with a file ID.
How to Execute a Command
Execute a command by calling executeCommand
on the Application object, often available in controllers as $app
.
$this->app->executeCommand(new ClearCacheCommand());
Alternatively, use the app()
method to get the Application object:
$app = app();
$command = new ReindexPageCommand(1);
$app->executeCommand($command);
Wiring Commands to Handlers
Commands and their corresponding handlers in Concrete CMS:
Concrete\Core\Cache\Command\ClearCacheCommand
→Concrete\Core\Cache\Command\ClearCacheCommandHandler
Concrete\Core\Page\Command\ReindexPageCommand
→Concrete\Core\Page\Command\ReindexPageCommandHandler
Concrete\Core\File\Command\RescanFileCommand
→Concrete\Core\File\Command\RescanFileCommandHandler
Handlers have an __invoke()
method accepting the Command object and __construct()
methods for automatic dependency instantiation. The RescanFileCommandHandler
example demonstrates this:
namespace Concrete\Core\File\Command;
use Concrete\Core\Config\Repository\Repository;
use Concrete\Core\Entity\File\File as FileEntity;
use Concrete\Core\File\Rescanner;
use Doctrine\ORM\EntityManager;
class RescanFileCommandHandler
{
protected $entityManager;
protected $config;
protected $rescanner;
public function __construct(Repository $config, EntityManager $em, Rescanner $rescanner)
{
$this->config = $config;
$this->entityManager = $em;
$this->rescanner = $rescanner;
}
public function __invoke(RescanFileCommand $command)
{
$f = $this->entityManager->find(FileEntity::class, $command->getFileID());
if ($f) {
$fv = $f->getApprovedVersion();
if ($fv) {
$this->rescanner->rescanFile($f);
}
}
}
}
Dependencies are resolved in __construct
and utilized in __invoke()
, which converts command data for the handler's operations.
Auto-Handled Commands
Commands extending Concrete\Core\Foundation\Command\Command
automatically pair with handlers sharing their name, plus "Handler" suffix. For example, ClearCacheCommand
pairs with ClearCacheCommandHandler
.
HandlerAwareCommandInterface
For more flexibility, use Concrete\Core\Foundation\Command\HandlerAwareCommandInterface
. This requires implementing a static getHandler
method to specify the handler.
Configuring Handlers
Manually route commands to handlers using the app.command_handlers
configuration key:
return [
'command_handlers' => [
'Concrete\Core\Cache\Command\ClearCacheCommand' => 'Path\To\Custom\Command\Handler',
],
];
Asynchronous Buses
Commands in Concrete CMS default to synchronous execution but can be set to operate asynchronously.
Asynchronous Execution
Asynchronous commands are queued and processed in the background, typically when an admin uses the Dashboard. For better performance and reliability, activate the manual queue listener.
Implementing Asynchronous Commands
AsyncCommandInterface
Commands implementing Concrete\Core\Foundation\Command\AsyncCommandInterface
are processed asynchronously. This interface, without methods, signals Concrete to handle the command asynchronously.
Configuration
Asynchronous or synchronous processing can also be set via configuration. Use concrete.messenger.routing
in your application/config/concrete.php
file to route commands to the desired transport. Example for routing ClearCacheCommand to asynchronous:
'messenger' => [
'routing' => [
'Concrete\Core\Cache\Command\ClearCacheCommand' => ['async'],
],
],
Explore Symfony Messenger
Concrete CMS's command and transport system is based on Symfony Messenger. While Concrete adds configuration layers for ease of use in its context, the core components remain unaltered from Symfony.
Advanced configurations allow creating custom buses, routing commands creatively, and adding middlewares for logging or other purposes. To fully leverage these capabilities in Concrete's command system, familiarize yourself with the Symfony Messenger documentation.