Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(doctrine): resource_class from context instead of entity class #6592

Open
wants to merge 3 commits into
base: 3.3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions features/graphql/query.feature
Original file line number Diff line number Diff line change
Expand Up @@ -677,3 +677,50 @@ Feature: GraphQL query support
Then the response status code should be 200
And the header "Content-Type" should be equal to "application/json"
And the JSON node "data.getSecurityAfterResolver.name" should be equal to "test"

@createSchema
@!mongodb
Scenario: Retrieve an item with a subResource collection within an DTO using stateOptions entityClass with an GraphQL query
Given there is an issue6590 foo object with 2 bar sub objects
When I send the following GraphQL request:
"""
{
issue6590OrmFoo(id: "/issue6590_orm_foos/1") {
id
bars {
edges {
node {
name
}
}
}
}
}
"""
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
And the JSON should be equal to:
"""
{
"data": {
"issue6590OrmFoo": {
"id": "/issue6590_orm_foos/1",
"bars": {
"edges": [
{
"node": {
"name": "bar1"
}
},
{
"node": {
"name": "bar2"
}
}
]
}
}
}
}
"""
2 changes: 1 addition & 1 deletion src/Doctrine/Orm/State/LinksHandlerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ private function handleLinks(QueryBuilder $queryBuilder, array $identifiers, Que
$doctrineClassMetadata = $manager->getClassMetadata($entityClass);
$alias = $queryBuilder->getRootAliases()[0];

$links = $this->getLinks($entityClass, $operation, $context);
$links = $this->getLinks($context['resource_class'] ?? $entityClass, $operation, $context);
Copy link
Member

Choose a reason for hiding this comment

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

From what I understand the problem occurs only when it's an embed resource? I'm wondering though why this entityClass is wrong, are you able to trace that call and fix it in the class responsible from calling this? Basically at https://github.com/api-platform/core/blob/3.4/src/Doctrine/Orm/State/ItemProvider.php#L74 it should already be the correct class.


if (!$links) {
return;
Expand Down
23 changes: 23 additions & 0 deletions tests/Behat/DoctrineContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue5722\ItemLog;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue5735\Group;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6039\Issue6039EntityUser;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6590\Bar as Issue6590BarDummy;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6590\Foo as Issue6590FooDummy;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\LinkHandledDummy;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MaxDepthDummy;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MultiRelationsDummy;
Expand Down Expand Up @@ -2325,6 +2327,27 @@ public function thereAreIssue6039Users(): void
$this->manager->flush();
}

/**
* @Given there is an issue6590 foo object with 2 bar sub objects
*/
public function thereIsAIssue6590DtoFooObjectWith2BarSubObjects(): void
{
$foo = new Issue6590FooDummy();
$this->manager->persist($foo);

$bar1 = new Issue6590BarDummy();
$bar1->setName('bar1');
$bar1->setFoo($foo);
$this->manager->persist($bar1);

$bar2 = new Issue6590BarDummy();
$bar2->setName('bar2');
$bar2->setFoo($foo);
$this->manager->persist($bar2);

$this->manager->flush();
}

private function isOrm(): bool
{
return null !== $this->schemaTool;
Expand Down
40 changes: 40 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/Issue6590/OrmBarResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue6590;

use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6590\Bar;
use ApiPlatform\Tests\Fixtures\TestBundle\State\Issue6590\BarResourceProvider;

#[ApiResource(
shortName: 'Issue6590OrmBar',
operations: [],
graphQlOperations: [
new Query(),
new QueryCollection(),
],
provider: BarResourceProvider::class,
stateOptions: new Options(entityClass: Bar::class)
)]
class OrmBarResource
{
#[ApiProperty(identifier: true)]
public int $id;

public string $name;
}
53 changes: 53 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/Issue6590/OrmFooResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue6590;

use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6590\Foo;
use ApiPlatform\Tests\Fixtures\TestBundle\State\Issue6590\FooResourceProvider;

#[ApiResource(
shortName: 'Issue6590OrmFoo',
operations: [],
graphQlOperations: [
new Query(),
new QueryCollection(),
],
provider: FooResourceProvider::class,
stateOptions: new Options(entityClass: Foo::class)
)]
class OrmFooResource
{
#[ApiProperty(identifier: true)]
public int $id;

/**
* @var OrmBarResource[]
*/
public array $bars;

public function addBar(OrmBarResource $bar): void
{
$this->bars[] = $bar;
}

public function removeBar(OrmBarResource $bar): void
{
unset($this->bars[array_search($bar, $this->bars, true)]);
}
}
61 changes: 61 additions & 0 deletions tests/Fixtures/TestBundle/Entity/Issue6590/Bar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6590;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity()]
#[ORM\Table(name: 'bar6590')]
class Bar
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private int $id;

#[ORM\Column]
private string $name;

#[ORM\ManyToOne(targetEntity: Foo::class, inversedBy: 'bars')]
private ?Foo $foo = null;

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

public function getName(): string
{
return $this->name;
}

public function setName(string $name): self
{
$this->name = $name;

return $this;
}

public function getFoo(): ?Foo
{
return $this->foo;
}

public function setFoo(?Foo $foo): self
{
$this->foo = $foo;

return $this;
}
}
59 changes: 59 additions & 0 deletions tests/Fixtures/TestBundle/Entity/Issue6590/Foo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6590;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'foo6590')]
class Foo
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private int $id;

#[ORM\OneToMany(targetEntity: Bar::class, mappedBy: 'foo')]
private Collection $bars;

public function __construct()
{
$this->bars = new ArrayCollection();
}

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

/**
* @return Collection<Bar>
*/
public function getBars(): Collection
{
return $this->bars;
}

/**
* @param Collection<Bar> $bars
*/
public function setBars(Collection $bars): self
{
$this->bars = $bars;

return $this;
}
}
64 changes: 64 additions & 0 deletions tests/Fixtures/TestBundle/State/Issue6590/BarResourceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\State\Issue6590;

use ApiPlatform\Doctrine\Orm\Paginator;
use ApiPlatform\Metadata\CollectionOperationInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\Pagination\TraversablePaginator;
use ApiPlatform\State\ProviderInterface;
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue6590\OrmBarResource;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6590\Bar as BarEntity;

class BarResourceProvider implements ProviderInterface
{
public function __construct(
private readonly ProviderInterface $itemProvider,
private readonly ProviderInterface $collectionProvider,
) {
}

public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
if ($operation instanceof CollectionOperationInterface) {
$entities = $this->collectionProvider->provide($operation, $uriVariables, $context);
\assert($entities instanceof Paginator);

$resources = [];
foreach ($entities as $entity) {
$resources[] = $this->getResource($entity);
}

return new TraversablePaginator(
new \ArrayIterator($resources),
$entities->getCurrentPage(),
$entities->getItemsPerPage(),
$entities->getTotalItems()
);
}

$entity = $this->itemProvider->provide($operation, $uriVariables, $context);

return $this->getResource($entity);
}

protected function getResource(BarEntity $entity): OrmBarResource
{
$resource = new OrmBarResource();
$resource->id = $entity->getId();
$resource->name = $entity->getName();

return $resource;
}
}
Loading
Loading