[BUG + Possible solution] [Semantical Error] line 0, col 63 near 'o_a1.id = :id_p1': Error: 'o_a1' is not defined.
See original GitHub issueAPI Platform version(s) affected: 3.0.0 | 3.0.1
Description
When upgraded from 2.7.0 to 3.0.0, I used the automatic tool provided by ApiPlatform.
It created some more operations on my entities but they seem not work.
For example, I have an Account entity and a Channel entity like below:
# Entity/Account
#[ApiResource]
#[Entity(repositoryClass: AccountRepository::class)]
class Account implements \Stringable, Timestampable
{
use TimestampableEntity;
#[Column(name: 'id', type: Types::INTEGER)]
#[Id]
#[GeneratedValue(strategy: 'IDENTITY')]
private int $id;
#[OneToMany(targetEntity: Channel::class, mappedBy: 'account')]
private Collection $channels;
...
}
# Entity/Channel
#[API\ApiResource]
#[API\ApiResource(uriTemplate: '/accounts/{id}/channels.{_format}', uriVariables: ['id' => new API\Link(fromClass: Account::class, identifiers: ['id'])], status: 200, operations: [new API\GetCollection()])]
#[API\ApiResource(uriTemplate: '/users/{id}/accounts/{accounts}/channels.{_format}', uriVariables: ['id' => new API\Link(fromClass: User::class, identifiers: ['id']), 'accounts' => new API\Link(fromClass: Account::class, identifiers: ['id'])], status: 200, operations: [new API\GetCollection()])]
#[Entity(repositoryClass: ChannelRepository::class)]
class Channel implements ConfigurableRemote, \Stringable, Timestampable
{
use RemoteTrait;
use TimestampableEntity;
#[Column(name: 'id', type: Types::INTEGER)]
#[Id]
#[GeneratedValue(strategy: 'IDENTITY')]
private int $id;
#[ManyToOne(targetEntity: Account::class, inversedBy: 'channels')]
private Account $account;
...
}
When I call /api/accounts/1/channels
, I get the following error from Doctrine:
[Semantical Error] line 0, col 63 near ‘o_a1.id = :id_p1’: Error: ‘o_a1’ is not defined.
Additional Context
The query that causes the bug is this:
SELECT o FROM App\Entity\Channel o WHERE o_a1.id = :id_p1 ORDER BY o.id ASC
As you can see, it uses o_a1
as table alias, while it sets the alias of the table to o
. So it seems there is a problem generating the correct DQL.
Digging into the code following the origin of the bug, I came to the trait ApiPlatform\Doctrine\Orm\State\LinksHandlerTrait::57
:
This is the interested code:
if (!$link->getFromProperty() && !$link->getToProperty()) {
$doctrineClassMetadata = $manager->getClassMetadata($link->getFromClass());
/* 57 */ $currentAlias = $link->getFromClass() === $resourceClass ? $alias : $queryNameGenerator->generateJoinAlias($alias);
foreach ($identifierProperties as $identifierProperty) {
$placeholder = $queryNameGenerator->generateParameterName($identifierProperty);
$queryBuilder->andWhere("$currentAlias.$identifierProperty = :$placeholder");
$queryBuilder->setParameter($placeholder, $this->getIdentifierValue($identifiers, $hasCompositeIdentifiers ? $identifierProperty : null), $doctrineClassMetadata->getTypeOfField($identifierProperty));
}
$previousAlias = $currentAlias;
$previousJoinProperties = $doctrineClassMetadata->getIdentifierFieldNames();
continue;
}
At line “57” the code is executed well: I have two classes:
Account
Channel
so the code correctly calls the method $queryNameGenerator->generateJoinAlias($alias)
.
The problem is that the query builder passed, in the DQL parts, for the select
part, has the o
value already set while the method $queryNameGenerator->generateJoinAlias($alias)
returns o_a1
.
Possible solution
The solution I tested is very simple: change ===
with !==
:
if (!$link->getFromProperty() && !$link->getToProperty()) {
$doctrineClassMetadata = $manager->getClassMetadata($link->getFromClass());
- /* 57 */ $currentAlias = $link->getFromClass() === $resourceClass ? $alias : $queryNameGenerator->generateJoinAlias($alias);
+ /* 57 */ $currentAlias = $link->getFromClass() !== $resourceClass ? $alias : $queryNameGenerator->generateJoinAlias($alias);
foreach ($identifierProperties as $identifierProperty) {
$placeholder = $queryNameGenerator->generateParameterName($identifierProperty);
$queryBuilder->andWhere("$currentAlias.$identifierProperty = :$placeholder");
$queryBuilder->setParameter($placeholder, $this->getIdentifierValue($identifiers, $hasCompositeIdentifiers ? $identifierProperty : null), $doctrineClassMetadata->getTypeOfField($identifierProperty));
}
$previousAlias = $currentAlias;
$previousJoinProperties = $doctrineClassMetadata->getIdentifierFieldNames();
continue;
}
Doing this simple change, makes the DQL being generated correctly.
As I don’t know deeply the internals of Api Platform, I don’t know if this is an actual fix or simply a lucky shot.
UPDATE
Continuing to work on this, it seems the fix I provided is wrong: the problem is the logic is broken.
The fix I provided works with GetCollection
.
But with Get
it is completely broken (or, maybe, the documentation is wrong and I’m missing something in configuration of operations).
Anyway, the two DQL queries generated are wrong in any case:
$currentAlias = $link->getFromClass() !== $resourceClass ? $alias : $queryNameGenerator->generateJoinAlias($alias);
generates
SELECT o FROM Coommercio\App\Coommercio\Entity\Channel o WHERE o.id = :id_p1 AND o_a1.id = :id_p2
while
$currentAlias = $link->getFromClass() === $resourceClass ? $alias : $queryNameGenerator->generateJoinAlias($alias);
generates
SELECT o FROM Coommercio\App\Coommercio\Entity\Channel o WHERE o_a1.id = :id_p1 AND o.id = :id_p2
In both cases, the alias o_a1
is not defined.
More, the query seems to be wrong also for the fields it uses. The correct one should be similar to this:
SELECT o FROM Coommercio\App\Coommercio\Entity\Channel o WHERE o.id = :id_p1 AND o.account_id = :id_p2
Issue Analytics
- State:
- Created a year ago
- Comments:5
Top GitHub Comments
issue details
In my exemple, id_school is an arbitrary value that I can’t use for now with the new implementation as in the database joins sould be like this : Candidate profile entity has ManyToOne with Campaign entity and Campaign entity has ManyToOne with School entity.
My goal is to : list every candidate profiles linked to a school resource.
So I tried with and without defining all parameters, but it can’t work because entities are not directly joinable as there is an intermediate entity between them (Campaign).
I tested these cases :
Exemple, when I take this uri : /schools/1/candidate-profiles/6
The exemple above throw the not found error because in the query I see c0_.id = ? AND c0_.id = ?. The problem here is id_school seem’s to be used by uriVariables as the first value for ? is 6 which is good, but the second is 1 which is not good as it’s id_school.
Query produced is the same as above.
When defining all new Link(…), I get the semantical error
[Semantical Error] line 0, col 50 near 'o_a1.id = :id_p1': Error: 'o_a1' is not defined.
.Because There is no school property in CandidateProfile::class as this property is in the intermediate table (Campaign).
With the previous metadata system this was working fine as i used id_school as an arbitrary parameter that was validated on my side with “security:” and only {id} was used. But with the new system I can’t.
I will try to bypass this issue by making a custom State Provider that reproduce the original behaviour (that will only use the {id} parameter).
bypass the issue
Edit : successfully bypass the issue by making a StateProvider that get by ID only (to reproduce the legacy behaviour).
So now I can do this and this disable the uriVariables of API Platform :
Here there is the solution to the problem.
Briefly, use
fromProperty
.