Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Controlling the "Formatted Value"

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

Head back to the index page. One of the nice things about the ImageField is that you can click to see a bigger version of it. But let's pretend that we don't want that for some reason... like because these are meant to be tiny avatars.

Actually, EasyAdmin has a field that's made specifically for avatars. It's called AvatarField!

Back in our code, yield AvatarField::new() and pass it avatar:

... lines 1 - 6
use EasyCorp\Bundle\EasyAdminBundle\Field\AvatarField;
... lines 8 - 16
class UserCrudController extends AbstractCrudController
... lines 19 - 23
public function configureFields(string $pageName): iterable
... lines 26 - 27
yield AvatarField::new('avatar');
... lines 29 - 50

Yes, we do temporarily have two fields for avatar. Go refresh and... the original works, but the AvatarField is broken!

Inspect the image. Yup! This looks like the same problem as before: it's dumping out the filename instead of the full path to it. To fix this, the ImageField has a ->setBasePath() method. Does that method exist on AvatarField? Apparently not!

Controlling the "Formatted Value"

So let's back up. No matter which field type you use, when a field is ultimately printed onto the page, what's printed is something called the formatted value. For some fields - like text fields - that formatted value is just rendered by itself. But for other fields, it's wrapped inside some markup. For example, if you dug into the template for the AvatarField - something we'll learn to do soon - you'd find that the formatted value is rendered as the src attribute of an img tag.

Anyways, the formatted value is something we can control. Do that by calling ->formatValue() and passing a callback. I'll use a static function() that will receive a $value argument - whatever EasyAdmin would normally render as the formatted $value - and then our entity: User $user. Inside, we can return whatever value should be printed inside the src of the img. So, return $user->getAvatarUrl():

... lines 1 - 16
class UserCrudController extends AbstractCrudController
... lines 19 - 23
public function configureFields(string $pageName): iterable
... lines 26 - 27
yield AvatarField::new('avatar')
->formatValue(static function ($value, User $user) {
return $user->getAvatarUrl();
... lines 32 - 53

The static isn't important... it's just kind of a "cool kid" thing to do if your callback does not need to leverage the $this variable.

Anyways, go back to your browser and refresh. Yay! We have a nice little avatar! But, if you go the the form for this user, interesting! It only renders one of our avatar fields. This is expected: even though we can display two avatar fields on the index page, we can't have two avatar fields in the form. The second one always wins. And that's fine. We don't actually want two fields... it's just nice to understand why that's happening.

If we deleted the ImageField and used the AvatarField on the form, you'd see that the AvatarField renders as a text input! Not very helpful. Ultimately, we want to use ImageField on the form and AvatarField when rendering. And we already know how to do that!

Down here... on ImageField, add ->onlyOnForms(). And above, on AvatarField, do the opposite: ->hideOnForm():

... lines 1 - 16
class UserCrudController extends AbstractCrudController
... lines 19 - 23
public function configureFields(string $pageName): iterable
... lines 26 - 27
yield AvatarField::new('avatar')
... lines 29 - 31
yield ImageField::new('avatar')
... lines 34 - 36
... lines 38 - 55

This gives us the exact result we want.

Allowing Null in formatValue

Oh, and I almost forgot! In the ->formatValue() callback, technically the User argument should be allowed to be null. We'll learn why later when we talk about entity permissions. In a real project, I would make the function look like this:

->formatValue(static function($value, ?User $user) {
    return $user?->getAvatarUrl();

That has a nullable User argument and uses a PHP 8 syntax that basically says:

If we have a User, then call getAvatarUrl() and return that string. But if we don't have a user, skip calling the method and just return null.

I'm actually going to remove this for now... because we'll re-add it later when we hit an error.

Next, I want to customize more fields inside of our admin! In particular, I'm excited to check out the very powerful AssociationField.

Leave a comment!

Login or Register to join the conversation
Piotr Avatar

Hello Symfonycast Team!
I have a problem with one issue.
I have created company and user entities. I made an onetoone relationship between them. When adding a company to the database by a logged in user in easyadmin, he does not fill in the user_id field, so I cannot see the company of the user who created it.

How by default, in the user field in the company entity, add the field of the currently logged in user. (user_id). So that at the time of creating the company, I would automatically fill in the user_id field in the database with the person who created the company.

Currently,automatic adding a company to me and the user_id = null field. Should be the id of the user who added it.

               ->formatValue(static function(Security $security){
                   return $security->getUser()->getId();

Thank you for your work, Regards


Hi Piotr!

Inside of your CompanyDashboard, I would do this:

A) Remove the user field that you have in your code above entirely. No need for this :).
B) Override the persisteEntity() method - https://github.com/EasyCorp/EasyAdminBundle/blob/71ec73d6482458ce7e98fa4b18c87a476914f34d/src/Controller/AbstractCrudController.php#L512-L516 - and inside, set the user:

public function persistEntity(EntityManagerInterface $entityManager, $entityInstance): void
    // $entityInstance will be a Company object

    parent::persistEntity($entityManager, $entityInstance);

That should do it :). Right before Company is saved, the user will be set. Let me know if this helps :).


Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=8.1.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", //
        "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