question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[BUG + Possible solution] [Semantical Error] line 0, col 63 near 'o_a1.id = :id_p1': Error: 'o_a1' is not defined.

See original GitHub issue

API 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:closed
  • Created a year ago
  • Comments:5

github_iconTop GitHub Comments

2reactions
ES-Sixcommented, Oct 5, 2022

If you don’t declare it in UriVariables, how is Api Platform able to use it? Does it have automatic “conversion”?

What happens if you explicitly set it in uriVariables?

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 :

new Get(
  ...,
  uriTemplate: '/schools/{id_school}/candidate-profiles/{id}',
  uriVariables: [
      'id' => new Link(fromClass: CandidateProfile::class)
  ],
);
// Not found error

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.

new Get(
  ...,
  uriTemplate: '/schools/{id_school}/candidate-profiles/{id}',
);
// Without uriVariables: [...]
// Not found error

Query produced is the same as above.

new Get(
  ...,
  uriTemplate: '/schools/{id_school}/candidate-profiles/{id}',
  uriVariables: [
      'id' => new Link(fromClass: CandidateProfile::class),
      'id_school' => new Link(fromClass: School::class)
  ],
);
// [Semantical Error] line 0, col 50 near 'o_a1.id = :id_p1': Error: 'o_a1' is not defined.

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).

class GenericEntityItemProvider implements ProviderInterface
{
    public function __construct(
        private readonly EntityManagerInterface $em
    ) {}

    /**
     * @throws NonUniqueResultException
     */
    public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|null
    {
        $identifierInUri = $context['operation']->getExtraProperties()['identifierInUri'] ?? 'id';

        $entity = $this->em->createQueryBuilder()
            ->select('Entity')
            ->from($context['resource_class'], 'Entity')
            ->andWhere('Entity.id = :id')
            ->setMaxResults(1)
            ->setParameters([
                'id' => $uriVariables[$identifierInUri]
            ])->getQuery()->getOneOrNullResult();

         // If $entity is null, API Platform ReadListener will trigger the 404 error automatically
        return $entity;
    }
}

So now I can do this and this disable the uriVariables of API Platform :

new Get(
  ...,
  uriTemplate: '/schools/{id_school}/candidate-profiles/{id}',
  security: 'rule set here...',
  openapiContext: ['parameters' => [... /* defined parameters for documentation here */]],
  provider: GenericEntityItemProvider::class,
);
// return the entity like before
0reactions
Aerendircommented, Oct 6, 2022

Here there is the solution to the problem.

Briefly, use fromProperty.

Read more comments on GitHub >

github_iconTop Results From Across the Web

[Semantical Error] line 0, col 61 near 't WHERE t.id ... - GitHub
[Semantical Error] line 0, col 61 near 't WHERE t.id': Error: Class AppBundle\Entity\Comment has no association named thread #615.
Read more >
Semantical Error line 0, col 63 near g, product p: Error: Class ...
The mistake I made was joining the entity class name instead of the association. My new code is:
Read more >
Bug List - Bugzilla
HTTPError: 400 Client Error: BAD REQUEST", Release Engineering ... It taks about 30-60sec to start uploading a video file to Youtube on Nightly57.0a1,...
Read more >
subdomains.txt - GitHub
... mumble guilinsijiazhentan error dalianhunyindiaocha bet365tiyu bailefang ... fix feilvbinshalong fang daffy col changzhoubaijiale caitongaibocailuntan ...
Read more >
best-dns-wordlist.txt - Index of /
... aws 13 eu www.get ip3 www.data report 80 bugs www.try www.mx v1 cacti oma ... ns16 mta63 mta109 mta100 mir mic mailscanner...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found