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:
- 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. - 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.
- 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.
- 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.
1 thought on “Why I Had to Build My Own Dynamic Field Resolver for EasyAdmin”