Skip to content

Embedded gets incorrectly treated as an association in PartialSearchFilter #7862

@bachinblack

Description

@bachinblack

API Platform version(s) affected: 4.3.1

Description

While trying to set up a FreeTextQueryFilter on my doctrine ORM entities, I discovered that Embedded objects did not seem to work with PartialSearchFilter.

In effect, trying to use the filter in a call will result in a doctrine error : Association name expected, 'address' is not an association. because the filter added a join to the QueryBuilder on a class that's not an association.

How to reproduce

Here is a simple entity with an embedded item and a failing QueryParameter :

<?php
# PostCard.php
namespace App\Entity;

use ApiPlatform\Doctrine\Orm\Filter\FreeTextQueryFilter;
use ApiPlatform\Doctrine\Orm\Filter\PartialSearchFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\QueryParameter;
use Doctrine\ORM\Mapping as ORM;


#[QueryParameter(
    key: 'free_search',
    filter: new FreeTextQueryFilter(new PartialSearchFilter()),
    properties: [
        'address.city',
    ],
)]
#[ApiResource()]
#[ORM\Entity]
class PostCard
{
    #[ORM\Id]
    #[ORM\Column(type: 'integer')]
    #[ORM\GeneratedValue(strategy: 'SEQUENCE')]
    private ?int $id = null;

    public function __construct(
        #[ORM\Embedded(class: Address::class)]
        public readonly Address $address
    ) {
        throw new \Exception('Not implemented');
    }

    public function getId(): ?int
    {
        return $this->id;
    }
}

# Address.php
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Embeddable]
class Address
{
    public function __construct(
        #[ORM\Column]
        public readonly int $city,

        #[ORM\Column]
        public readonly string $street,

        #[ORM\Column]
        public readonly string $zipCode,
    ) {}
}

Calling https://localhost/post_cards?free_search=test will result in the aforementioned error

Possible Solution

In PartialSearchFilter.php:46, addNestedParameterJoins(), is always called, regardless of wether the property is acually a relation, and the method itself just checks extraProperties['nested_properties_info'], which is provided for Embedded.
In SearchFilter, isPropertyNested() is called first, to ensure the property is a relation, which prevents the join.
This method looks for the relation in doctrine metadatas, which prevents mistaking Embedded for relations : return $this->getClassMetadata($resourceClass)->hasAssociation(substr($property, 0, $pos))

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions