boxes within box in nature

Crédit: Yannick (Rêveries)

02-05-2022
Custom EasyAdmin – Thou shall rule the Posts Listing (1) – Fields customization

This article is part of a series of Article around the customization of EasyAdmin within Symfony. You can find the list of related articles and the context of it in this article : Symfony & EasyAdmin – space for extra functionalities. Since April 2025, the update to Symfony 7.2 has been done, please see the changes here: Custom Easy Admin - update to Symfony 7.2

Introduction

Let’s continue our dive into the customization of Easy Admin linked to Symfony. We’ve looked to update the security on specific actions on the listing/index page. Here, we want to continue exploring the listing and its customizations. In this first, out of three posts here, we’ll concentrate ourselves on how to customize the rendering of the data in the listing.

Impacted functionalities

To allow the customization of the data display in our listing, we’ll need to look at the following functionalities of EasyAdmin

Night sky with Nebula

Unmapped fields

On the Post entity, there is a date for each status, being the date on which the Post arrived in that state. So, there is a date for the creation (draft), a date for the push in review, a date for the Publishing and a date for the Cancellation (in case of).

In the table, it would be a mess to have one column for each date, so the goal is to have 1 column for the date of the current status, let’s call it : Status Date. The issue is that this field is not existing like that in our entity Post, hence we need to use an unmapped Field.

In the CRUD Controller

This is how it’s done in the CRUD Controller

1class PostCrudController extends AbstractCrudController
2{
3    public function configureFields(string $pageName): iterable
4    {
5        yield IdField::new('id')->hideOnForm();
6        ...
7        // let's define here our new field linked a non-field in our entity
8        yield DateTimeField::new('statusDate', 'Status Date')
9            // here let's configure it with standard EasyAdmin Field options
10            ->setFormat(self::STATUS_DATE_FORMAT)
11        ;
12        ...
13    }
14}

As you can see, we do the same as for a “normal” entity field by declaring a new field with a custom property name (here: statusDate). We can even declare options to that field as it is using behind the standard EasyAdmin Field.

Application update

With only that modification, we would have an issue as EasyAdmin would not find the data in our Post class, so we need to update the application a little bit by modifying our entity as the following :

1```php
2class Post
3{
4    ...
5    public function getStatusDate(): \DateTimeImmutable
6    {
7        switch ($this->status){
8            case PostWorkflow::STATUS_DRAFT:
9                return $this->createdAt;
10            case PostWorkflow::STATUS_IN_REVIEW:
11                return $this->inReviewAt;
12            case PostWorkflow::STATUS_PUBLISHED:
13                return $this->publishedAt;
14            case PostWorkflow::STATUS_CANCELLED:
15                return $this->cancelledAt;
16        }
17    }
18    ...
19}

As you can see, it’s as easy as declaring a “getter” as you would do with any field in our entity to allow EasyAdmin to fetch the data from it. Just ensure that the return type of your getter match the type of your EasyAdmin Field and you’re good to go.

“As easy as that…. Nice!!! but I see no “hideOnForm” or “onlyOnIndex”… What about the forms?”

Indeed… the “magic” of EasyAdmin is there to help us…. As there is no “setter” in our Post entity for the statusDate, it understand automatically that there is no need to add it to any form… So, neither the new nor the edit will provide this field to be populated…

IMPORTANT : as stated in the documentation, an unmapped field is not sortable as not existing as a database column.

Green field below a blue sky

Custom Field

Linked to the status date, we do have an issue with the display of the status itself… The exact value in DB is something link post.status.draft… not very user friendly. This value is to allow any translation to benefit from a trans-locale value on the status… But currently, if we use the EasyAdmin TextField, it will not display the translated content but well the raw value from the DB.

Indeed, if we look in the code of the template for that field we see that there are no translation made on the value of the field.

1{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
2{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #}
3{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
4{% if ea.crud.currentAction == 'detail' %}
5    <span title="{{ field.value }}">{{ field.formattedValue|raw|nl2br }}</span>
6{% else %}
7    <span title="{{ field.value }}">{{ field.formattedValue|raw }}</span>
8{% endif %}

CRUD Controller

The solution here is to declare our own custom Field. So, let's create it first:

1<?php
2
3declare(strict_types=1);
4
5namespace App\Controller\EasyAdmin\Fields;
6
7use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
8use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
9use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
10
11/**
12 * Custom Field to allow for the translation of a Text Field
13 */
14class TranslatedTextField implements FieldInterface
15{
16    use FieldTrait;
17
18    public static function new(string $propertyName, ?string $label = null): TextField
19    {
20        // Simple decoration of the TextField by specifying a custom template
21        // in which I used the translator on the field value
22        return (TextField::new($propertyName, $label))
23            ->setTemplatePath('easyadmin/field/translated_text.html.twig')
24            ;
25    }
26}

No real specifics here, we just decorated a TextField (as it has the behaviour in terms of data handling that we want) but we declare a specific template for it where we’ll update the way to display the data like so

1{# 'templates/easyadmin/field/translated-text.html.twig' #}
2
3{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
4{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #}
5{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
6{% if ea.crud.currentAction == 'detail' %}
7    <span title="{{ field.value }}">{{ field.formattedValue|raw|trans|nl2br }}</span>
8{% else %}
9    <span title="{{ field.value }}">{{ field.formattedValue|raw|trans }}</span>
10{% endif %}

Following that, we just need to use it in our CRUD Controller like so :

1class PostCrudController extends AbstractCrudController
2{
3    public function configureFields(string $pageName): iterable
4    {
5        yield IdField::new('id')->hideOnForm();
6        ...
7        // Set up a custom field for the display of the status on the index
8        yield TranslatedTextField::new('status')->hideOnForm();
9        ...
10    }
11}

As you can see, nothing more than any other field already defined but using our brand new shiny Custom Field.

I hope this was helpful for you, thanks for the read until here and see you soon for the part 2 of the Listing customization !

REPOSITORY : the code of this example is available in the branch 02.Post_Workflow_Listing

All pictures of this article are of the making of the author and some can be seen here : Rêveries