API Platform version(s) affected: 4.3.3
Description
When using the ExactFilter on a nested entity containing a Doctrine embeddable, to filter and only get records matching one of the embeddable properties, I've got the following error: [Semantical Error] [...] Error: Class [...] has no field or association named [...].
For example:
I've got a Foo entity with a $bars property which is a 1-n association with a Bar entity.
The Bar entity has a Baz embeddable, which "groups" several properties, like a $qux property.
I want Foo records where qux is equal to 1.
The url will look like this: /foos?bars.baz.qux=1.
If I hadn't an embeddable, and my entity was "flat", it would be equivalent to this: /foos?bars.bazQux=1.
When I query /foos?bars.baz.qux=1, I've got the following error (which is thrown by Doctrine btw):
[Semantical Error] line 0, col 1210 near 'qux = :bars_baz_qux_p3)': Error: Class Foo has no field or association named bars.baz.qux.
When I looked at the generated DQL, I noticed it uses the wrong alias: it uses the alias of the Foo entity instead of the Bar entity.
That's why Doctrine raises an exception, it looks for a bars.baz.qux field on the Foo entity, instead of looking for a baz.qux field on the Bar entity.
SELECT o_a3
FROM Foo o_a3
INNER JOIN o_a3.bars bars_a4
WHERE o_a3.bars.baz.qux = :bars_baz_qux_p3
Here, instead of o_a3.bars.baz.qux, it should be bars_a4.baz.qux.
How to reproduce
The embeddable:
<?php
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Embeddable]
class Baz
{
#[ORM\Column(nullable: true)]
public ?int $qux = null;
#[ORM\Column(nullable: true)]
public ?string $anotherProp = null;
}
The nested entity:
<?php
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Uid\Uuid;
#[ORM\Entity]
class Bar
{
#[ORM\Id]
#[ORM\Column(type: 'uuid', unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
public ?Uuid $id = null;
#[ORM\Embedded(class: Baz::class)]
public Baz $baz;
}
The main resource/entity:
<?php
use ApiPlatform\Doctrine\Orm\Filter\ExactFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\QueryParameter;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Uid\Uuid;
#[ORM\Entity]
#[ApiResource(
operations: [
new GetCollection(
parameters: [
'bar.baz.qux' => new QueryParameter(
filter: new ExactFilter(),
),
],
)
]
)]
class Foo
{
#[ORM\Id]
#[ORM\Column(type: 'uuid', unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
public ?Uuid $id = null;
/** @var Collection<int, Bar> */
#[ORM\OneToMany(targetEntity: Bar::class)]
public Collection $bars;
public function __construct()
{
$this->bars = new ArrayCollection();
}
}
Possible Solution
The problem probably lies in the lines 57 & 58 of the ApiPlatform\Doctrine\Orm\Filter\ExactFilter class.
$queryBuilder
->{$context['whereClause'] ?? 'andWhere'}(\sprintf('%s.%s %s :%s', $alias, $property, $operator, $parameterName));
I guess one solution could be to read the doctrine metadata to detect whether the property is part of an embeddable, get the name of the parent entity, and figure out the alias used in the JOIN.
Easy to say I guess...
API Platform version(s) affected: 4.3.3
Description
When using the
ExactFilteron a nested entity containing a Doctrine embeddable, to filter and only get records matching one of the embeddable properties, I've got the following error:[Semantical Error] [...] Error: Class [...] has no field or association named [...].For example:
I've got a
Fooentity with a$barsproperty which is a 1-n association with aBarentity.The
Barentity has aBazembeddable, which "groups" several properties, like a$quxproperty.I want
Foorecords wherequxis equal to1.The url will look like this:
/foos?bars.baz.qux=1.If I hadn't an embeddable, and my entity was "flat", it would be equivalent to this:
/foos?bars.bazQux=1.When I query
/foos?bars.baz.qux=1, I've got the following error (which is thrown by Doctrine btw):[Semantical Error] line 0, col 1210 near 'qux = :bars_baz_qux_p3)': Error: Class Foo has no field or association named bars.baz.qux.When I looked at the generated DQL, I noticed it uses the wrong alias: it uses the alias of the
Fooentity instead of theBarentity.That's why Doctrine raises an exception, it looks for a
bars.baz.quxfield on theFooentity, instead of looking for abaz.quxfield on theBarentity.Here, instead of
o_a3.bars.baz.qux, it should bebars_a4.baz.qux.How to reproduce
The embeddable:
The nested entity:
The main resource/entity:
Possible Solution
The problem probably lies in the lines
57&58of theApiPlatform\Doctrine\Orm\Filter\ExactFilterclass.I guess one solution could be to read the doctrine metadata to detect whether the property is part of an embeddable, get the name of the parent entity, and figure out the alias used in the
JOIN.Easy to say I guess...