PHPackages                             ucscode/easyadmin-dependency-field-resolver - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Admin Panels](/categories/admin)
4. /
5. ucscode/easyadmin-dependency-field-resolver

ActiveSymfony-bundle[Admin Panels](/categories/admin)

ucscode/easyadmin-dependency-field-resolver
===========================================

A state-aware dependency resolver for EasyAdmin fields that handles complex field dependencies via a redirect-and-bridge mechanism to prevent Symfony validation type mismatches.

v1.0.1(3mo ago)114MITPHPPHP &gt;=8.1

Since Jan 26Pushed 1mo agoCompare

[ Source](https://github.com/ucscode/easyadmin-dependency-field-resolver)[ Packagist](https://packagist.org/packages/ucscode/easyadmin-dependency-field-resolver)[ RSS](/packages/ucscode-easyadmin-dependency-field-resolver/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (2)Versions (2)Used By (0)

EasyAdmin Dependency Field Resolver
===================================

[](#easyadmin-dependency-field-resolver)

A lightweight, event-driven Symfony bundle for **EasyAdmin 4** that allows fields to dynamically appear, disappear, or change their data based on the values of other fields.

Unlike standard EasyAdmin dynamic forms, this library uses a **Redirect &amp; Recovery** strategy. This ensures that even complex fields (like Autocomplete Entity types) are correctly re-initialized with full Doctrine support after a dependency change.

---

Features
--------

[](#features)

- **Closure-based logic:** Define dependencies using simple PHP closures.
- **Gatekeeper Logic:** Closures are only executed when all required parent values are present.
- **State Tracking:** Uses a hidden internal state to detect exactly which field changed.
- **Autocomplete Support:** Correctly "inflates" entity IDs back into full Doctrine objects during recovery.
- **Extensible:** Hook into the process using custom DTOs and Events.

---

Installation
------------

[](#installation)

```
composer require ucscode/easyadmin-dependency-field-resolver
```

---

Basic Usage
-----------

[](#basic-usage)

### 1. The Controller Setup

[](#1-the-controller-setup)

Inject the `DependencyFieldResolver` into your CRUD Controller and use it within your `configureFields` method.

```
use Ucscode\EasyAdmin\DependencyFieldResolver\Service\DependencyFieldResolver;

class UserCrudController extends AbstractCrudController
{
    public function __construct(
        private DependencyFieldResolver $resolver
    ) {}

    public function configureFields(string $pageName): iterable
    {
        return $this->resolver
            ->configureFields(function(): iterable {
                // Do exactly the same thing you would do in `configureFields()` of your crud controller
                // You can return an array or use `yield` to return a Generator
                // However, you should only return *INDEPENDENT* Fields
                yield TextField::new('username');

                yield ChoiceField::new('type')
                    ->setChoices([
                        'Individual' => 'individual',
                        'Organization' => 'org',
                    ]);
            })
            ->dependsOn('type', function(array $values): iterable {
                // This Closure will only run if 'type' is not null
                if ($values['type'] === 'org') {
                    yield TextField::new('companyName');
                    yield AssociationField::new('industry');
                }
            })
            ->dependsOn(['type', 'username'], function(array $values) use ($pageName): iterable {
                // This Closure will only run if both 'type' and 'username' are not null
                if ($values['username'] == 'joe' && $values['type'] == 'org') {
                    yield TextField::new('website');
                    return;
                }

                yield ChoiceField::new(...);
            })
            ->resolve();
    }
}
```

---

How It Works (Server-Side Lifecycle)
------------------------------------

[](#how-it-works-server-side-lifecycle)

This library operates entirely on the server side by hijacking the Symfony Form submission process before it reaches the persistence layer.

### 1. State Encapsulation

[](#1-state-encapsulation)

The `DependencyFieldResolver` generates a `HiddenField` named `__resolver_state`. This field contains an unmapped base64-encoded snapshot of the "monitored parents" at the time the form was rendered.

### 2. Difference Detection

[](#2-difference-detection)

When the form is submitted (e.g., via a "Save" button or a field that triggers a submit), an event listener (`DependencyStateListener`) listens to easyadmin's `BeforeCrudActionEvent` to compares:

- The **current POST data** (what the user just submitted).
- The **`__resolver_state`** (what the values were before the submission).

### 3. The Redirect Loop

[](#3-the-redirect-loop)

If a difference is detected in any monitored field:

1. The listener **intercepts** the request before the Controller can persist the data.
2. The current POST data is stored in the `ResolverDataBridge` (Session).
3. A `RedirectResponse` is issued to the same URL (GET request) to prevent false validation error message.

### 4. Data Recovery &amp; Dynamic Yielding

[](#4-data-recovery--dynamic-yielding)

On the subsequent GET request:

1. The `DependencyFormExtension` detects data in the `ResolverDataBridge`.
2. It injects this data back into the form fields, ensuring `EntityType` fields have their choices correctly populated.
3. The `DependencyFieldResolver` runs its closures. Since the parent values are now present in the Bridge, the dependent fields are **yielded** and rendered in the UI.

---

Architecture Summary
--------------------

[](#architecture-summary)

ComponentResponsibility**`DependencyFieldResolver`**Defines dependencies and yields fields based on available data.**`DependencyStateListener`**Compares POST vs. Hidden State; triggers the redirect.**`ResolverDataBridge`**Acts as temporary storage for form data across the redirect.**`DependencyFormExtension`**Reconstructs the form state from the Bridge during the GET request.---

Advanced: Events &amp; Data Refinement
--------------------------------------

[](#advanced-events--data-refinement)

You can modify the data during the transition using the `DependencyChangedEvent`. This is useful for clearing specific fields when a parent changes.

### Create a Subscriber

[](#create-a-subscriber)

```
use Ucscode\EasyAdmin\DependencyFieldResolver\Event\DependencyChangedEvent;

class DependencySubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            DependencyChangedEvent::class => 'onDependencyChange',
        ];
    }

    public function onDependencyChange(DependencyChangedEvent $event): void
    {
        $data = $event->getPostData();

        // If the type changes, we might want to force clear the company name
        if ($data->get('type') === 'individual') {
            $data->set('companyName', null);
        }
    }
}
```

---

1. Event System &amp; Usage Examples
------------------------------------

[](#1-event-system--usage-examples)

The library dispatches several events throughout the **Detection → Redirect → Recovery** lifecycle. These allow you to hook into the data flow to modify values, inject metadata, or manipulate fields dynamically.

### `DependencyChangedEvent`

[](#dependencychangedevent)

**Location:** Dispatched by the `DependencyStateListener` when it detects a change in a monitored parent field, just before the redirect.
**Usage:** Sanitize or "reset" dependent data.

```
public function onDependencyChange(DependencyChangedEvent $event): void
{
    $data = $event->getPostData(); // The ResolverPostData DTO

    // If 'country' changed, clear 'state' so old data doesn't persist
    if ($data->has('country')) {
        $data->set('state', null);
    }
}
```

### `DependencyDataRecoveredEvent`

[](#dependencydatarecoveredevent)

**Location:** Dispatched in the `FormExtension` when data is successfully pulled from the Bridge (Session).
**Usage:** Logging or performing global transformations on the recovered dataset.

### `DependencyFieldRehydrateEvent`

[](#dependencyfieldrehydrateevent)

**Location:** Dispatched for every individual field during the recovery phase.
**Usage:** This is the primary hook for custom "inflation." If you have a non-entity field (like a JSON object or a File) that needs special handling, do it here.

---

Handling Field Requirements
---------------------------

[](#handling-field-requirements)

When building dynamic forms, you may want certain dependent fields to be mandatory. While your first instinct might be to use `setRequired(true)`, there is a more flexible approach using Symfony Constraints that provides a better experience during the "Redirect &amp; Recovery" phase.

### The Recommendation

[](#the-recommendation)

For fields yielded inside a `dependsOn` closure, I recommend setting `setRequired(false)` and enforcing the requirement via **Symfony Constraints** (e.g., `NotBlank`).

### Why this is suggested

[](#why-this-is-suggested)

When a field is marked as `required` at the form level, EasyAdmin's internal pre-validation often block the form submission if that field is empty.

In a dependency flow, if a user changes a "parent" field but a previously required "child" field is now empty, the form might trigger a validation error immediately. By using constraints instead of the `required` flag, you allow the library to smoothly intercept the data and perform the redirect without the browser or the server's initial validation layer getting in the way.

### An Example

[](#an-example)

Let's assume you mark a dependent field **state** as `required` and the field depends on a **country**. This might create a "deadlock":

1. User selects **United States** and submits.
2. **State** field appears and is marked `required`.
3. User realizes they meant **United Kingdom** and changes the **Country** field.
4. User clicks "Submit".
5. **Validation Fails:** EasyAdmin sees the **State** field is empty but required.
6. The form refuses to submit.

### The Recommended Solution

[](#the-recommended-solution)

Set field to `required == false` and use **Symfony Constraints** with **Validation Groups** (or simple conditional constraints) to enforce the "required" state.

#### Example Implementation

[](#example-implementation)

In this example, the `state` field is logically required, but we handle that requirement through a constraint to ensure the "Redirect &amp; Recovery" cycle remains fluid.

```
->configureFields(function() {
    yield CountryField::new('country');
})
->dependsOn('country', function(array $values) {
    yield ChoiceField::new('state')
        // Use false to ensure the form can always submit for state-tracking
        ->setRequired(false)
        ->setFormTypeOptions([
            'constraints' => [
                // Use a constraint to ensure the data is valid upon final save
                new NotBlank([
                    'message' => 'Please select a state for ' . $values['country'],
                ])
            ]
        ]);
})
```

### Benefits of this approach

[](#benefits-of-this-approach)

- **Smoother Transitions:** Users can switch parent values without being "trapped" by browser-level validation tooltips on child fields that are about to change anyway.
- **Reliable State Tracking:** Ensures the `DependencyStateListener` always receives the POST data it needs to calculate the next state.
- **Full Validation:** Your data integrity remains intact; Symfony will still prevent a final save to the database if the `NotBlank` constraint is not met.

---

License
-------

[](#license)

MIT

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance85

Actively maintained with recent releases

Popularity9

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

103d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/65673b1b31e87471999a7614d107e7e061a38bf72191d149c66c1b943124e09c?d=identicon)[ucscode](/maintainers/ucscode)

---

Top Contributors

[![ucscode](https://avatars.githubusercontent.com/u/34024404?v=4)](https://github.com/ucscode "ucscode (6 commits)")

###  Code Quality

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ucscode-easyadmin-dependency-field-resolver/health.svg)

```
[![Health](https://phpackages.com/badges/ucscode-easyadmin-dependency-field-resolver/health.svg)](https://phpackages.com/packages/ucscode-easyadmin-dependency-field-resolver)
```

###  Alternatives

[wandi/easyadmin-plus-bundle

Wandi/EasyAdminPlusBundle

3926.1k](/packages/wandi-easyadmin-plus-bundle)[easycorp/easyadmin-demo

EasyAdmin Demo Application

145.7k](/packages/easycorp-easyadmin-demo)[insitaction/easyadmin-fields-bundle

Set of easyadmin fields and useful helpers for Insitaction

1910.3k](/packages/insitaction-easyadmin-fields-bundle)[umanit/easyadmin-tree-bundle

Plugin to add category tree features for EasyAdmin

237.0k](/packages/umanit-easyadmin-tree-bundle)[alshenetsky/easyadmin-breadcrumbs

A bundle that allows you to add breadcrumbs to EasyAdmin

124.9k](/packages/alshenetsky-easyadmin-breadcrumbs)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
