Skip to content

Commit

Permalink
Merge pull request #1275 from soyuka/fix/subresource-recursive
Browse files Browse the repository at this point in the history
fix #1272 subresource recursive + 1 more test
  • Loading branch information
soyuka committed Jul 21, 2017
2 parents 473fc04 + f345681 commit 1a82364
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 111 deletions.
7 changes: 7 additions & 0 deletions features/bootstrap/FeatureContext.php
Expand Up @@ -664,6 +664,13 @@ public function createProductWithOffers()
$product->setName('Dummy product');
$product->addOffer($aggregate);

$relatedProduct = new DummyProduct();
$relatedProduct->setName('Dummy related product');
$relatedProduct->setParent($product);

$product->addRelatedProduct($relatedProduct);

$this->manager->persist($relatedProduct);
$this->manager->persist($product);
$this->manager->flush();
}
Expand Down
36 changes: 30 additions & 6 deletions features/main/subresource.feature
Expand Up @@ -26,7 +26,6 @@ Feature: Subresource support

Scenario: Get subresource one to one relation
When I send a "GET" request to "/questions/1/answer/related_questions"
And print last JSON response
And the response status code should be 200
And the response should be in JSON
And the JSON should be equal to:
Expand Down Expand Up @@ -253,29 +252,29 @@ 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"
When I send a "GET" request to "/dummy_products/2/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",
"@id": "/dummy_products/2/offers/1/offers",
"@type": "hydra:Collection",
"hydra:member": [
{
"@id": "/dummy_offers/1",
"@type": "DummyOffer",
"id": 1,
"value": 2
"value": 2,
"aggregate": "/dummy_aggregate_offers/1"
}
],
"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
Expand All @@ -292,9 +291,34 @@ Feature: Subresource support
"@id": "/dummy_offers/1",
"@type": "DummyOffer",
"id": 1,
"value": 2
"value": 2,
"aggregate": "/dummy_aggregate_offers/1"
}
],
"hydra:totalItems": 1
}
"""

@dropSchema
Scenario: test
When I send a "GET" request to "/dummy_products/2"
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/DummyProduct",
"@id": "/dummy_products/2",
"@type": "DummyProduct",
"offers": [
"/dummy_aggregate_offers/1"
],
"id": 2,
"name": "Dummy product",
"relatedProducts": [
"/dummy_products/1"
],
"parent": null
}
"""
25 changes: 13 additions & 12 deletions src/Operation/Factory/SubresourceOperationFactory.php
Expand Up @@ -59,7 +59,7 @@ public function create(string $resourceClass): array
* @param string $rootResourceClass null on the first iteration, it then keeps track of the origin resource class
* @param array $parentOperation the previous call operation
*/
private function computeSubresourceOperations(string $resourceClass, array &$tree, string $rootResourceClass = null, array $parentOperation = null)
private function computeSubresourceOperations(string $resourceClass, array &$tree, string $rootResourceClass = null, array $parentOperation = null, $parentVisiting = null)
{
if (null === $rootResourceClass) {
$rootResourceClass = $resourceClass;
Expand All @@ -79,18 +79,13 @@ private function computeSubresourceOperations(string $resourceClass, array &$tre
if (null === $parentOperation) {
$visiting = "$rootResourceClass-$property-$subresourceClass";
} else {
$prefix = '';
$visiting = "{$parentOperation['property']}-{$parentOperation['resource_class']}-$property-$subresourceClass";
$suffix = "$property-$subresourceClass";

foreach ($parentOperation['identifiers'] as $key => list($param, $class)) {
$prefix .= 0 === $key ? $class : "-$param-$class";
}

if (false !== strpos($prefix, $visiting)) {
if (false !== strpos($parentVisiting, $suffix)) {
continue;
}

$visiting = $prefix.'-'.$visiting;
$visiting = "$parentVisiting-$suffix";
}

$operationName = 'get';
Expand Down Expand Up @@ -121,13 +116,18 @@ private function computeSubresourceOperations(string $resourceClass, array &$tre
self::FORMAT_SUFFIX
);

$operation['shortNames'][] = $rootShortname;
if (!in_array($rootShortname, $operation['shortNames'], true)) {
$operation['shortNames'][] = $rootShortname;
}
} else {
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
$operation['identifiers'] = $parentOperation['identifiers'];
$operation['identifiers'][] = [$parentOperation['property'], $resourceClass, $parentOperation['collection']];
$operation['route_name'] = str_replace('get'.self::SUBRESOURCE_SUFFIX, RouteNameGenerator::inflector($property, $operation['collection']).'_get'.self::SUBRESOURCE_SUFFIX, $parentOperation['route_name']);
$operation['shortNames'][] = $resourceMetadata->getShortName();

if (!in_array($resourceMetadata->getShortName(), $operation['shortNames'], true)) {
$operation['shortNames'][] = $resourceMetadata->getShortName();
}

$operation['path'] = str_replace(self::FORMAT_SUFFIX, '', $parentOperation['path']);
if ($parentOperation['collection']) {
Expand All @@ -138,7 +138,8 @@ private function computeSubresourceOperations(string $resourceClass, array &$tre
}

$tree[$visiting] = $operation;
$this->computeSubresourceOperations($subresourceClass, $tree, $rootResourceClass, $operation);

$this->computeSubresourceOperations($subresourceClass, $tree, $rootResourceClass, $operation, $visiting);
}
}
}
15 changes: 12 additions & 3 deletions src/Swagger/Serializer/DocumentationNormalizer.php
Expand Up @@ -129,13 +129,22 @@ public function normalize($object, $format = null, array $context = [])
'404' => ['description' => 'Resource not found'],
];

if ($parameters = $this->getFiltersParameters($resourceClass, $operationName, $resourceMetadata, $definitions, $serializerContext)) {
$pathOperation['parameters'] = $parameters;
}
// Avoid duplicates parameters when there is a filter on a subresource identifier
$parametersMemory = [];
$pathOperation['parameters'] = [];

foreach ($subresourceOperation['identifiers'] as list($identifier, , $hasIdentifier)) {
if (true === $hasIdentifier) {
$pathOperation['parameters'][] = ['name' => $identifier, 'in' => 'path', 'required' => true, 'type' => 'string'];
$parametersMemory[] = $identifier;
}
}

if ($parameters = $this->getFiltersParameters($resourceClass, $operationName, $resourceMetadata, $definitions, $serializerContext)) {
foreach ($parameters as $parameter) {
if (!in_array($parameter['name'], $parametersMemory, true)) {
$pathOperation['parameters'][] = $parameter;
}
}
}

Expand Down
27 changes: 22 additions & 5 deletions tests/Fixtures/TestBundle/Entity/DummyAggregateOffer.php
Expand Up @@ -16,7 +16,6 @@
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
Expand All @@ -43,10 +42,17 @@ class DummyAggregateOffer
* @var ArrayCollection
*
* @ApiSubresource
* @ORM\OneToMany(targetEntity="DummyOffer", mappedBy="id", cascade={"persist"})
* @ORM\OneToMany(targetEntity="DummyOffer", mappedBy="aggregate", cascade={"persist"})
*/
private $offers;

/**
* @var DummyProduct The dummy product
*
* @ORM\ManyToOne(targetEntity="DummyProduct", inversedBy="offers")
*/
private $product;

/**
* @var int The dummy aggregate offer value
*
Expand All @@ -59,7 +65,7 @@ public function __construct()
$this->offers = new ArrayCollection();
}

public function getOffers(): Collection
public function getOffers()
{
return $this->offers;
}
Expand All @@ -72,20 +78,31 @@ public function setOffers($offers)
public function addOffer(DummyOffer $offer)
{
$this->offers->add($offer);
$offer->setAggregate($this);
}

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

public function getValue(): int
public function getValue()
{
return $this->value;
}

public function setValue(int $value)
public function setValue($value)
{
$this->value = $value;
}

public function getProduct()
{
return $this->product;
}

public function setProduct(DummyProduct $product)
{
$this->product = $product;
}
}
17 changes: 17 additions & 0 deletions tests/Fixtures/TestBundle/Entity/DummyOffer.php
Expand Up @@ -43,6 +43,13 @@ class DummyOffer
*/
private $value;

/**
* @var DummyAggregateOffer The dummy aggregate offer value
*
* @ORM\ManyToOne(targetEntity="DummyAggregateOffer", inversedBy="offers")
*/
private $aggregate;

public function getId()
{
return $this->id;
Expand All @@ -57,4 +64,14 @@ public function setValue(int $value)
{
$this->value = $value;
}

public function getAggregate()
{
return $this->aggregate;
}

public function setAggregate(DummyAggregateOffer $aggregate)
{
$this->aggregate = $aggregate;
}
}
43 changes: 42 additions & 1 deletion tests/Fixtures/TestBundle/Entity/DummyProduct.php
Expand Up @@ -43,7 +43,7 @@ class DummyProduct
* @var Collection
*
* @ApiSubresource
* @ORM\OneToMany(targetEntity="DummyAggregateOffer", mappedBy="id", cascade={"persist"})
* @ORM\OneToMany(targetEntity="DummyAggregateOffer", mappedBy="product", cascade={"persist"})
*/
private $offers;

Expand All @@ -54,9 +54,23 @@ class DummyProduct
*/
private $name;

/**
* @var Collection
*
* @ApiSubresource
* @ORM\OneToMany(targetEntity="DummyProduct", mappedBy="parent")
*/
private $relatedProducts;

/**
* @ORM\ManyToOne(targetEntity="DummyProduct", inversedBy="relatedProducts")
*/
private $parent;

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

public function getOffers(): Collection
Expand All @@ -72,6 +86,7 @@ public function setOffers($offers)
public function addOffer(DummyAggregateOffer $offer)
{
$this->offers->add($offer);
$offer->setProduct($this);
}

public function getId()
Expand All @@ -88,4 +103,30 @@ public function setName($name)
{
$this->name = $name;
}

public function getRelatedProducts(): Collection
{
return $this->relatedProducts;
}

public function setRelatedProducts(Collection $relatedProducts)
{
$this->relatedProducts = $relatedProducts;
}

public function addRelatedProduct(DummyProduct $relatedProduct)
{
$this->relatedProducts->add($relatedProduct);
$relatedProduct->setParent($this);
}

public function getParent()
{
return $this->parent;
}

public function setParent(DummyProduct $product)
{
$this->parent = $product;
}
}

0 comments on commit 1a82364

Please sign in to comment.