Extending Sylius Services with Decorators

Pedro Carlos Abreu Jiménez
2 min readMar 19, 2019

--

Sylius documentation has many ways to customize main parts on Sylius framework. For most of Sylius developers there are no problem extending any part of it. But I’ve realized for new comers could be confuse.

Almost everything on Sylius is a service, so you can inject it in a controller or other services. They usually have 2 ids, the legacy name by vendor dot function like sylius.calculator.product_variant_price and the based on the interface that implements Sylius\Component\Core\Calculator\ProductVariantPriceCalculatorInterface (The last one is an alias for the first one, so you only have to change the first which is you can use for autowire your services).

So if you want to override the sylius.calculator.product_variant_price service you simple could create your own service (App\Calculator\MyCalculator for example) by implementing its interface
Sylius\Component\Core\Calculator\ProductVariantPriceCalculatorInterface and name your service
sylius.calculator.product_variant_price or just create an alias:

[yaml]
# config/services.yaml
services:
# ..
sylius.calculator.product_variant_price: '@App\Calculator\MyCalculator'

That is a simple solutions but has some problems, like you are losing the original service and you may only want to check some attribute before execute the calculator (just for example).

We could do 3 things at this moments.

1. Extend the original class and override what we want. Impossible: Sylius has made its services ‘final class’ so we can not extend them.
2. Copy + Paste the original code. Problem: You have to be on alert to recopy all the code on code changes of fixes.
3. Decorate the original service: Best solution: You only have to do it :).

Service Decoration on Symfony

We recoment to use this pattern by follow the official documentation of Service Decoration on Symfony. With it we can inject the original service on the new one, linking all reference to your service and having the possibility of execute the original code.

Sylius documentation already comments and recommends using decorator to override factories services. But it does not end there, you can do it for almost every service on Sylius, calculators, processors, factories and so on.

Here is an example of App\Calculator\MyCalculator:

[php]
<?php
// src/Calculatror/MyCalculator.php
namespace App\Calculator;

use Sylius\Component\Core\Calculator\ProductVariantPriceCalculatorInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use App\Service\OtherService;

class MyCalculator implements ProductVariantPriceCalculatorInterface
{
protected $calculator;
protected $service;

public function __construct(ProductVariantPriceCalculatorInterface $calculator, OtherService $service)
{
$this->calculator = $calculator;
$this->service = $service;
}

public function calculate(ProductVariantInterface $productVariant, array $context): int
{
$price = $this->calculator->calculate($productVariant, $context); //.. call before or alfer depend of your logic

$price = $this->service->doSomethingWithThePrice($price); //.. do something the price

return $price;
}
}

And the configuration you have to define:

[yaml]
# config/services.yaml
services:
# …

App\Calculator\MyCalculator:
decorates: sylius.calculator.product_variant_price
# pass the old service as an argument, with the name of the current service with the .inner at the end
arguments: [‘@App\Calculator\MyCalculator.inner’, ‘@App\Service\OtherService’]

Now you can extend everything you want on Sylius.

--

--