↓ Aller au contenu
  1. Actualités/

Creating Custom Sylius Grid Fields

·1152 mots·6 mins
Sylius Stack - Cet article fait partie d'une série.
Partie 2: Cet article
Sylius grids offer a powerful, flexible, and customizable way to present your entities in an HTML table for your back or front office. For those interested in creating a custom Sylius grid, they provide numerous options.

FieldType services are responsible for rendering each cell in the table, and the Sylius grid bundle currently provides three main types:

  • StringFieldType: used to render a simple string.
  • DatetimeFieldType: displays a Datetime object, allowing configuration of format and timezone.
  • TwigFieldType: renders the field using a Twig template, enabling great customization.

A new type is coming to Sylius to give you even more flexibility. We will cover it at the end of this article. This includes possibilities for creating custom Sylius grids.

While these field types cover many needs, you may want to create custom field types for specific scenarios.


Implementing FieldTypeInterface
#

First, let’s examine the FieldTypeInterface. This interface is straightforward and has two methods:

  • render(Field $field, $data, array $options); – Responsible for returning the HTML content of a cell.
  • configureOptions(OptionsResolver $resolver): void; – Allows the field type to be configured with options.

For example:

  1. 1

    Create the field type

    Create a field type BadgeFieldType.php for displaying labels.
  2. 2

    Register it

    Configure the field type, specify the service for $dataExtractor and add the sylius.grid_field tag to register the type.
  3. 3

    Add a builder

    Create a builder BadgeField.php to make the field easier to configure.
  4. 4

    Use it in a grid

    Configure the field in a grid in PostGrid.php.
src/FieldType/BadgeFieldType.php
<?php

declare(strict_types=1);

namespace App\FieldType;

use Sylius\Component\Grid\DataExtractor\DataExtractorInterface;
use Sylius\Component\Grid\Definition\Field;
use Sylius\Component\Grid\FieldTypes\FieldTypeInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

final class BadgeFieldType implements FieldTypeInterface
{
public function __construct(
    private DataExtractorInterface $dataExtractor,
) {
}

public function render(Field $field, $data, array $options): string
{
    $value = $this->dataExtractor->get($field, $data);

    return sprintf(
        '<span class="badge">%s</span>',
        $value,
    );
}

public function configureOptions(OptionsResolver $resolver): void
{
    // Options don't need to be configured for now.
}
}
config/services.yaml
services:
App\FieldType\BadgeFieldType:
    arguments:
        $dataExtractor: '@sylius.grid.data_extractor'
    tags:
        - { name: 'sylius.grid_field', type: 'badge' }
src/FieldType/Builder/BadgeField.php
<?php

declare(strict_types=1);

namespace App\FieldType\Builder;

use Sylius\Bundle\GridBundle\Builder\Field\Field;
use Sylius\Bundle\GridBundle\Builder\Field\FieldInterface;

final class BadgeField
{
public static function create(string $name): FieldInterface
{
    return Field::create($name, 'badge');
}
}
src/Grid/PostGrid.php
<?php

declare(strict_types=1);

namespace App\Grid;

use App\Entity\Post;
use App\FieldType\Builder\BadgeField;
use Sylius\Bundle\GridBundle\Builder\Field\StringField;
use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface;
use Sylius\Bundle\GridBundle\Grid\AbstractGrid;
use Sylius\Bundle\GridBundle\Grid\ResourceAwareGridInterface;

final class PostGrid extends AbstractGrid implements ResourceAwareGridInterface
{
public static function getName(): string
{
    return 'app_post';
}

public function buildGrid(GridBuilderInterface $gridBuilder): void
{
    $gridBuilder
        ->addField(
            BadgeField::create('status'),
        )
        ->addField(StringField::create('title'))
    ;
}

public function getResourceClass(): string
{
    return Post::class;
}
}

And voilà! After following these steps, you have successfully implemented a custom Sylius grid.

Sylius admin posts grid with a Status column rendered as grey badges (draft, published)
The custom badge field in the posts grid

Playing with options
#

Creating such a field is relatively simple, but we haven’t covered options yet, so let’s explore that.

Imagine we want to render the label with different colors and translation keys depending on its type. This is where options come into play. We’ll keep it simple here, but note that Symfony’s OptionsResolver library defines options.

We will set two options: translationKeys and colors. When configuring the field, you can define values for these options. Let’s update the previous code:

  • In BadgeFieldType.php, the render method retrieves these values from the $options parameter, allowing us to determine the appropriate color and label to use for the field
  • Since we added new options, let’s update the BadgeField.php builder so it’s easy to define them
  • Finally, let’s update the PostGrid to specify colors and translation keys
src/FieldType/BadgeFieldType.php
<?php

declare(strict_types=1);

namespace App\FieldType;

use Sylius\Component\Grid\DataExtractor\DataExtractorInterface;
use Sylius\Component\Grid\Definition\Field;
use Sylius\Component\Grid\FieldTypes\FieldTypeInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface;

final class BadgeFieldType implements FieldTypeInterface
{
public function __construct(
    private DataExtractorInterface $dataExtractor,
    private TranslatorInterface $translator,
) {
}

public function render(Field $field, $data, array $options): string
{
    $value = $this->dataExtractor->get($field, $data);

    return sprintf(
        '<span class="badge bg-%s-lt">%s</span>',
        $this->getColor($value, $options['colors']),
        $this->getLabel($value, $options['translationKeys']),
    );
}

public function configureOptions(OptionsResolver $resolver): void
{
    $resolver->setDefault('translationKeys', []);
    $resolver->setDefault('colors', []);
}

private function getColor(mixed $value, array $colors): mixed
{
    return $colors[$value] ?? 'secondary';
}

private function getLabel(mixed $value, array $translationKeys): mixed
{
    $translationKey = $translationKeys[$value] ?? null;
    if ($translationKey === null) {
        return $value;
    }

    return $this->translator->trans($translationKey);
}
}
src/FieldType/Builder/BadgeField.php
<?php

declare(strict_types=1);

namespace App\FieldType\Builder;

use Sylius\Bundle\GridBundle\Builder\Field\Field;
use Sylius\Bundle\GridBundle\Builder\Field\FieldInterface;

final class BadgeField
{
public static function create(
    string $name,
    array $translationKeys = [],
    array $colors = [],
): FieldInterface
{
    return Field::create($name, 'badge')
        ->setOption('translationKeys', $translationKeys)
        ->setOption('colors', $colors)
    ;
}
}
src/Grid/PostGrid.php
<?php

declare(strict_types=1);

namespace App\Grid;

use App\Entity\Post;
use App\FieldType\Builder\BadgeField;
use Sylius\Bundle\GridBundle\Builder\Field\StringField;
use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface;
use Sylius\Bundle\GridBundle\Grid\AbstractGrid;
use Sylius\Bundle\GridBundle\Grid\ResourceAwareGridInterface;

final class PostGrid extends AbstractGrid implements ResourceAwareGridInterface
{
public static function getName(): string
{
    return 'app_post';
}

public function buildGrid(GridBuilderInterface $gridBuilder): void
{
    $gridBuilder
        ->addField(
            BadgeField::create(
                'status',
                [
                    'draft' => 'app.ui.draft',
                    'published' => 'app.ui.published',
                ],
                [
                    'draft' => 'secondary',
                    'published' => 'primary',
                ],
            ),
        )
        ->addField(StringField::create('title'))
    ;
}

public function getResourceClass(): string
{
    return Post::class;
}
}
Business logic stays in PHP

We have an easy-to-configure and reusable BadgeFieldType. This is great because custom field types allow business logic to stay in PHP classes rather than Twig templates, allowing for more efficient creation of custom Sylius grids.

Sylius admin posts grid with colored, translated status badges: grey for draft, green for published
Colors and translation keys configured through options

A new field type: CallableFieldType
#

Now that you know how to create custom field types, keep in mind that this approach can be time-consuming, especially for one-time use cases.

To simplify this process, I’ve implemented a new callable field type that will be available in the SyliusGridBundle. This field type enables you to configure a PHP callable to display your data, supporting both PHP and YAML grids.

Not released yet

The pull request for this new field type can be found on GitHub, though it has not been officially released yet.

To try it out, you must install the 1.14 branch of the bundle using Composer:

Terminal
composer require sylius/grid-bundle:1.14.x-dev

Once installed, you can use the callable field in YAML by prefixing a callable with callable:. This supports both static methods and PHP functions.

When configuring grids with PHP, you can use any callable, including arrays, services, and callback functions with Sylius grid to create a custom layout.

config/packages/sylius_grid.yaml
sylius_grid:
grids:
    app_user:
        fields:
            id:
                type: callable
                options:
                    callable: "callable:App\\Helper\\GridHelper::addHashPrefix"
                label: app.ui.id
            name:
                type: callable
                options:
                    callable: "callable:strtoupper"
                label: app.ui.name
src/Grid/UserGrid.php
<?php

declare(strict_types=1);

namespace App\Grid;

use App\Entity\User;
use App\Helper\GridHelper;
use Sylius\Bundle\GridBundle\Builder\Field\CallableField;
use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface;
use Sylius\Bundle\GridBundle\Grid\AbstractGrid;
use Sylius\Bundle\GridBundle\Grid\ResourceAwareGridInterface;

final class UserGrid extends AbstractGrid implements ResourceAwareGridInterface
{
public static function getName(): string
{
    return 'app_user';
}

public function buildGrid(GridBuilderInterface $gridBuilder): void
{
    $gridBuilder
        ->addField(
            CallableField::create('id', GridHelper::addHashPrefix(...))
                ->setLabel('app.ui.id')
        )
        ->addField(
            CallableField::create('name', 'strtoupper')
                ->setLabel('app.ui.name')
        )
        ->addField(
            CallableField::create('roles', fn (array $roles): string => implode(', ', $roles))
                ->setLabel('app.ui.roles')
        )
    ;
}

public function getResourceClass(): string
{
    return User::class;
}
}

This feature simplifies the definition of custom field types, reducing the need for TwigFieldType and custom templates while keeping your grid configuration clean and efficient. Stay tuned for updates on its release!

👈 Previously in the series: Bring Sylius Components to Your Project.

Need custom grids for your Sylius back office?

Grid fields, filters, actions, Sylius Stack: our Sylius experts design admin interfaces tailored to your business.
Discover our Sylius expertise
Sylius Stack - Cet article fait partie d'une série.
Partie 2: Cet article