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:

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!

22
Login or Register to join the conversation

Whoops, we're sorry about that! It should be good now, I re-upload the video. Let us know if you still have some troubles with playing it

Cheers!

Reply

The error still here...

Please check my screen: https://ibb.co/fFPLk8S

Reply

Hey Dzianisr,

We're sorry about that! Looks like uploading was incomplete, I just re-uploaded this video again, now it should work!

Cheers!

Reply

Thanks for confirming, and sorry for the inconvenience!

Cheers!

1 Reply
Sébastien J. Avatar
Sébastien J. Avatar Sébastien J. | posted 7 months ago

Hi, video not works : "The media could not be loaded, either because the server or network failed or because the format is not supported."

1 Reply

Hey Sébastien,

Thank you for reporting this! The video was re-uploaded, it should be fixed now. We're sorry for the inconveniences! Let us know if you still have any troubles with playing it.

Cheers!

Reply
Sébastien J. Avatar
Sébastien J. Avatar Sébastien J. | victor | posted 7 months ago

It's good. Thanks Victor Bocharsky

1 Reply

Thank you for confirming it works for you now!

Cheers!

Reply
Sébastien J. Avatar
Sébastien J. Avatar Sébastien J. | victor | posted 7 months ago

The error came back :(

1 Reply

Hey Sébastien,

I'm sorry for that again! That's weird, but I just re-uploaded the video again, it should work now... at least it works for me.

Cheers!

Reply
Sébastien J. Avatar
Sébastien J. Avatar Sébastien J. | victor | posted 7 months ago

Victor,
It works again!
Thanks!

Reply
EinzigTech Avatar

I still have the issue.

Reply

Hey Stiff,

I fixed it again! We're sorry about that, now it should work.

Cheers!

1 Reply
EinzigTech Avatar

Hi Victor,

Just to confirm, it is working now.

Best,
Stiff

Reply

Thanks for confirming!

Cheers!

1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

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
    }
}