Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Overriding Field Templates

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

We know that a field describes both the form type that you see on the form and also how that field is rendered on the detail and index pages. We also know how easy it is to customize the form type. We can ->setFormType() to use a completely different type or ->setFormTypeOption() to configure that type.

We can also change a lot about how each renders on the detail and index pages. For example, let's play with this "Votes" field:

... lines 1 - 9
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
class AnswerCrudController extends AbstractCrudController
{
... lines 14 - 18
public function configureFields(string $pageName): iterable
{
... lines 21 - 23
yield IntegerField::new('votes');
... lines 25 - 31
}
}

If I autocomplete the methods on this, we have options like ->setCssClass(), ->addWebpackEncoreEntries(), ->addHtmlContentsToBody(), and ->addHtmlContentsToHead(). You can even call ->setTemplatePath() to completely override how this field is rendered on the index and detail pages, which we'll do in a moment.

But also notice that there's ->setTemplatePath() and ->setTemplateName(). What's the difference?

Template "Names" and the Template Registry

To answer that question, I'm going to hit Shift + Shift and open up a core class from EasyAdmin called TemplateRegistry.php. If you don't see it, make sure to "Include non-project items".

Perfect! Internally, EasyAdmin has many templates and it maintains this "map" of template names to the template filename behind each. So when you call ->setTemplateName(), what you would pass is some other template name. For example, I could pass crud/field/money if I wanted to use that template instead of the normal one.

But you probably won't override the template name very often. Most of the time, if you want to completely control how a field is rendered, you'll call ->setTemplatePath().

Here's the plan: when "Votes" is rendered on the index and detail pages, I want to render a completely different template. Let's call it admin/field/votes.html.twig:

... lines 1 - 11
class AnswerCrudController extends AbstractCrudController
{
... lines 14 - 18
public function configureFields(string $pageName): iterable
{
... lines 21 - 23
yield IntegerField::new('votes')
->setTemplatePath('admin/field/votes.html.twig');
... lines 26 - 32
}
}

Ok! Time to create that. In templates/, add 2 new directories admin/field... and a new file: votes.html.twig. Inside, I don't really know what to put here yet, so I'll just put "💯 votes!"... and see what happens:

💯 votes!

When we move over and refresh... there it is! We are now in complete control of the votes!

Digging into the Core Templates

But, if you're like me, you're probably wondering what we can do inside of here. What variables do we have access to? One important thing to realize (and you can see it here in TemplateRegistry.php) is that every single field has a corresponding template. If you need to extend or change how a field is rendered, looking into the core template is pretty handy.

For example, votes is an IntegerField. Whelp, there's a template called integer.html.twig. Close this template registry and... let's go find that! Open vendor/easycorp/easyadmin-bundle/src/... close up Field/ and instead open Resources/views/crud/field. Here is the list of all of the field templates in the system. You can also see other templates that are used to render other parts of EasyAdmin... and you can override these as well.

Open up integer.html.twig. Ok cool. Check out the collection of comments on top. I like this! It helps our editor (and us) to know which variables we have access to. Apparently, we have access to a field variable, which is that familiar FieldDto object we talked about earlier. All the integer template does is just... print field.formattedValue.

Customizing the Template

Copy these three lines and paste them into our votes.html.twig:

{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #}
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
... lines 4 - 5

Then instead of "💯 votes!", say field.formattedValue "votes":

{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #}
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
{{ field.formattedValue }} votes

And when we try this... beautiful! But I bet we can make this fancier! If the votes are negative, let's put a little thumbs down. And if positive, a thumbs up.

Take off the word "votes"... and add if field.. Hmm. What we want to get is the underlying value - the true integer, not necessarily the "formatted" value. We can get that by saying field.value.

So formattedValue is the string that would print on the page, while value is the actual underlying (in this case) integer. So if field.value >= 0, else, and endif:

... lines 1 - 3
{% if field.value >= 0 %}
... line 5
{% else %}
... line 7
{% endif %}
{{ field.formattedValue }}

If it is greater than zero, add an icon with fas fa-thumbs-up text-success. Copy that... and paste for our thumbs down with text-danger:

... lines 1 - 3
{% if field.value >= 0 %}
<i class="fas fa-thumbs-up text-success"></i>
{% else %}
<i class="fas fa-thumbs-down text-danger"></i>
{% endif %}
... lines 9 - 10

And... just like that, we're making this field render however we want. It doesn't change how it looks like inside of the form (that's entirely handled by the form type), but it does control how it's rendered on the index page, and the detail page.

But, hmm. We also have a "votes" field inside of the Questions section. While it would be pretty easy to also point that votes field to the same new template, instead, I want to create a brand new custom field in EasyAdmin. That's next.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.4
        "doctrine/doctrine-bundle": "^2.1", // 2.5.5
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.2.1
        "doctrine/orm": "^2.7", // 2.10.4
        "easycorp/easyadmin-bundle": "^4.0", // v4.0.2
        "handcraftedinthealps/goodby-csv": "^1.4", // 1.4.0
        "knplabs/knp-markdown-bundle": "dev-symfony6", // dev-symfony6
        "knplabs/knp-time-bundle": "^1.11", // 1.17.0
        "sensio/framework-extra-bundle": "^6.0", // v6.2.5
        "stof/doctrine-extensions-bundle": "^1.4", // v1.7.0
        "symfony/asset": "6.0.*", // v6.0.1
        "symfony/console": "6.0.*", // v6.0.2
        "symfony/dotenv": "6.0.*", // v6.0.2
        "symfony/flex": "^2.0.0", // v2.0.1
        "symfony/framework-bundle": "6.0.*", // v6.0.2
        "symfony/mime": "6.0.*", // v6.0.2
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/runtime": "6.0.*", // v6.0.0
        "symfony/security-bundle": "6.0.*", // v6.0.2
        "symfony/stopwatch": "6.0.*", // v6.0.0
        "symfony/twig-bundle": "6.0.*", // v6.0.1
        "symfony/ux-chartjs": "^2.0", // v2.0.1
        "symfony/webpack-encore-bundle": "^1.7", // v1.13.2
        "symfony/yaml": "6.0.*", // v6.0.2
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.7
        "twig/twig": "^2.12|^3.0" // v3.3.7
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.1
        "symfony/debug-bundle": "6.0.*", // v6.0.2
        "symfony/maker-bundle": "^1.15", // v1.36.4
        "symfony/var-dumper": "6.0.*", // v6.0.2
        "symfony/web-profiler-bundle": "6.0.*", // v6.0.2
        "zenstruck/foundry": "^1.1" // v1.16.0
    }
}