Skip to main content

How to autocomplete translatable entities in Sylius

· 4 min read
Stephan Hochdörfer
Head of IT Business Operations

The Sylius framework, built on top of Symfony, offers a robust foundation for e-commerce applications. One of its strengths is the ease of integrating Symfony UX components, such as the autocomplete element, into the Sylius Admin UI. However, when dealing with translatable Sylius entities, the Symfony UX Autocomplete component requires additional configuration to function properly.

By default, the Symfony UX autocomplete component lacks awareness of Sylius' translation mechanism. This limitation requires some extra effort to make it compatible with translatable entities. Fortunately, the Sylius team has provided a solution in the form of the Sylius\Bundle\AdminBundle\Form\Type\TranslatableAutocompleteType implementation.

To illustrate the implementation, let's examine how to apply the TranslatableAutocompleteType to the Destination entity. By following this example, you can adapt the solution to your specific use case and successfully integrate the autocomplete feature with translatable entities in Sylius.

Let's assume we have a Destination entity with just one property name created. The name property is translatable:

<?php

declare(strict_types=1);

namespace App\Entity\Destination;

use App\Form\Type\Admin\DestinationType;
use App\Grid\Admin\DestinationGrid;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Resource\Metadata\AsResource;
use Sylius\Resource\Metadata\Create;
use Sylius\Resource\Metadata\Delete;
use Sylius\Resource\Metadata\Index;
use Sylius\Resource\Metadata\Update;
use Sylius\Resource\Model\ResourceInterface;
use Sylius\Resource\Model\TranslatableInterface;
use Sylius\Resource\Model\TranslatableTrait;
use Sylius\Resource\Model\TranslationInterface;

#[AsResource(
alias: 'app.destination',
section: 'admin',
formType: DestinationType::class,
templatesDir: '@SyliusAdmin\\shared\\crud',
routePrefix: '/%sylius_admin.path_name%',
operations: [
new Index(grid: DestinationGrid::class),
new Create(),
new Update(),
new Delete(),
],
)]
#[ORM\Entity]
#[ORM\Table(name: 'app_destination')]
class Destination implements ResourceInterface, TranslatableInterface
{
use TranslatableTrait {
__construct as private initializeTranslationsCollection;
}

#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
/** @phpstan-ignore-next-line */
private ?int $id = null;

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

public function getId(): ?int
{
return $this->id;
}

public function getName(): ?string
{
/** @var DestinationTranslation $translation */
$translation = $this->getTranslation();

return $translation->getName();
}

public function setName(?string $name): void
{
/** @var DestinationTranslation $translation */
$translation = $this->getTranslation();
$translation->setName($name);
}

protected function createTranslation(): TranslationInterface
{
return new DestinationTranslation();
}
}

The translations are stored in the DestinationTranslation entity which looks like this:

<?php

declare(strict_types=1);

namespace App\Entity\Destination;

use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Resource\Model\AbstractTranslation;
use Sylius\Component\Resource\Model\TranslationInterface;
use Sylius\Resource\Model\ResourceInterface;

#[ORM\Entity]
#[ORM\Table(name: 'destination_translation')]
class DestinationTranslation extends AbstractTranslation implements ResourceInterface, TranslationInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
/** @phpstan-ignore-next-line */
private ?int $id = null;

#[ORM\Column(length: 255, nullable: true)]
private ?string $name = null;

public function getId(): ?int
{
return $this->id;
}

public function getName(): ?string
{
return $this->name;
}

public function setName(?string $name): void
{
$this->name = $name;
}
}

To enable autocomplete functionality for our Destination entity, we can leverage the TranslatableAutocompleteType by Sylius. This approach ensures seamless integration of the autocomplete feature with Sylius' translation system. Our custom DestinationAutocompleteField implementation looks like this:

<?php

declare(strict_types=1);

namespace App\Form\Type\Admin\Destination;

use App\Entity\Destination\Destination;
use Sylius\Bundle\AdminBundle\Form\Type\TranslatableAutocompleteType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\UX\Autocomplete\Form\AsEntityAutocompleteField;

#[AsEntityAutocompleteField(
alias: 'autocomplete_admin_destination',
route: 'ux_entity_autocomplete_admin_destination'
)]
class DestinationAutocompleteField extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'class' => Destination::class,
'label' => 'app.ui.destination',
'multiple' => false,
'required' => true,
'security' => 'ROLE_ADMINISTRATION_ACCESS',
'entity_fields' => ['id'],
'translation_fields' => ['name'],
'choice_label' => 'name',
]);
}

public function getParent(): string
{
return TranslatableAutocompleteType::class;
}
}

In the getParent() method, it's important to use the TranslatableAutocompleteType provided by Sylius instead of the BaseEntityAutocompleteType defined by Symfony UX.

To use the TranslatableAutocompleteType, we must provide additional configuration in the setDefaults() method:

  • entity_fields: If your entity lacks a code property, you need to specify the fields that identify the entity using the entity_fields option. In our example, the id property is used as an alternative.
  • translation_fields: List all fields that should be searchable in the translation_fields option. Since the Destination entity only has a name property, it is configured accordingly.
  • choice_label: Define the data displayed in the autocomplete field using the choice_label option. In this case, showing the destination's name is the most logical choice.

As the Sylius implementation extends the Symfony UX implementation, you can also configure security access for the autocomplete field according to the Symfony UX Autocomplete documentation. This allows for fine-grained control over access permissions.