Decorating Commands

Dev Diary

Last summer we started working on a new project that involves the e-commerce framework Sylius. As you might know, it makes use of state machines. A lot. This is fine as it’s easy to hook into them and that’s also what we did.

Michael Zangerle
August 3, 2022
Reading time
2 Minutes

In this project, there is also an ERP system involved that needs to be informed of e.g. new orders. When a new order gets created in Sylius we also want to push this information to the ERP system. We created a state machine listener and in the push method we call our custom ErpOderManager::push logic which then does all the necessary things to forward the order to the ERP system.

<?php declare(strict_types=1); namespace App\Sync\StateMachineListener; // ... final class PushOrderOnCreate { private ErpOrderManager $erpOrderManager; public function __construct(ErpOrderManager $erpOrderManager) { $this->erpOrderManager = $erpOrderManager; } public function push(OrderInterface $order): void { $this->erpOrderManager->push($order); } }

Register it as a service (in the service.yaml) and let the statemachine know about it:

winzou_state_machine: sylius_order: callbacks: after: push_to_erp: on: "create" do: [ "@App\\Sync\\StateMachineListener\\PushOrderOnCreate", "push" ] args: [ "object" ]

Perfect! This works like a charm, is easy to understand and consists of very little code.

But …

… unfortunately, this logic gets also executed when the fixtures get loaded. That’s something we don’t want. We want to differentiate between these two cases. Now we could have started fiddling around with environment variables, created a few helper scripts, enable all devs to connect to the review apps / testing environments; but there were quite a few “what ifs” coming up and just didn’t feel like a good thing to do (error-prone, complicated, cumbersome).

Service decoration

It turns out that the command to load fixtures in Sylius sylius:fixtures:load is a service and that’s a perfect use-case for the service decorator pattern in Symfony, as we want to execute something before and after calling it.

Therefore we created a new command to load fixtures, which just calls the original command. But before that, we can now check if the communication to the ERP is enabled, if so disable it and re-enable it after loading the fixtures with the normal Sylius command. By using the same name for the command nothing changed in the way normal Sylius applications load fixtures as well.

<?php declare(strict_types=1); namespace App\Command; use Sylius\Bundle\FixturesBundle\Command\FixturesLoadCommand; // ... final class SyliusFixtureDecoratorCommand extends Command { protected static $defaultName = 'sylius:fixtures:load'; // ... public function __construct(FixturesLoadCommand $command, ErpClientInterface $erpClient) { parent::__construct(); $this->command = $command; $this->erpClient = $erpClient; } protected function configure(): void { $this ->setName('sylius:fixtures:load') ->setDescription('Loads fixtures from given suite') ->addArgument('suite', InputArgument::OPTIONAL, 'Suite name', 'default'); } protected function execute(InputInterface $input, OutputInterface $output): int { $erpCommunicationStatus = $this->erpClient->isErpCommunicationEnabled(); $this->erpClient->disableErpCommunication(); $this->command->setApplication($this->getApplication()); $exitCode = $this->command->execute($input, $output); if (true === $erpCommunicationStatus) { $this->erpClient->enableErpCommunication(); } return $exitCode; } }

Now the only thing that remains to be done is to add the decoration configuration in the service.yaml.

App\Command\SyliusFixtureDecoratorCommand: decorates: Sylius\Bundle\FixturesBundle\Command\FixturesLoadCommand arguments: - '@.inner' - '@App\Sync\Erp\ErpClientInterface'

That’s all there is to it! If you have any questions or would like to discuss details, please type us a message. 

More of that?

automated release notes
Dev Diary
Automated release notes
November 2, 2022 | 5 Min.

Contact form

*Required field
*Required field
*Required field
*Required field
We protect your privacy

We keep your personal data safe and do not share it with third parties. You can find out more about this in our privacy policy.