Skip to content

Commit

Permalink
fix: upgrade command remove ApiSubresource attribute (#5049)
Browse files Browse the repository at this point in the history
Fixes #5038
  • Loading branch information
davy-beauzil authored Oct 24, 2022
1 parent 9c19fa1 commit 1b64ebf
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 64 deletions.
19 changes: 13 additions & 6 deletions src/Core/Upgrade/UpgradeApiResourceVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,8 @@ public function enterNode(Node $node)
}

if ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Interface_) {
if ($this->isAnnotation) {
$this->removeAnnotation($node);
} else {
$this->removeAttribute($node);
}
$this->removeAnnotation($node);
$this->removeAttribute($node);

$arguments = [];
$operations = null === $this->resourceAnnotation->itemOperations && null === $this->resourceAnnotation->collectionOperations ? null : array_merge(
Expand Down Expand Up @@ -361,13 +358,23 @@ private function removeAttribute(Node\Stmt\Class_|Node\Stmt\Interface_ $node)
}
}
}
foreach ($node->stmts as $k => $stmts) {
foreach ($stmts->attrGroups as $i => $attrGroups) {
foreach ($attrGroups->attrs as $j => $attrs) {
if (str_ends_with(implode('\\', $attrs->name->parts), 'ApiSubresource')) {
unset($node->stmts[$k]->attrGroups[$i]);
break;
}
}
}
}
}

private function removeAnnotation(Node\Stmt\Class_|Node\Stmt\Interface_ $node)
{
$comment = $node->getDocComment();

if (preg_match('/@ApiResource/', $comment->getText())) {
if ($comment && preg_match('/@ApiResource/', $comment->getText())) {
$node->setDocComment($this->removeAnnotationByTag($comment, 'ApiResource'));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
use ApiPlatform\Core\Upgrade\SubresourceTransformer;
use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\ResourceNameCollection;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyToUpgradeWithOnlyAnnotation;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyToUpgradeWithOnlyAttribute;
use Doctrine\Common\Annotations\AnnotationReader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
Expand All @@ -49,75 +50,117 @@ private function getCommandTester(ResourceNameCollectionFactoryInterface $resour

/**
* @requires PHP 8.1
*
* @dataProvider debugResourceProvider
*/
public function testDebugResource()
public function testDebugResource(string $entityClass, array $subresourceOperationFactoryReturn, array $expectedStrings)
{
$resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class);
$resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([RelatedDummy::class]));
$resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([$entityClass]));
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
$resourceMetadataFactoryProphecy->create(RelatedDummy::class)->willReturn(new ResourceMetadata());
$resourceMetadataFactoryProphecy->create($entityClass)->willReturn(new ResourceMetadata());
$subresourceOperationFactoryProphecy = $this->prophesize(SubresourceOperationFactoryInterface::class);
$subresourceOperationFactoryProphecy->create(RelatedDummy::class)->willReturn([[
'property' => 'id',
'collection' => false,
'resource_class' => RelatedDummy::class,
'shortNames' => [
'RelatedDummy',
],
'legacy_filters' => [
'related_dummy.friends',
'related_dummy.complex_sub_query',
],
'legacy_normalization_context' => [
'groups' => [
'friends',
],
],
'legacy_type' => 'https://schema.org/Product',
'identifiers' => [
'id' => [
RelatedDummy::class,
'id',
true,
],
],
'operation_name' => 'id_get_subresource',
'route_name' => 'api_related_dummies_id_get_subresource',
'path' => '/related_dummies/{id}/id.{_format}',
]]);
$subresourceOperationFactoryProphecy->create($entityClass)->willReturn($subresourceOperationFactoryReturn);

$commandTester = $this->getCommandTester($resourceNameCollectionFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), $subresourceOperationFactoryProphecy->reveal());
$commandTester->execute([]);

$expectedStrings = [
'-use ApiPlatform\\Core\\Annotation\\ApiSubresource',
'-use ApiPlatform\\Core\\Annotation\\ApiProperty',
'-use ApiPlatform\\Core\\Annotation\\ApiResource',
'-use ApiPlatform\\Core\\Annotation\\ApiFilter',
'-use ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Filter\\SearchFilter;',
'-use ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Filter\\ExistsFilter;',
'-use ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Filter\\DateFilter;',
'+use ApiPlatform\\Metadata\\ApiProperty',
'+use ApiPlatform\\Metadata\\ApiResource',
'+use ApiPlatform\\Metadata\\ApiFilter',
'+use ApiPlatform\\Doctrine\\Orm\\Filter\\SearchFilter',
'+use ApiPlatform\\Doctrine\\Orm\\Filter\\ExistsFilter',
'+use ApiPlatform\\Doctrine\\Orm\\Filter\\DateFilter',
'+use ApiPlatform\\Metadata\\Get',
"+#[ApiResource(graphQlOperations: [new Query(name: 'item_query'), new Mutation(name: 'update', normalizationContext: ['groups' => ['chicago', 'fakemanytomany']], denormalizationContext: ['groups' => ['friends']])], types: ['https://schema.org/Product'], normalizationContext: ['groups' => ['friends']], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'])]",
"#[ApiResource(uriTemplate: '/related_dummies/{id}/id.{_format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]",
"+#[ApiFilter(filterClass: SearchFilter::class, properties: ['id'])]",
'+ #[ApiFilter(filterClass: SearchFilter::class)]',
'+ #[ApiFilter(filterClass: ExistsFilter::class)]',
'+ #[ApiFilter(filterClass: DateFilter::class)]',
'+ #[ApiProperty(writable: false)]',
"+ #[ApiProperty(iris: ['RelatedDummy.name'])]",
"+ #[ApiProperty(deprecationReason: 'This property is deprecated for upgrade test')]",
];

$display = $commandTester->getDisplay();
foreach ($expectedStrings as $expectedString) {
$this->assertStringContainsString($expectedString, $display);
}
}

public function debugResourceProvider(): array
{
$entityClasses = [
'only_annotation' => DummyToUpgradeWithOnlyAnnotation::class,
'only_attribute' => DummyToUpgradeWithOnlyAttribute::class,
];

return array_map(function ($key, $entityClass) {
$expectedStrings = [
'+#[ApiResource]',
'-use ApiPlatform\\Core\\Annotation\\ApiSubresource',
'-use ApiPlatform\\Core\\Annotation\\ApiProperty',
'-use ApiPlatform\\Core\\Annotation\\ApiResource',
'+use ApiPlatform\\Metadata\\ApiProperty',
'+use ApiPlatform\\Metadata\\ApiResource',
'+use ApiPlatform\\Metadata\\ApiFilter',
'+use ApiPlatform\\Metadata\\Get',
sprintf("#[ApiResource(uriTemplate: '/%s/{id}/name.{_format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]", $key),
];

if (DummyToUpgradeWithOnlyAnnotation::class === $entityClass) {
array_push($expectedStrings,
'+use ApiPlatform\\Doctrine\\Orm\\Filter\\SearchFilter',
'+use ApiPlatform\\Doctrine\\Orm\\Filter\\ExistsFilter',
'+use ApiPlatform\\Doctrine\\Orm\\Filter\\DateFilter',
'-use ApiPlatform\\Core\\Annotation\\ApiFilter',
'-use ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Filter\\SearchFilter;',
'-use ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Filter\\ExistsFilter;',
'-use ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Filter\\DateFilter;',
'- * @ApiResource',
'- * @ApiFilter(SearchFilter::class, properties={"id"})',
"+#[ApiFilter(filterClass: SearchFilter::class, properties: ['id'])]",
'- * @ApiProperty(writable=false)',
'+ #[ApiProperty(writable: false)]',
'- * @ApiSubresource',
'- * @ApiFilter(DateFilter::class)',
'- * @ApiProperty(iri="DummyToUpgradeWithOnlyAnnotation.dummyToUpgradeProduct")',
"+ #[ApiProperty(iris: ['DummyToUpgradeWithOnlyAnnotation.dummyToUpgradeProduct'])]",
'- * @ApiFilter(SearchFilter::class)',
'- * @ApiFilter(ExistsFilter::class)',
'+ #[ApiFilter(filterClass: SearchFilter::class)]',
'+ #[ApiFilter(filterClass: ExistsFilter::class)]',
'+ #[ApiFilter(filterClass: DateFilter::class)]'
);
}

if (DummyToUpgradeWithOnlyAttribute::class === $entityClass) {
array_push($expectedStrings,
'-#[ApiResource()]',
"+#[ApiResource(uriTemplate: '/only_attribute/{id}/name.{_format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]",
'- #[ApiSubresource]',
"- #[ApiProperty(iri: 'DummyToUpgradeWithOnlyAttribute.dummyToUpgradeProduct')]",
"+ #[ApiProperty(iris: ['DummyToUpgradeWithOnlyAttribute.dummyToUpgradeProduct'])]"
);
}

return [
$entityClass,
[
[
'property' => 'id',
'collection' => false,
'resource_class' => $entityClass,
'shortNames' => [
substr($entityClass, (\strlen($entityClass) - strrpos($entityClass, '\\') - 1) * (-1)),
],
'legacy_filters' => [
'related_dummy.friends',
'related_dummy.complex_sub_query',
],
'legacy_normalization_context' => [
'groups' => [
'friends',
],
],
'legacy_type' => 'https://schema.org/Product',
'identifiers' => [
'id' => [
$entityClass,
'id',
true,
],
],
'operation_name' => 'name_get_subresource',
'route_name' => sprintf('api_%s_name_get_subresource', $key),
'path' => sprintf('/%s/{id}/name.{_format}', $key),
],
],
array_merge($expectedStrings),
];
}, array_keys($entityClasses), array_values($entityClasses));
}
}
49 changes: 49 additions & 0 deletions tests/Fixtures/TestBundle/Entity/DummyToUpgradeProduct.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*
* @ApiResource
*/
class DummyToUpgradeProduct
{
/**
* @var int
*
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;

/**
* @var Collection<int,DummyToUpgradeWithOnlyAnnotation>
*
* @ORM\OneToMany(mappedBy="dummyToUpgradeProduct", targetEntity=DummyToUpgradeWithOnlyAnnotation::class)
*/
private $dummysToUpgradeWithOnlyAnnotation;

/**
* @var Collection<int,DummyToUpgradeWithOnlyAttribute>
*
* @ORM\OneToMany(mappedBy="dummyToUpgradeProduct", targetEntity=DummyToUpgradeWithOnlyAttribute::class)
*/
private $dummysToUpgradeWithOnlyAttribute;
}
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 <dunglas@gmail.com>
*
* 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;

use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\ExistsFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

/**
* @ORM\Entity
*
* @ApiResource
*
* @ApiFilter(SearchFilter::class, properties={"id"})
*/
class DummyToUpgradeWithOnlyAnnotation
{
/**
* @var int
*
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Groups({"chicago", "friends"})
* @ApiProperty(writable=false)
* @ApiFilter(DateFilter::class)
*/
private $id;

/**
* @var DummyToUpgradeProduct
*
* @ORM\ManyToOne(targetEntity="DummyToUpgradeProduct", cascade={"persist"}, inversedBy="dummysToUpgradeWithOnlyAnnotation")
* @ORM\JoinColumn(nullable=false)
* @Groups({"barcelona", "chicago", "friends"})
*
* @ApiSubresource
*
* @ApiProperty(iri="DummyToUpgradeWithOnlyAnnotation.dummyToUpgradeProduct")
* @ApiFilter(SearchFilter::class)
* @ApiFilter(ExistsFilter::class)
*/
private $dummyToUpgradeProduct;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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;

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

/**
* @ORM\Entity
*/
#[ApiResource()]
class DummyToUpgradeWithOnlyAttribute
{
/**
* @var int
*
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
#[Groups(['chicago', 'friends'])]
#[ApiProperty(writable: false)]
private $id;

/**
* @var DummyToUpgradeProduct
*
* @ORM\ManyToOne(targetEntity="DummyToUpgradeProduct", inversedBy="dummysToUpgradeWithOnlyAttribute")
* @ORM\JoinColumn(nullable=false)
*/
#[Groups(['barcelona', 'chicago', 'friends'])]
#[ApiSubresource]
#[ApiProperty(iri: 'DummyToUpgradeWithOnlyAttribute.dummyToUpgradeProduct')]
private $dummyToUpgradeProduct;
}

0 comments on commit 1b64ebf

Please sign in to comment.