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
->hideOnForm();
yield ImageField::new('avatar')
... lines 34 - 36
->onlyOnForms();
... 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!

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