Why I Had to Build My Own Dynamic Field Resolver for EasyAdmin

In my last post, I shared a quick example of how to render dynamic fields in EasyAdmin. You know the drill: a user selects a country, and you want the state list to be displayed based on that selection. It sounds like a basic feature, right? But if youโ€™ve used EasyAdmin, you know itโ€™s just not there.

When I first came across this problem, I searched everywhere for an answer. I eventually found a thread where the creator of EasyAdmin basically said they werenโ€™t going to implement the feature because itโ€™s a “Symfony Form” problem. That was the moment I realized a fix wasn’t coming from the repoโ€”I had to build it myself.

The Issue with the Previous Solution

My previous solution had some serious issues. It relied on Symfonyโ€™s validation mechanism to block submissions, which was… honestly, a mess. The fields would always render but stay empty. It logic was not very user-friendly, and it was a nightmare to reuse. (Well at least it solved the problem. Hehehe…). Youโ€™d end up writing the same logic over and over in every controller, creating multiple classes just to manage a simple dependency. It was working but wasn’t reliable.

Why the solution was difficult?

EasyAdmin is a beautiful project with a clean architecture, but to be frank, its internals are heavily hard-coded. It’s easy to use what easyadmin already provides as a public feature, but trying to modify internal concepts is a hell. Almost every class is marked as final. You want to extend a TextField? Itโ€™s final. You want to tweak a dashboard menu or modify FormType? You’re on your own! Everything is locked down.

The difficulty isn’t that we can’t write the code; it’s that the library’s constraints make it nearly impossible to inject simple custom logic without breaking the ecosystem. I spent a long time fighting those final keywords before I realized I needed a completely different approach.

Why Not Just Write a Custom Form?

Sure, I could have built my own custom forms and templates from scratch. But then you lose everything that makes EasyAdmin great. You lose the automatic dropdown styling (TomSelect), the field type options, and the clean design. Youโ€™d spend all your time rebuilding what already exists.

Instead of fighting EasyAdmin or rebuilding it, I decided to build a library that works with it.


The New Logic: EasyAdmin Dependency Field Resolver

Iโ€™ve named it EasyAdmin Dependency Field Resolver. Itโ€™s lightweight, modular, and most importantly, it lets you keep your standard EasyAdmin coding style. You can solve the dependency problem with a single line of code.

Here is the “magic” behind how it works:

  1. The Field Resolver: This is a non-shared class that mimics EasyAdmin’s own configureFields. You autowire it, call it, and write your code exactly like you always have.
  2. The State Listener: This is the “brain.” It watches for changes in the form. If it sees a change in a field you’ve marked as a dependency, it doesn’t wait for a validation error. It redirects the current request immediately. This is the key, it prevents those annoying validation errors from ever showing up.
  3. The Data Bridge: To make the redirect work, we need to pass data from the “old” request to the “new” one. The Bridge stores that data in the session, uses it to rebuild the form, and then immediately wipes the session clean to keep things secure.
  4. The Form Extension: This intercepts the global EasyAdmin extension to set up the data before EasyAdmin even sees it. This is how we prevent the form from losing the data the user just typed.

By splitting everything into different componentsโ€”the Resolver, the Listener, the Bridge, and the Extensionโ€”it becomes a reliable cycle that doesn’t break the “final” constraints of the library.

Iโ€™ve published the code on my GitHub. Itโ€™s a solution born out of a very tough time. Seriously, I didn’t develop it with Joy , I developed it because I was unhappy that the easyadmin team were not willing to provide a solution and I’m sharing it because I know many of you are hitting that same wall.


Implementation: One Service, Infinite Dependencies

The beauty of the EasyAdmin Dependency Field Resolver is that it doesn’t change how you think about EasyAdmin. It just gives you a “Smart Container” for your fields.

1. The Setup

First, autowire the FieldDependencyResolver into your CRUD Controller. Because itโ€™s a non-shared service, itโ€™s fresh and ready for every request.

use Ucscode\EasyAdmin\DependencyFieldResolver\Service\FieldDependencyResolver;

class OrderCrudController extends AbstractCrudController
{
    public function __construct(
        private FieldDependencyResolver $resolver
    ) {}
    
    // ...
}

2. The “DependsOn” Flow

In your configureFields, instead of returning an array immediately, you let the resolver handle the collection.

Notice how we use Gatekeeper Logic: The closure for state won’t even execute unless country has a value. This keeps your code clean and prevents “Undefined Index” errors.

public function configureFields(string $pageName): iterable
{
    return $this->resolver
        
        // The callback function uses exactly the same style as you would
        // In self::configureFields()
        
        ->configureFields(function() {
            // If you prefer, you can use "yield"
            return [
                IdField::new('id')->hideOnForm(),
                TextField::new('customerName'),
              
                // The "Parent" field
                ChoiceField::new('country')->setChoices([
                    'United States' => 'US',
                    'United Kingdom' => 'UK',
                ]),
            ];
        })
        
        // Fields in "dependsOn" will render only after submission
        
        ->dependsOn('country', function(array $values) {
            // This field only renders if Country is selected
            yield ChoiceField::new('state')
                ->setChoices($this->getStatesFor($values['country']))
                ->setRequired(false); // Recommended best practice!
                ->setFormTypeOption('constraint', new NotBlank())
        })
        
        // This runs the internal logic and determines visible fields
        ->resolve();
}

That’s it! You now have a fully functional field that depends on another field.


Handling Complex “Inflation”

You can do something more logical. Let’s assume your dependent field is an AssociationField, the library handles the ID-to-Entity conversion automatically via the Data Bridge. However, if you have a very custom field, you can use the event system to “rehydrate” that data before the form renders.

class MyEventListener
{
    // ... Some Other Codes
    
    public function onRehydrate(DependencyFieldRehydrateEvent $event)
    {
        if ($event->getName() === 'product') {
            $product = $event->getValue();
            $product->setQuantity(5);
            $event->setValue($product);
        }
    }
}

The Advantage of this “New” Method

  • Clean UI: If the dependency isn’t met, the field is not in the DOM. No empty, confusing fields for the user.
  • No Validation Deadlocks: Because the State Listener triggers a redirect as soon as the Country changes, the “State” field isn’t validated yet.
  • Preserved State: When the page reloads, the user doesn’t lose the “Customer Name” they typed. The Form Extension restores it from the Bridge seamlessly.

So Here’s The Catch

This library was built to respect the “Clean Architecture” of EasyAdmin while bypassing the “Final” restrictions that make dynamic forms so hard to build. By moving the logic into a coordinated cycle of Listeners and Bridges, we get the best of both worlds: EasyAdmin’s beauty and Symfony’s flexibility.

Share this:

1 thought on “Why I Had to Build My Own Dynamic Field Resolver for EasyAdmin”

Leave a Comment