8. Custom calculators

Danger

We’re sorry but this documentation section is outdated. Please have that in mind when trying to use it. You can help us making documentation up to date via Sylius Github. Thank you!

Sylius ships with several default calculators, but you can easily register your own.

8.1. Simple calculators

All shipping cost calculators implement CalculatorInterface. In our example we’ll create a calculator which calls an external API to obtain the shipping cost.

<?php
# src/Shipping/Calculator/DHLCalculator.php

declare(strict_types=1);

namespace App\Shipping\Calculator;

use Sylius\Component\Shipping\Calculator\CalculatorInterface;
use Sylius\Component\Shipping\Model\ShipmentInterface;

final class DHLCalculator implements CalculatorInterface
{
    /**
     * @var DHLService
     */
    private $dhlService;

    /**
     * @param DHLService $dhlService
     */
    public function __construct(DHLService $dhlService)
    {
        $this->dhlService = $dhlService;
    }

    /**
     * {@inheritdoc}
     */
    public function calculate(ShipmentInterface $subject, array $configuration): int
    {
        return $this->dhlService->getShippingCostForWeight($subject->getShippingWeight());
    }

    /**
     * {@inheritdoc}
     */
    public function getType(): string
    {
        return 'dhl';
    }
}

Now, you need to register your new service in container and tag it with sylius.shipping_calculator.

services:
    app.shipping_calculator.dhl:
        class: App\Shipping\Calculator\DHLCalculator
        arguments: ['@app.dhl_service']
        tags:
            - { name: sylius.shipping_calculator, calculator: dhl, label: "DHL" }

That would be all. This new option (“DHL”) will appear on the ShippingMethod creation form, in the “calculator” field.

8.2. Configurable calculators

You can also create configurable calculators, meaning that you can have several ShippingMethod’s using same type of calculator, with different settings.

Let’s modify the DHLCalculator, so that it charges 0 if shipping more than X items. First step is to create a form type which will be displayed if our calculator is selected.

<?php
# src/Form/Type/Shipping/Calculator/DHLConfigurationType.php

declare(strict_types=1);

namespace App\Form\Type\Shipping\Calculator;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Type;

final class DHLConfigurationType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('limit', IntegerType::class, [
                'label' => 'Free shipping above total items',
                'constraints' => [
                    new NotBlank(),
                    new Type(['type' => 'integer']),
                ]
            ])
        ;
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver
            ->setDefaults([
                'data_class' => null,
                'limit' => 10,
            ])
            ->setAllowedTypes('limit', 'integer')
        ;
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix(): string
    {
        return 'app_shipping_calculator_dhl';
    }
}

We also need to register the form type in the container and set this form type in the definition of the calculator.

services:
    app.shipping_calculator.dhl:
        class: App\Shipping\Calculator\DHLCalculator
        arguments: ['@app.dhl_service']
        tags:
            - { name: sylius.shipping_calculator, calculator: dhl, form_type: App\Form\Type\Shipping\Calculator\DHLConfigurationType, label: "DHL" }

    app.form.type.shipping_calculator.dhl:
        class: App\Form\Type\Shipping\Calculator\DHLConfigurationType
        tags:
            - { name: form.type }

Perfect, now we’re able to use the configuration inside the calculate method.

<?php
# src/Shipping/Calculator/DHLCalculator.php

declare(strict_types=1);

namespace App\Shipping\Calculator;

use Sylius\Component\Shipping\Calculator\CalculatorInterface;
use Sylius\Component\Shipping\Model\ShipmentInterface;

final class DHLCalculator implements CalculatorInterface
{
    /**
     * @var DHLService
     */
    private $dhlService;

    /**
     * @param DHLService $dhlService
     */
    public function __construct(DHLService $dhlService)
    {
        $this->dhlService = $dhlService;
    }

    /**
     * {@inheritdoc}
     */
    public function calculate(ShipmentInterface $subject, array $configuration): int
    {
        if ($subject->getShippingUnitCount() > $configuration['limit']) {
            return 0;
        }

        return $this->dhlService->getShippingCostForWeight($subject->getShippingWeight());
    }

    /**
     * {@inheritdoc}
     */
    public function getType(): string
    {
        return 'dhl';
    }
}

Your new configurable calculator is ready to use. When you select the “DHL” calculator in ShippingMethod form, configuration fields will appear automatically.