Skip to content

Commit 840cb48

Browse files
committed
fix documentation & tests
1 parent 6a8cf7d commit 840cb48

File tree

4 files changed

+55
-26
lines changed

4 files changed

+55
-26
lines changed

api/src/Entity/ContentNode.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
denormalizationContext: ['groups' => ['write']],
3939
normalizationContext: ['groups' => ['read']],
4040
)]
41-
#[ApiFilter(SearchFilter::class, properties: ['parent'])]
41+
#[ApiFilter(SearchFilter::class, properties: ['parent', 'contentType'])]
4242
abstract class ContentNode extends BaseEntity implements BelongsToCampInterface {
4343
/**
4444
* @ORM\OneToOne(targetEntity="AbstractContentNodeOwner", mappedBy="rootContentNode", cascade={"persist", "remove"})

api/src/Entity/ContentType.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ class ContentType extends BaseEntity {
4646
* @ORM\Column(type="text")
4747
*/
4848
#[ApiProperty(writable: false)]
49-
#[Groups(['read'])]
5049
public ?string $entityClass = null;
5150

5251
/**
@@ -59,10 +58,17 @@ class ContentType extends BaseEntity {
5958
public ?array $jsonConfig = [];
6059

6160
/**
62-
* API endpoint path for creating new entities of type entityClass.
61+
* API endpoint link for creating new entities of type entityClass.
6362
*/
6463
#[Groups(['read'])]
65-
public function getEntityPath(): string {
66-
return ''; // empty here; actual content of this property is filled/decorated in ContentTypeNormalizer
64+
#[ApiProperty(
65+
example: '/content_node/column_layouts?contentType=%2Fcontent_types%2F1a2b3c4d',
66+
openapiContext: [
67+
'type' => 'array',
68+
'format' => 'iri-reference',
69+
]
70+
)]
71+
public function getContentNodes(): array {
72+
return []; // empty here; actual content is filled/decorated in ContentTypeNormalizer
6773
}
6874
}

api/src/Serializer/Normalizer/ContentTypeNormalizer.php

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@
22

33
namespace App\Serializer\Normalizer;
44

5+
use ApiPlatform\Core\Api\IriConverterInterface;
56
use App\Entity\ContentType;
67
use App\Metadata\Resource\Factory\UriTemplateFactory;
8+
use Rize\UriTemplate;
79
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
810
use Symfony\Component\Serializer\SerializerAwareInterface;
911
use Symfony\Component\Serializer\SerializerInterface;
1012

1113
/**
12-
* ...
14+
* Adds API link to contentNodes for ContentType based on the defined 'entityClass'.
1315
*/
1416
class ContentTypeNormalizer implements NormalizerInterface, SerializerAwareInterface {
1517
public function __construct(
1618
private NormalizerInterface $decorated,
19+
private UriTemplate $uriTemplate,
1720
private UriTemplateFactory $uriTemplateFactory,
21+
private IriConverterInterface $iriConverter,
1822
) {
1923
}
2024

@@ -25,12 +29,16 @@ public function supportsNormalization($data, $format = null) {
2529
public function normalize($object, $format = null, array $context = []) {
2630
$data = $this->decorated->normalize($object, $format, $context);
2731

28-
if ($object instanceof ContentType && isset($data['entityClass'])) {
29-
[$uriTemplate, $templated] = $this->uriTemplateFactory->createFromResourceClass($data['entityClass']);
30-
$data['_links']['contentNodes']['href'] = $uriTemplate;
31-
$data['_links']['contentNodes']['templated'] = $templated;
32+
if ($object instanceof ContentType && isset($object->entityClass)) {
33+
// get uri for the respective ContentNode entity and add ContentType as query parameter
34+
[$uriTemplate, $templated] = $this->uriTemplateFactory->createFromResourceClass($object->entityClass);
35+
$uri = $this->uriTemplate->expand($uriTemplate, ['contentType' => $this->iriConverter->getIriFromItem($object)]);
3236

33-
unset($data['entityClass']);
37+
// add uri as HAL link
38+
$data['_links']['contentNodes']['href'] = $uri;
39+
40+
// unset the property itself (property definition was only needed to ensure proper API documentation)
41+
unset($data['contentNodes']);
3442
}
3543

3644
return $data;

api/tests/Serializer/Normalizer/ContentTypeNormalizerTest.php

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
namespace App\Tests\Serializer\Normalizer;
44

5+
use ApiPlatform\Core\Api\IriConverterInterface;
56
use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameResolverInterface;
67
use App\Entity\ContentType;
8+
use App\Metadata\Resource\Factory\UriTemplateFactory;
79
use App\Serializer\Normalizer\ContentTypeNormalizer;
810
use PHPUnit\Framework\MockObject\MockObject;
911
use PHPUnit\Framework\TestCase;
12+
use Rize\UriTemplate;
1013
use Symfony\Component\Routing\RouterInterface;
1114
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
1215
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
@@ -21,16 +24,22 @@ class ContentTypeNormalizerTest extends TestCase {
2124
private MockObject|NormalizerInterface $decoratedMock;
2225
private MockObject|RouteNameResolverInterface $routeNameResolver;
2326
private MockObject|RouterInterface $routerMock;
27+
private MockObject|IriConverterInterface $iriConverter;
28+
private MockObject|UriTemplate $uriTemplate;
29+
private MockObject|UriTemplateFactory $uriTemplateFactory;
2430

2531
protected function setUp(): void {
2632
$this->decoratedMock = $this->createMock(ContextAwareNormalizerInterface::class);
27-
$this->routeNameResolver = $this->createMock(RouteNameResolverInterface::class);
28-
$this->routerMock = $this->createMock(RouterInterface::class);
33+
34+
$this->iriConverter = $this->createMock(IriConverterInterface::class);
35+
$this->uriTemplate = $this->createMock(UriTemplate::class);
36+
$this->uriTemplateFactory = $this->createMock(UriTemplateFactory::class);
2937

3038
$this->normalizer = new ContentTypeNormalizer(
3139
$this->decoratedMock,
32-
$this->routeNameResolver,
33-
$this->routerMock,
40+
$this->uriTemplate,
41+
$this->uriTemplateFactory,
42+
$this->iriConverter,
3443
);
3544
$this->normalizer->setSerializer($this->createMock(SerializerInterface::class));
3645
}
@@ -58,39 +67,45 @@ public function testDelegatesNormalizeToDecorated() {
5867
;
5968

6069
// when
61-
$result = $this->normalizer->normalize($resource, null, ['resource_class' => DummyEntity::class]);
70+
$result = $this->normalizer->normalize($resource, null, ['resource_class' => \stdClass::class]);
6271

6372
// then
6473
$this->assertEquals($delegatedResult, $result);
6574
}
6675

6776
public function testNormalizeAddsEntityPath() {
6877
// given
69-
$resource = new ContentType();
78+
$contentType = new ContentType();
79+
$contentType->entityClass = 'App\Entity\ContentNode\DummyContentNode';
80+
7081
$delegatedResult = [
7182
'hello' => 'world',
72-
'entityClass' => 'DummyClass',
7383
];
7484
$this->decoratedMock->expects($this->once())
7585
->method('normalize')
7686
->willReturn($delegatedResult)
7787
;
78-
$this->routerMock->expects($this->once())
79-
->method('generate')
80-
->willReturn('/path')
88+
$this->uriTemplateFactory->expects($this->once())
89+
->method('createFromResourceClass')
90+
->willReturn(['/templatedUri', 'true'])
8191
;
82-
$this->routeNameResolver->expects($this->once())
83-
->method('getRouteName')
92+
93+
$this->uriTemplate->expects($this->once())
94+
->method('expand')
95+
->willReturn('/expandedUri')
8496
;
8597

8698
// when
87-
$result = $this->normalizer->normalize($resource, null, ['resource_class' => DummyEntity::class]);
99+
$result = $this->normalizer->normalize($contentType, null, ['resource_class' => ContentType::class]);
88100

89101
// then
90102
$expectedResult = [
91103
'hello' => 'world',
92-
'entityClass' => 'DummyClass',
93-
'entityPath' => '/path',
104+
'_links' => [
105+
'contentNodes' => [
106+
'href' => '/expandedUri',
107+
],
108+
],
94109
];
95110
$this->assertEquals($expectedResult, $result);
96111
}

0 commit comments

Comments
 (0)