Skip to content

Fix nested associations and embedded properties - #7500

Merged
javiereguiluz merged 4 commits into
EasyCorp:5.xfrom
Seb33300:filters-nested-asso
Jul 25, 2026
Merged

Fix nested associations and embedded properties#7500
javiereguiluz merged 4 commits into
EasyCorp:5.xfrom
Seb33300:filters-nested-asso

Conversation

@Seb33300

@Seb33300 Seb33300 commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Same as #7295 with a different approach.

Fixes #7452, #6664 and #7514

In resolveNestedAssociations() I have extracted the logic handling nested associations from search to make it reusable.
It also properly handles doctrine embeddables.

It's now possible to use associations in filters using the dot syntax:

    public function configureFilters(Filters $filters): Filters
    {
        return $filters
            // author is an association
            ->add(TextFilter::new('author.fullName', 'Author name'))
            // address is an Embeddable
            ->add(TextFilter::new('author.address.country', 'Author country'));
    }

This PR also includes contributions from @KDederichs to reuse resolveNestedAssociations() and fix usage of nested associations in AssociationField and CollectionField

@Seb33300
Seb33300 force-pushed the filters-nested-asso branch from 18c6ca4 to 646cbcb Compare March 12, 2026 04:19
@Seb33300
Seb33300 marked this pull request as draft March 12, 2026 04:28
@Seb33300
Seb33300 force-pushed the filters-nested-asso branch 2 times, most recently from a009235 to 6a93dff Compare March 12, 2026 06:09
@Seb33300
Seb33300 marked this pull request as ready for review March 12, 2026 06:14
@Seb33300
Seb33300 marked this pull request as draft March 12, 2026 07:11
@Seb33300
Seb33300 force-pushed the filters-nested-asso branch from 6a93dff to 1b66e5e Compare March 12, 2026 07:29
@Seb33300

Copy link
Copy Markdown
Contributor Author

Just found an issue with the EntityFilter + nested associations, I am investigating.

@Seb33300
Seb33300 force-pushed the filters-nested-asso branch 3 times, most recently from 9fbf27a to 1a53387 Compare March 12, 2026 09:18
* property_name: string,
* }
*/
public function resolveNestedAssociations(?QueryBuilder $queryBuilder, EntityDto $rootEntityDto, string $propertyName, bool $mustEndWithAssociation = false): array

@Seb33300 Seb33300 Mar 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While extracting that logic from search, I also modified:

  • $queryBuilder is nullable so the resolveNestedAssociations() method can be called to resolve the nested EntityDto & property name without applying the joins to the query (eg: EntityConfigurator use it to resolve the entity used to fetch choice list)
  • $mustEndWithAssociation is used to allow property ending on an association field (eg: post.author instead of post.author.fullName) as required by EntityFilter

@Seb33300
Seb33300 marked this pull request as ready for review March 12, 2026 09:33
@Seb33300

Copy link
Copy Markdown
Contributor Author

Issue on EntityFilter is now fixed

@Seb33300
Seb33300 force-pushed the filters-nested-asso branch 3 times, most recently from 4823239 to 4758cb5 Compare March 12, 2026 10:00
Comment on lines +242 to +247
// Fallback to support custom filters with unmapped property names
$resolvedProperty = [
'entity_dto' => $entityDto,
'entity_alias' => current($queryBuilder->getRootAliases()),
'property_name' => $originalPropertyName,
];

@Seb33300 Seb33300 Mar 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this fallback, AuthorWithMinPostsFilter was not working in the EasyAdmin demo project.

@Seb33300 Seb33300 Mar 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note about this:

Instead of try/catch, it would be cleaner to check if the filter is unmapped.
This may be achieved by checking the related form option as suggested by EA documentation:
https://symfony.com/bundles/EasyAdminBundle/current/filters.html#unmapped-filters

However, it looks like previous code was working on unmapped fields even if the mapped option was not specified.
For example, the custom filter from EA demo is not defining the mapped option, and it still work:
https://github.com/EasyCorp/easyadmin-demo/blob/main/src/Admin/Filter/AuthorWithMinPostsFilter.php

And if we replace the try/catch by a check on the form type option, this may introduce a BC break on unmapped filter that does not specify the unmapped option, but this should also fix this bug: #6386

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Seb33300
Seb33300 force-pushed the filters-nested-asso branch 4 times, most recently from e8ad127 to 1be57a5 Compare March 12, 2026 15:54
@Seb33300
Seb33300 marked this pull request as draft April 15, 2026 03:48
@Seb33300
Seb33300 marked this pull request as ready for review April 15, 2026 03:48
@dmitryuk

Copy link
Copy Markdown
Contributor

@javiereguiluz please review

@ckaotik

ckaotik commented May 19, 2026

Copy link
Copy Markdown

This is awesome! Should we add the new resolveNestedAssociations method to EasyCorp\Bundle\EasyAdminBundle\Contracts\Orm\EntityRepositoryInterface?

@Seb33300

Copy link
Copy Markdown
Contributor Author

This PR is missing some tests, and I believe this one of the reasons why it's not reviewed/merged yet.
If someone has time to work on implementing tests, I can merge your commit in my branch.

@KDederichs

Copy link
Copy Markdown
Contributor

@Seb33300 Done, take a look. It looks a bit messy cause I included #7584 and #7585 and merged 5.x to resolve some conflicts.

@Seb33300
Seb33300 force-pushed the filters-nested-asso branch from 1be57a5 to 6459419 Compare July 4, 2026 11:47
* Fix filters on nested associations and embedded properties

* Add tests for EntityRepository and EntityConfigurator

* Import EasyCorp#7584 into main PR

* Import EasyCorp#7585 into main PR

* Fix tests after merge

* remove outdated comment, change parameter order for createEntityDto and moved it back down

* fix parameters for createEntityDto and reorder it

* minor cleanup

* Fix Phpstan issues

* Fix PHP-Linter (I hope)

* Add missing linebreaks

---------

Co-authored-by: Sébastien Alfaiate <s.alfaiate@webarea.fr>
@Seb33300 Seb33300 changed the title Fix filters on nested associations and embedded properties Fix nested associations and embedded properties Jul 4, 2026
@KDederichs

Copy link
Copy Markdown
Contributor

Do we need to notify Javier that this changed btw? Don't see him in the list of participants so not sure

@Seb33300

Seb33300 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Hi @javiereguiluz, if you have any concern about this PR, please let us know so we can improve it.

@finnef

finnef commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Hi, great work!

I tested the latest patch on EA 5.1.0 and one thing that did not work was adding a field like this to my crudcontroller:

AssociationField::new('coachProfile.labels')

Where the CrudController is for users, and the property is users.coachProfile.labels (ManyToMany). This throws an error 'Can't get a way to read the property "id" in class "Doctrine\ORM\PersistentCollection"'. In this case the AssociationConfigurator does not distinguish to-one vs to-many via isSingleValuedAssociation()/isCollectionValuedAssociation(). It always assumes the final segment is to-one.

Maybe this is out of scope for this issue?

@Seb33300

Seb33300 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Hi @finnef, can you please try again and confirm if this is working now? I just pushed a fix for this.

@finnef

finnef commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Great! that works fine now. Good addition.

@finnef

finnef commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Ah, sorry to say there is still an issue.

When accessing the NEW page of my CoachCrudcontroller (of entity user), which has an CollectionField::new('coachProfile.attachments'), so it should access user.coachProfile.attachments, I get an error

"Uncaught PHP Exception Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException: "Can't get a way to read the property "attachments" in class "App\Entity\User"." at PropertyAccessor.php line 456"

@Seb33300

Seb33300 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Hi @finnef just pushed another fix, if you can test it

@finnef

finnef commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Latest patch works everywhere for me!

@KDederichs

Copy link
Copy Markdown
Contributor

If it helps make a decision if this is gonna be merged anytime soon...
I've been running this in production for a long while now (the original PR and now the extended version) and it's been running just fine without any issues.
I'd really love to see this finally added to the code base officially without having to patch it in every time

@javiereguiluz javiereguiluz added this to the 5.x milestone Jul 25, 2026
@javiereguiluz
javiereguiluz merged commit c77572a into EasyCorp:5.x Jul 25, 2026
16 checks passed
@javiereguiluz

Copy link
Copy Markdown
Collaborator

This is now merged! Thanks a lot @Seb33300 and all reviewers and testers!

While merging we did some changes (36b92aa):

  • Per-QueryBuilder JOIN cache (EntityRepository): the join cache was a service property that was never reset → broken queries in sub-requests and worker runtimes (FrankenPHP, etc.). Now a WeakMap keyed by QueryBuilder; the class is final readonly again.
  • Respect custom root aliases: the resolver hardcoded 'entity'; it now uses $queryBuilder->getRootAliases(), so custom createIndexQueryBuilder() implementations keep working.
  • New ResolvedPropertyDto: replaces the array{entity_dto, entity_alias, property_name} shape returned by resolveNestedAssociations().
  • EntityRepositoryInterface restored: the new method is a BC break for implementors; it moved to a new additive contract, NestedAssociationResolverInterface (aliased in services.php, injected in the three configurators).
  • FilterDataDto BC restored: new() keeps its original signature (string $entityAlias); resolution info is an optional 5th ?ResolvedPropertyDto argument.
  • Fixed nested to-many EntityFilter: apply() checked to-many-ness on the root entity's metadata → invalid DQL for e.g. EntityFilter::new('author.books'). It now uses the resolved entity via the new FilterDataDto::getEntityDto().
  • EntityFilter subclasses: EntityFilter::class === $filter->getFqcn()is_a().
  • EntityConfigurator graceful guard restored: a non-association property no-ops again instead of throwing a raw Doctrine MappingException.
  • Safer property mutate-and-restore: Association/CollectionConfigurator restore the original field property in try/finally.
  • Tests: regressions for per-QB joins, custom root aliases, alias leakage, FilterDataDto BC, the configurator guard and nested to-many filters.
  • Docs: new "Filters on Associated Properties" section in filters.rst; nested dot-syntax mentions in AssociationField/CollectionField pages.

@javiereguiluz javiereguiluz mentioned this pull request Jul 25, 2026
@Seb33300
Seb33300 deleted the filters-nested-asso branch July 25, 2026 11:12
@Seb33300

Seb33300 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

Hi @javiereguiluz,

I only have 1 concern regarding this point:

Respect custom root aliases: the resolver hardcoded 'entity'; it now uses $queryBuilder->getRootAliases(), so custom createIndexQueryBuilder() implementations keep working.

Isn't this a BC break if we used that alias inside a ->setQueryBuilder() of an AssociationField?

I have many places where I did something like that in my projects:

AssociationField::new(...)
    ->setQueryBuilder(fn (QueryBuilder $queryBuilder) => $queryBuilder
        ->where('entity.field = :field')
        ->setParameter('field', $this->getMyValue())
    )

@Seb33300

Copy link
Copy Markdown
Contributor Author

My bad, just checked the code and realized I misunderstood this line. Seems all good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Filter] Unable to use EntityFilter on nested accociations

6 participants