Implementation

The goal of our plugin is simple - we need to extend the ProductVariant entity and provide a new flag, that could be set on the product variant form. Following customizations are done just like in the Sylius Customization Guide, take a look at customizing models, form and template.

Attention

PluginSkeleton is focused on delivering the most friendly and testable environment. That’s why in tests/Application directory, there is a tiny Sylius application placed, with your plugin already used. Thanks to that, you can test your plugin with Behat scenarios within Sylius application without installing it to any test app manually! There is, however, one important consequence of such an architecture. Everything that should be done by a plugin user (configuration import, templates copying etc.) should also be done in tests/Application to simulate the real developer behavior - and therefore make your new features testable.

Model

The only field we need to add is an additional $availableOnDemand boolean. To allow plugin’s user to use multiple plugins extending the same entity, it’s recommended to provide a trait with new properties and methods, together with ORM mapping written in annotations (if necessary). Providing an interface containing new methods is advisable.

<?php

// src/Model/ProductVariantTrait.php

declare(strict_types=1);

namespace IronMan\SyliusProductOnDemandPlugin\Model;

trait ProductVariantTrait
{
    /**
     * @var bool
     *
     * @ORM\Column(type="boolean", name="available_on_demand")
     */
    private $availableOnDemand = false;

    public function setAvailableOnDemand(bool $availableOnDemand): void
    {
        $this->availableOnDemand = $availableOnDemand;
    }

    public function isAvailableOnDemand(): bool
    {
        return $this->availableOnDemand;
    }
}
<?php

// src/Model/ProductVariantInterface.php

declare(strict_types=1);

namespace IronMan\SyliusProductOnDemandPlugin\Entity;

interface ProductVariantInterface
{
    public function setAvailableOnDemand(bool $availableOnDemand): void;

    public function isAvailableOnDemand(): bool;
}

Warning

Remember that if you modify or add some mapping, you should either provide a migration for the plugin user (that could be copied to their migration folder) or mention the requirement of migration generation in the installation instructions.

Form

To make our new field available in Admin panel, a form extension is required:

<?php

// src/Form/Extension/ProductVariantTypeExtension.php

declare(strict_types=1);

namespace IronMan\SyliusProductOnDemandPlugin\Form\Extension;

use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Sylius\Bundle\ProductBundle\Form\Type\ProductVariantType;
use Symfony\Component\Form\FormBuilderInterface;

final class ProductVariantTypeExtension extends AbstractTypeExtension
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add('availableOnDemand', CheckboxType::class, [
            'label' => 'iron_man_sylius_product_on_demand_plugin.ui.available_on_demand',
        ]);
    }

    public function getExtendedType(): string
    {
        return ProductVariantType::class;
    }
}

Translation keys placed in src/Resources/translations/message.{locale}.yml will be resolved automatically.

# src/Resources/translations/message.en.yml

iron_man_sylius_product_on_demand_plugin:
    ui:
        available_on_demand: Available on demand

And in your services.yml file:

# src/Resources/config/services.yml

services:
    iron_man_sylius_product_on_demand_plugin.form.extension.type.product_variant:
        class: IronMan\SyliusProductOnDemandPlugin\Form\Extension\ProductVariantTypeExtension
        tags:
            - { name: form.type_extension, extended_type: Sylius\Bundle\ProductBundle\Form\Type\ProductVariantType }

Again, you must remember about importing src/Resources/config/services.yml in tests/Application/config/services.yaml.

Template

The last step is extending the template of a product variant form. It can be done in three ways:

  • by overwriting template
  • by using sonata block events
  • by writing a theme

For the needs of this tutorial, we will go the first way. What’s crucial, we need to determine which template should be overwritten. Naming for twig files in Sylius, both in ShopBundle and AdminBundle are pretty clear and straightforward. In this specific case, the template to override is src/Sylius/Bundle/AdminBundle/Resources/views/ProductVariant/Tab/_details.html.twig. It should be copied to src/Resources/views/SyliusAdminBundle/ProductVariant/Tab/ directory, and additional field should be placed somewhere in the template.

{# src/Resources/views/SyliusAdminBundle/ProductVariant/Tab/_details.html.twig #}

{#...#}

<div class="ui segment">
    <h4 class="ui dividing header">{{ 'sylius.ui.inventory'|trans }}</h4>
    {{ form_row(form.onHand) }}
    {{ form_row(form.tracked) }}
    {{ form_row(form.version) }}
    {{ form_row(form.availableOnDemand) }}
</div>

{#...#}

Warning

Beware! Implementing a new template on the plugin level is not everything! You must remember that this template should be copied to templates/SyliusAdminBundle/ directory (with whole catalogs structure, means /ProductVariant/Tab in the application that uses your plugin - and therefore it should be mentioned in installation instruction. The same thing should be done for your test application (you should have tests/Application/templates/SyliusAdminBundle/ catalog with this template copied).

Take a look at customizing the templates section in the documentation, for a better understanding of this topic.