From e66ed60aa0ea4065f75cbd8c24ef07d8f2b14abb Mon Sep 17 00:00:00 2001 From: abluchet Date: Wed, 10 May 2017 11:30:12 +0200 Subject: [PATCH] Fix route name resolving with subresources Also resolves conflicts with existing subresources on a property having the same name --- features/bootstrap/FeatureContext.php | 22 ++++ features/main/subresource.feature | 48 ++++++- src/Bridge/Symfony/Routing/ApiLoader.php | 2 +- .../Routing/CachedRouteNameResolver.php | 11 +- src/Bridge/Symfony/Routing/IriConverter.php | 4 +- .../Symfony/Routing/RouteNameResolver.php | 30 ++++- .../Routing/RouteNameResolverInterface.php | 2 +- src/Hydra/Serializer/CollectionNormalizer.php | 2 +- .../Routing/CachedRouteNameResolverTest.php | 16 ++- .../Symfony/Routing/IriConverterTest.php | 10 +- .../Symfony/Routing/RouteNameResolverTest.php | 12 +- .../TestBundle/Entity/DummyAggregateOffer.php | 120 ++++++++++++++++++ .../Fixtures/TestBundle/Entity/DummyOffer.php | 75 +++++++++++ .../TestBundle/Entity/DummyProduct.php | 120 ++++++++++++++++++ 14 files changed, 454 insertions(+), 20 deletions(-) create mode 100644 tests/Fixtures/TestBundle/Entity/DummyAggregateOffer.php create mode 100644 tests/Fixtures/TestBundle/Entity/DummyOffer.php create mode 100644 tests/Fixtures/TestBundle/Entity/DummyProduct.php diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index 655d3b90f88..40b9e8be797 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -17,9 +17,12 @@ use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Container; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyAggregateOffer; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCarColor; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyFriend; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyOffer; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyProduct; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Node; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Question; @@ -474,4 +477,23 @@ public function thePasswordForUserShouldBeHashed($password, $user) throw new \Exception('User password mismatch'); } } + + /** + * @Given I have a product with offers + */ + public function createProductWithOffers() + { + $offer = new DummyOffer(); + $offer->setValue(2); + $aggregate = new DummyAggregateOffer(); + $aggregate->setValue(1); + $aggregate->addOffer($offer); + + $product = new DummyProduct(); + $product->setName('Dummy product'); + $product->addOffer($aggregate); + + $this->manager->persist($product); + $this->manager->flush(); + } } diff --git a/features/main/subresource.feature b/features/main/subresource.feature index 91ef169adf0..02967934e06 100644 --- a/features/main/subresource.feature +++ b/features/main/subresource.feature @@ -204,7 +204,6 @@ Feature: Subresource support } """ - @dropSchema Scenario: Get the embedded relation collection When I send a "GET" request to "/dummies/1/related_dummies/1/third_level" And the response status code should be 200 @@ -222,3 +221,50 @@ Feature: Subresource support } """ + Scenario: Get offers subresource from aggregate offers subresource + Given I have a product with offers + When I send a "GET" request to "/dummy_products/1/offers/1/offers" + And the response status code should be 200 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + And the JSON should be equal to: + """ + { + "@context": "/contexts/DummyOffer", + "@id": "/dummy_products/1/offers/1/offers", + "@type": "hydra:Collection", + "hydra:member": [ + { + "@id": "/dummy_offers/1", + "@type": "DummyOffer", + "id": 1, + "value": 2 + } + ], + "hydra:totalItems": 1 + } + """ + + @dropSchema + Scenario: Get offers subresource from aggregate offers subresource + When I send a "GET" request to "/dummy_aggregate_offers/1/offers" + And the response status code should be 200 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + And the JSON should be equal to: + """ + { + "@context": "/contexts/DummyOffer", + "@id": "/dummy_aggregate_offers/1/offers", + "@type": "hydra:Collection", + "hydra:member": [ + { + "@id": "/dummy_offers/1", + "@type": "DummyOffer", + "id": 1, + "value": 2 + } + ], + "hydra:totalItems": 1 + } + """ diff --git a/src/Bridge/Symfony/Routing/ApiLoader.php b/src/Bridge/Symfony/Routing/ApiLoader.php index 5026c9d9741..605411d16a8 100644 --- a/src/Bridge/Symfony/Routing/ApiLoader.php +++ b/src/Bridge/Symfony/Routing/ApiLoader.php @@ -173,7 +173,7 @@ private function computeSubresourceOperations(RouteCollection $routeCollection, '_controller' => self::DEFAULT_ACTION_PATTERN.'get_subresource', '_format' => null, '_api_resource_class' => $subresource, - '_api_subresource_operation_name' => 'get_subresource_'.$operation['property'], + '_api_subresource_operation_name' => $operation['route_name'], '_api_subresource_context' => [ 'property' => $operation['property'], 'identifiers' => $operation['identifiers'], diff --git a/src/Bridge/Symfony/Routing/CachedRouteNameResolver.php b/src/Bridge/Symfony/Routing/CachedRouteNameResolver.php index da31bf989c7..371832c8e8a 100644 --- a/src/Bridge/Symfony/Routing/CachedRouteNameResolver.php +++ b/src/Bridge/Symfony/Routing/CachedRouteNameResolver.php @@ -37,7 +37,7 @@ public function __construct(CacheItemPoolInterface $cacheItemPool, RouteNameReso /** * {@inheritdoc} */ - public function getRouteName(string $resourceClass, $operationType): string + public function getRouteName(string $resourceClass, $operationType /**, array $context = []**/): string { $cacheKey = self::CACHE_KEY_PREFIX.md5(serialize([$resourceClass, $operationType])); @@ -51,7 +51,14 @@ public function getRouteName(string $resourceClass, $operationType): string // do nothing } - $routeName = $this->decorated->getRouteName($resourceClass, $operationType); + if (func_num_args() > 2) { + $context = func_get_arg(2); + } else { + $context = []; + @trigger_error(sprintf('Method %s() will have a third `$context = []` argument in version 3.0. Not defining it is deprecated since 2.1.', __METHOD__), E_USER_DEPRECATED); + } + + $routeName = $this->decorated->getRouteName($resourceClass, $operationType, $context); if (!isset($cacheItem)) { return $routeName; diff --git a/src/Bridge/Symfony/Routing/IriConverter.php b/src/Bridge/Symfony/Routing/IriConverter.php index 757d17efa39..8a7a4366a8c 100644 --- a/src/Bridge/Symfony/Routing/IriConverter.php +++ b/src/Bridge/Symfony/Routing/IriConverter.php @@ -125,10 +125,10 @@ public function getItemIriFromResourceClass(string $resourceClass, array $identi /** * {@inheritdoc} */ - public function getSubresourceIriFromResourceClass(string $resourceClass, array $identifiers, int $referenceType = UrlGeneratorInterface::ABS_PATH): string + public function getSubresourceIriFromResourceClass(string $resourceClass, array $context, int $referenceType = UrlGeneratorInterface::ABS_PATH): string { try { - return $this->router->generate($this->routeNameResolver->getRouteName($resourceClass, OperationType::SUBRESOURCE), $identifiers, $referenceType); + return $this->router->generate($this->routeNameResolver->getRouteName($resourceClass, OperationType::SUBRESOURCE, $context), $context['subresource_identifiers'], $referenceType); } catch (RoutingExceptionInterface $e) { throw new InvalidArgumentException(sprintf('Unable to generate an IRI for "%s".', $resourceClass), $e->getCode(), $e); } diff --git a/src/Bridge/Symfony/Routing/RouteNameResolver.php b/src/Bridge/Symfony/Routing/RouteNameResolver.php index 2cf137fd515..1ecf15dfef4 100644 --- a/src/Bridge/Symfony/Routing/RouteNameResolver.php +++ b/src/Bridge/Symfony/Routing/RouteNameResolver.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Bridge\Symfony\Routing; +use ApiPlatform\Core\Api\OperationType; use ApiPlatform\Core\Api\OperationTypeDeprecationHelper; use ApiPlatform\Core\Exception\InvalidArgumentException; use Symfony\Component\Routing\RouterInterface; @@ -34,8 +35,15 @@ public function __construct(RouterInterface $router) /** * {@inheritdoc} */ - public function getRouteName(string $resourceClass, $operationType): string + public function getRouteName(string $resourceClass, $operationType /**, array $context = [] **/): string { + if (func_num_args() > 2) { + $context = func_get_arg(2); + } else { + $context = []; + @trigger_error(sprintf('Method %s() will have a third `$context = []` argument in version 3.0. Not defining it is deprecated since 2.1.', __METHOD__), E_USER_DEPRECATED); + } + $operationType = OperationTypeDeprecationHelper::getOperationType($operationType); foreach ($this->router->getRouteCollection()->all() as $routeName => $route) { @@ -44,10 +52,30 @@ public function getRouteName(string $resourceClass, $operationType): string $methods = $route->getMethods(); if ($resourceClass === $currentResourceClass && null !== $operation && (empty($methods) || in_array('GET', $methods, true))) { + if ($operationType === OperationType::SUBRESOURCE && false === $this->isSameSubresource($context, $route->getDefault('_api_subresource_context'))) { + continue; + } + return $routeName; } } throw new InvalidArgumentException(sprintf('No %s route associated with the type "%s".', $operationType, $resourceClass)); } + + private function isSameSubresource(array $context, array $currentContext): bool + { + $subresources = array_keys($context['subresource_resources']); + $currentSubresources = []; + + foreach ($currentContext['identifiers'] as $identiferContext) { + $currentSubresources[] = $identiferContext[1]; + } + + if ($currentSubresources === $subresources) { + return true; + } + + return false; + } } diff --git a/src/Bridge/Symfony/Routing/RouteNameResolverInterface.php b/src/Bridge/Symfony/Routing/RouteNameResolverInterface.php index efa5c52276d..8c97cfba27f 100644 --- a/src/Bridge/Symfony/Routing/RouteNameResolverInterface.php +++ b/src/Bridge/Symfony/Routing/RouteNameResolverInterface.php @@ -32,5 +32,5 @@ interface RouteNameResolverInterface * * @return string */ - public function getRouteName(string $resourceClass, $operationType): string; + public function getRouteName(string $resourceClass, $operationType /**, array $context = [] **/): string; } diff --git a/src/Hydra/Serializer/CollectionNormalizer.php b/src/Hydra/Serializer/CollectionNormalizer.php index 67bf29171b6..36f21b76d13 100644 --- a/src/Hydra/Serializer/CollectionNormalizer.php +++ b/src/Hydra/Serializer/CollectionNormalizer.php @@ -76,7 +76,7 @@ public function normalize($object, $format = null, array $context = []) $context = $this->initContext($resourceClass, $context); if (isset($context['operation_type']) && $context['operation_type'] === OperationType::SUBRESOURCE) { - $data['@id'] = $this->iriConverter->getSubresourceIriFromResourceClass($resourceClass, $context['subresource_identifiers']); + $data['@id'] = $this->iriConverter->getSubresourceIriFromResourceClass($resourceClass, $context); } else { $data['@id'] = $this->iriConverter->getIriFromResourceClass($resourceClass); } diff --git a/tests/Bridge/Symfony/Routing/CachedRouteNameResolverTest.php b/tests/Bridge/Symfony/Routing/CachedRouteNameResolverTest.php index 0024a922c33..47c118ef67e 100644 --- a/tests/Bridge/Symfony/Routing/CachedRouteNameResolverTest.php +++ b/tests/Bridge/Symfony/Routing/CachedRouteNameResolverTest.php @@ -39,6 +39,7 @@ public function testConstruct() /** * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException * @expectedExceptionMessage No item route associated with the type "AppBundle\Entity\User". + * @group legacy */ public function testGetRouteNameForItemRouteWithNoMatchingRoute() { @@ -50,7 +51,7 @@ public function testGetRouteNameForItemRouteWithNoMatchingRoute() $cacheItemPoolProphecy->save($cacheItemProphecy)->shouldNotBeCalled(); $decoratedProphecy = $this->prophesize(RouteNameResolverInterface::class); - $decoratedProphecy->getRouteName('AppBundle\Entity\User', false) + $decoratedProphecy->getRouteName('AppBundle\Entity\User', false, []) ->willThrow(new InvalidArgumentException('No item route associated with the type "AppBundle\Entity\User".')) ->shouldBeCalled(); @@ -58,6 +59,9 @@ public function testGetRouteNameForItemRouteWithNoMatchingRoute() $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', false); } + /** + * @group legacy + */ public function testGetRouteNameForItemRouteOnCacheMiss() { $cacheItemProphecy = $this->prophesize(CacheItemInterface::class); @@ -69,7 +73,7 @@ public function testGetRouteNameForItemRouteOnCacheMiss() $cacheItemPoolProphecy->save($cacheItemProphecy)->willReturn(true)->shouldBeCalled(); $decoratedProphecy = $this->prophesize(RouteNameResolverInterface::class); - $decoratedProphecy->getRouteName('AppBundle\Entity\User', false)->willReturn('some_item_route')->shouldBeCalled(); + $decoratedProphecy->getRouteName('AppBundle\Entity\User', false, [])->willReturn('some_item_route')->shouldBeCalled(); $cachedRouteNameResolver = new CachedRouteNameResolver($cacheItemPoolProphecy->reveal(), $decoratedProphecy->reveal()); $actual = $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', false); @@ -99,6 +103,7 @@ public function testGetRouteNameForItemRouteOnCacheHit() /** * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException * @expectedExceptionMessage No collection route associated with the type "AppBundle\Entity\User". + * @group legacy */ public function testGetRouteNameForCollectionRouteWithNoMatchingRoute() { @@ -110,7 +115,7 @@ public function testGetRouteNameForCollectionRouteWithNoMatchingRoute() $cacheItemPoolProphecy->save($cacheItemProphecy)->shouldNotBeCalled(); $decoratedProphecy = $this->prophesize(RouteNameResolverInterface::class); - $decoratedProphecy->getRouteName('AppBundle\Entity\User', true) + $decoratedProphecy->getRouteName('AppBundle\Entity\User', true, []) ->willThrow(new InvalidArgumentException('No collection route associated with the type "AppBundle\Entity\User".')) ->shouldBeCalled(); @@ -118,6 +123,9 @@ public function testGetRouteNameForCollectionRouteWithNoMatchingRoute() $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', true); } + /** + * @group legacy + */ public function testGetRouteNameForCollectionRouteOnCacheMiss() { $cacheItemProphecy = $this->prophesize(CacheItemInterface::class); @@ -129,7 +137,7 @@ public function testGetRouteNameForCollectionRouteOnCacheMiss() $cacheItemPoolProphecy->save($cacheItemProphecy)->willReturn(true)->shouldBeCalled(); $decoratedProphecy = $this->prophesize(RouteNameResolverInterface::class); - $decoratedProphecy->getRouteName('AppBundle\Entity\User', true)->willReturn('some_collection_route')->shouldBeCalled(); + $decoratedProphecy->getRouteName('AppBundle\Entity\User', true, [])->willReturn('some_collection_route')->shouldBeCalled(); $cachedRouteNameResolver = new CachedRouteNameResolver($cacheItemPoolProphecy->reveal(), $decoratedProphecy->reveal()); $actual = $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', true); diff --git a/tests/Bridge/Symfony/Routing/IriConverterTest.php b/tests/Bridge/Symfony/Routing/IriConverterTest.php index de84bf1179d..a983a2b6c21 100644 --- a/tests/Bridge/Symfony/Routing/IriConverterTest.php +++ b/tests/Bridge/Symfony/Routing/IriConverterTest.php @@ -21,6 +21,8 @@ use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy; +use Prophecy\Argument; use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Component\Routing\RouterInterface; @@ -206,7 +208,7 @@ public function testGetSubresourceIriFromResourceClass() $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $routeNameResolverProphecy->getRouteName(Dummy::class, OperationType::SUBRESOURCE)->willReturn('api_dummies_related_dummies_get_subresource'); + $routeNameResolverProphecy->getRouteName(Dummy::class, OperationType::SUBRESOURCE, Argument::type('array'))->willReturn('api_dummies_related_dummies_get_subresource'); $routerProphecy = $this->prophesize(RouterInterface::class); $routerProphecy->generate('api_dummies_related_dummies_get_subresource', ['id' => 1], UrlGeneratorInterface::ABS_PATH)->willReturn('/dummies/1/related_dummies'); @@ -219,7 +221,7 @@ public function testGetSubresourceIriFromResourceClass() $routerProphecy->reveal() ); - $this->assertEquals($converter->getSubresourceIriFromResourceClass(Dummy::class, ['id' => 1]), '/dummies/1/related_dummies'); + $this->assertEquals($converter->getSubresourceIriFromResourceClass(Dummy::class, ['subresource_identifiers' => ['id' => 1], 'subresource_resources' => [RelatedDummy::class => 1]]), '/dummies/1/related_dummies'); } /** @@ -235,7 +237,7 @@ public function testNotAbleToGenerateGetSubresourceIriFromResourceClass() $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $routeNameResolverProphecy->getRouteName(Dummy::class, OperationType::SUBRESOURCE)->willReturn('dummies'); + $routeNameResolverProphecy->getRouteName(Dummy::class, OperationType::SUBRESOURCE, Argument::type('array'))->willReturn('dummies'); $routerProphecy = $this->prophesize(RouterInterface::class); $routerProphecy->generate('dummies', ['id' => 1], UrlGeneratorInterface::ABS_PATH)->willThrow(new RouteNotFoundException()); @@ -248,7 +250,7 @@ public function testNotAbleToGenerateGetSubresourceIriFromResourceClass() $routerProphecy->reveal() ); - $converter->getSubresourceIriFromResourceClass(Dummy::class, ['id' => 1]); + $converter->getSubresourceIriFromResourceClass(Dummy::class, ['subresource_identifiers' => ['id' => 1], 'subresource_resources' => [RelatedDummy::class => 1]]); } public function testGetItemIriFromResourceClass() diff --git a/tests/Bridge/Symfony/Routing/RouteNameResolverTest.php b/tests/Bridge/Symfony/Routing/RouteNameResolverTest.php index da377772294..30c914a96f7 100644 --- a/tests/Bridge/Symfony/Routing/RouteNameResolverTest.php +++ b/tests/Bridge/Symfony/Routing/RouteNameResolverTest.php @@ -118,9 +118,15 @@ public function testGetRouteNameForCollectionRoute() public function testGetRouteNameForSubresourceRoute() { $routeCollection = new RouteCollection(); - $routeCollection->add('some_subresource_route', new Route('/some/item/path/{id}', [ + $routeCollection->add('a_some_subresource_route', new Route('/a/some/item/path/{id}', [ + '_api_resource_class' => 'AppBundle\Entity\User', + '_api_subresource_operation_name' => 'some_other_item_op', + '_api_subresource_context' => ['identifiers' => [[1, 'bar']]], + ])); + $routeCollection->add('b_some_subresource_route', new Route('/b/some/item/path/{id}', [ '_api_resource_class' => 'AppBundle\Entity\User', '_api_subresource_operation_name' => 'some_item_op', + '_api_subresource_context' => ['identifiers' => [[1, 'foo']]], ])); $routeCollection->add('some_collection_route', new Route('/some/collection/path', [ '_api_resource_class' => 'AppBundle\Entity\User', @@ -131,8 +137,8 @@ public function testGetRouteNameForSubresourceRoute() $routerProphecy->getRouteCollection()->willReturn($routeCollection); $routeNameResolver = new RouteNameResolver($routerProphecy->reveal()); - $actual = $routeNameResolver->getRouteName('AppBundle\Entity\User', OperationType::SUBRESOURCE); + $actual = $routeNameResolver->getRouteName('AppBundle\Entity\User', OperationType::SUBRESOURCE, ['subresource_resources' => ['foo' => 1]]); - $this->assertSame('some_subresource_route', $actual); + $this->assertSame('b_some_subresource_route', $actual); } } diff --git a/tests/Fixtures/TestBundle/Entity/DummyAggregateOffer.php b/tests/Fixtures/TestBundle/Entity/DummyAggregateOffer.php new file mode 100644 index 00000000000..a9b2d125944 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/DummyAggregateOffer.php @@ -0,0 +1,120 @@ + + * + * 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\Core\Tests\Fixtures\TestBundle\Entity; + +use ApiPlatform\Core\Annotation\ApiProperty; +use ApiPlatform\Core\Annotation\ApiResource; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\ORM\Mapping as ORM; + +/** + * Dummy Aggregate Offer. + * https://github.com/api-platform/core/issues/1107. + * + * @author Antoine Bluchet + * + * @ApiResource + * @ORM\Entity + */ +class DummyAggregateOffer +{ + /** + * @var int The id + * + * @ORM\Column(type="integer") + * @ORM\Id + * @ORM\GeneratedValue(strategy="AUTO") + */ + private $id; + + /** + * @var ArrayCollection + * + * @ApiProperty(subresource=true) + * @ORM\OneToMany(targetEntity="DummyOffer", mappedBy="id", cascade={"persist"}) + */ + private $offers; + + /** + * @var int The dummy aggregate offer value + * + * @ORM\Column(type="integer") + */ + private $value; + + public function __construct() + { + $this->offers = new ArrayCollection(); + } + + /** + * Get offers. + * + * @return offers + */ + public function getOffers(): ArrayCollection + { + return $this->offers; + } + + /** + * Set offers. + * + * @param offers the value to set + */ + public function setOffers($offers) + { + $this->offers = $offers; + } + + /** + * Add offer. + * + * @param offer the value to add + */ + public function addOffer(DummyOffer $offer) + { + $this->offers->add($offer); + } + + /** + * Get id. + * + * @return id + */ + public function getId(): int + { + return $this->id; + } + + /** + * Get value. + * + * @return value + */ + public function getValue(): int + { + return $this->value; + } + + /** + * Set value. + * + * @param value the value to set + */ + public function setValue(int $value) + { + $this->value = $value; + } +} diff --git a/tests/Fixtures/TestBundle/Entity/DummyOffer.php b/tests/Fixtures/TestBundle/Entity/DummyOffer.php new file mode 100644 index 00000000000..adbfe4d6288 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/DummyOffer.php @@ -0,0 +1,75 @@ + + * + * 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\Core\Tests\Fixtures\TestBundle\Entity; + +use ApiPlatform\Core\Annotation\ApiResource; +use Doctrine\ORM\Mapping as ORM; + +/** + * Dummy Offer. + * https://github.com/api-platform/core/issues/1107. + * + * @author Antoine Bluchet + * + * @ApiResource + * @ORM\Entity + */ +class DummyOffer +{ + /** + * @var int The id + * + * @ORM\Column(type="integer") + * @ORM\Id + * @ORM\GeneratedValue(strategy="AUTO") + */ + private $id; + + /** + * @var int The dummy aggregate offer value + * + * @ORM\Column(type="integer") + */ + private $value; + + /** + * Get id. + * + * @return id + */ + public function getId(): int + { + return $this->id; + } + + /** + * Get value. + * + * @return value + */ + public function getValue(): int + { + return $this->value; + } + + /** + * Set value. + * + * @param value the value to set + */ + public function setValue(int $value) + { + $this->value = $value; + } +} diff --git a/tests/Fixtures/TestBundle/Entity/DummyProduct.php b/tests/Fixtures/TestBundle/Entity/DummyProduct.php new file mode 100644 index 00000000000..ecc612bc2fc --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/DummyProduct.php @@ -0,0 +1,120 @@ + + * + * 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\Core\Tests\Fixtures\TestBundle\Entity; + +use ApiPlatform\Core\Annotation\ApiProperty; +use ApiPlatform\Core\Annotation\ApiResource; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\ORM\Mapping as ORM; + +/** + * Dummy Product. + * https://github.com/api-platform/core/issues/1107. + * + * @author Antoine Bluchet + * + * @ApiResource + * @ORM\Entity + */ +class DummyProduct +{ + /** + * @var int The id + * + * @ORM\Column(type="integer") + * @ORM\Id + * @ORM\GeneratedValue(strategy="AUTO") + */ + private $id; + + /** + * @var ArrayCollection + * + * @ApiProperty(subresource=true) + * @ORM\OneToMany(targetEntity="DummyAggregateOffer", mappedBy="id", cascade={"persist"}) + */ + private $offers; + + /** + * @var string The tour name + * + * @ORM\Column + */ + private $name; + + public function __construct() + { + $this->offers = new ArrayCollection(); + } + + /** + * Get offers. + * + * @return offers + */ + public function getOffers(): ArrayCollection + { + return $this->offers; + } + + /** + * Set offers. + * + * @param offers the value to set + */ + public function setOffers($offers) + { + $this->offers = $offers; + } + + /** + * Add offer. + * + * @param offer the value to add + */ + public function addOffer(DummyAggregateOffer $offer) + { + $this->offers->add($offer); + } + + /** + * Get id. + * + * @return id + */ + public function getId(): int + { + return $this->id; + } + + /** + * Get name. + * + * @return name + */ + public function getName(): string + { + return $this->name; + } + + /** + * Set name. + * + * @param name the value to set + */ + public function setName($name) + { + $this->name = $name; + } +}