How to add a custom translatable model?¶

In this guide we will create a new translatable model in our system, which is quite similar to adding a simple model, although it requires some additional steps.

As an example we will take a translatable Supplier entity, which may be really useful for shop maintenance.

1. Define your needs¶

A Supplier needs three essential fields: name, description and enabled flag. The name and description fields need to be translatable.

2. Generate the SupplierTranslation entity¶

Symfony, the framework Sylius uses, provides the Symfony MakerBundle, that simplifies the process of adding a model.

Warning

Remember to have the MakerBundle imported in the AppKernel, as it is not there by default.

You need to use such a command in your project directory.

php bin/console make:entity

The generator will ask you for the entity name and fields. See how it should look like to match our assumptions.

../../_images/generating_translation_entity.png

As you can see we have provided only the desired translatable fields.

Below the final SupplierTranslation class is presented, it implements the ResourceInterface.

<?php

namespace App\Entity;

use Sylius\Component\Resource\Model\AbstractTranslation;
use Sylius\Component\Resource\Model\ResourceInterface;

class SupplierTranslation extends AbstractTranslation implements ResourceInterface
{
    /**
     * @var int
     */
    private $id;

    /**
     * @var string
     */
    private $name;

    /**
     * @var string
     */
    private $description;

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param string $name
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param string $description
     */
    public function setDescription($description)
    {
        $this->description = $description;
    }

    /**
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }
}

3. Generate the Supplier entity¶

While generating the entity, similarly to the way the translation was generated, we are providing only non-translatable fields. In our case only the enabled field.

../../_images/generating_basic_entity.png

Having the stubs generated, we need to extend our class with a connection to SupplierTranslation.

  • implement the ResourceInterface,
  • implement the TranslatableInterface,
  • use the TranslatableTrait,
  • initialize the translations collection in the constructor,
  • add the createTranslation() method,
  • implement getters and setters for the properties that are held on the translation model.

As a result you should get such a Supplier class:

<?php

namespace App\Entity;

use Sylius\Component\Resource\Model\ResourceInterface;
use Sylius\Component\Resource\Model\TranslatableInterface;
use Sylius\Component\Resource\Model\TranslatableTrait;

class Supplier implements ResourceInterface, TranslatableInterface
{
    use TranslatableTrait {
        __construct as private initializeTranslationsCollection;
    }

    public function __construct()
    {
        $this->initializeTranslationsCollection();
    }

    /**
     * @var int
     */
    private $id;

    /**
     * @var bool
     */
    private $enabled;

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param string $name
     */
    public function setName($name)
    {
        $this->getTranslation()->setName($name);
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->getTranslation()->getName();
    }

    /**
     * @param string $description
     */
    public function setDescription($description)
    {
        $this->getTranslation()->setDescription($description);
    }

    /**
     * @return string
     */
    public function getDescription()
    {
        return $this->getTranslation()->getDescription();
    }

    /**
     * @param boolean $enabled
     */
    public function setEnabled($enabled)
    {
        $this->enabled = $enabled;
    }

    /**
     * @return bool
     */
    public function getEnabled()
    {
        return $this->enabled;
    }

    /**
     * {@inheritdoc}
     */
    protected function createTranslation()
    {
        return new SupplierTranslation();
    }
}

4. Register your entity together with translation as a Sylius resource¶

If you don’t have it yet, create a file config/packages/sylius_resource.yaml.

# config/packages/sylius_resource.yaml
sylius_resource:
    resources:
        app.supplier:
            driver: doctrine/orm # You can use also different driver here
            classes:
                model: App\Entity\Supplier
            translation:
                classes:
                    model: App\Entity\SupplierTranslation

To check if the process was run correctly run such a command:

php bin/console debug:container | grep supplier

The output should be:

../../_images/container_debug_supplier_translation.png

5. Update the database using migrations¶

Assuming that your database was up-to-date before adding the new entity, run:

php bin/console doctrine:migrations:diff

This will generate a new migration file which adds the Supplier entity to your database. Then update the database using the generated migration:

php bin/console doctrine:migrations:migrate

6. Prepare new forms for your entity, that will be aware of its translation¶

You will need both SupplierType and SupplierTranslationType.

Let’s start with the translation type, as it will be included into the entity type.

<?php

namespace App\Form\Type;

use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class SupplierTranslationType extends AbstractResourceType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class)
            ->add('description', TextareaType::class, [
                'required' => false,
            ])
        ;
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'app_supplier_translation';
    }
}

On the SupplierTranslationType we need to define only the translatable fields.

Then let’s prepare the entity type, that will include the translation type.

<?php

namespace App\Form\Type;

use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Sylius\Bundle\ResourceBundle\Form\Type\ResourceTranslationsType;
use Sylius\Component\Resource\Translation\Provider\TranslationLocaleProviderInterface;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class SupplierType extends AbstractResourceType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('translations', ResourceTranslationsType::class, [
                'entry_type' => SupplierTranslationType::class,
            ])
            ->add('enabled', CheckboxType::class, [
                'required' => false,
            ])
        ;
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'app_supplier';
    }
}

7. Register the new forms as services¶

Before the newly created forms will be ready to use them, they need to be registered as services:

# config/services.yaml
services:
    app.supplier.form.type:
        class: App\Form\Type\SupplierType
        tags:
            - { name: form.type }
        arguments: ['%app.model.supplier.class%', ['sylius']]
    app.supplier_translation.form.type:
        class: App\Form\Type\SupplierTranslationType
        tags:
            - { name: form.type }
        arguments: ['%app.model.supplier_translation.class%', ['sylius']]

8. Register the forms as resource forms of the Supplier entity¶

Extend the resource configuration of the app.supplier with forms:

# config/resources.yaml
sylius_resource:
    resources:
        app.supplier:
            driver: doctrine/orm # You can use also different driver here
            classes:
                model: App\Entity\Supplier
                form: App\Form\Type\SupplierType
            translation:
                classes:
                    model: App\Entity\SupplierTranslation
                    form: App\Form\Type\SupplierTranslationType

9. Define grid structure for the new entity¶

To have templates for your Entity administration out of the box you can use Grids. Here you can see how to configure a grid for the Supplier entity.

# config/packages/_sylius.yaml
sylius_grid:
    grids:
        app_admin_supplier:
            driver:
                name: doctrine/orm
                options:
                    class: App\Entity\Supplier
            fields:
                name:
                    type: string
                    label: sylius.ui.name
                    sortable: translation.name
                enabled:
                    type: twig
                    label: sylius.ui.enabled
                    options:
                        template: "@SyliusUi/Grid/Field/enabled.html.twig"
            actions:
                main:
                    create:
                        type: create
                item:
                    update:
                        type: update
                    delete:
                        type: delete

10. Create template¶

# App/Resources/views/Supplier/_form.html.twig
{% from '@SyliusAdmin/Macro/translationForm.html.twig' import translationForm %}

{{ form_errors(form) }}
{{ translationForm(form.translations) }}
{{ form_row(form.enabled) }}

11. Define routing for entity administration¶

Having a grid prepared we can configure routing for the entity administration:

# config/routes.yaml
app_admin_supplier:
    resource: |
        alias: app.supplier
        section: admin
        templates: "@SyliusAdmin\\Crud"
        redirect: update
        grid: app_admin_supplier
        vars:
            all:
                subheader: app.ui.supplier
                templates:
                    form: App:Supplier:_form.html.twig
            index:
                icon: 'file image outline'
    type: sylius.resource
    prefix: admin

12. Add entity administration to the admin menu¶

13. Check the admin panel for your changes¶

Tip

To see what you can do with your new entity access the http://localhost:8000/admin/suppliers/ url.