Skip to content

Commit a441368

Browse files
Merge pull request #2175 from usu/feature/content-type-entity-path
inject entityPath into ContentType
2 parents 89978e8 + 93fedc5 commit a441368

File tree

13 files changed

+224
-18
lines changed

13 files changed

+224
-18
lines changed

api/config/services.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ services:
8787
App\Serializer\Normalizer\CircularReferenceDetectingHalItemNormalizer:
8888
decorates: 'api_platform.hal.normalizer.item'
8989

90+
App\Serializer\Normalizer\ContentTypeNormalizer:
91+
decorates: 'api_platform.hal.normalizer.item'
92+
9093
App\Serializer\Normalizer\UriTemplateNormalizer:
9194
decorates: 'api_platform.hal.normalizer.entrypoint'
9295

api/src/Entity/ContentNode.php

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

api/src/Entity/ContentType.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,19 @@ class ContentType extends BaseEntity {
5656
*/
5757
#[ApiProperty(writable: false)]
5858
public ?array $jsonConfig = [];
59+
60+
/**
61+
* API endpoint link for creating new entities of type entityClass.
62+
*/
63+
#[Groups(['read'])]
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
73+
}
5974
}

api/src/Entity/Day.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public function getDayNumber(): int {
9595
* @return ScheduleEntry[]
9696
*/
9797
#[ApiProperty(example: '/schedule_entries?period=%2Fperiods%2F1a2b3c4d&start%5Bstrictly_before%5D=2022-01-03T00%3A00%3A00%2B00%3A00&end%5Bafter%5D=2022-01-02T00%3A00%3A00%2B00%3A00')]
98-
#[RelatedCollectionLink('scheduleEntry', ['period' => 'period', 'start[strictly_before]' => 'end', 'end[after]' => 'start'])]
98+
#[RelatedCollectionLink(ScheduleEntry::class, ['period' => 'period', 'start[strictly_before]' => 'end', 'end[after]' => 'start'])]
9999
#[Groups(['read'])]
100100
public function getScheduleEntries(): array {
101101
return array_filter(

api/src/Metadata/Resource/Factory/UriTemplateFactory.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,25 @@ public function __construct(
3535
* @return array contains the templated URI (or null when not successful), as well as a boolean flag which
3636
* indicates whether any template parameters are present in the URI
3737
*/
38-
public function create(string $shortName): array {
38+
public function createFromShortname(string $shortName): array {
3939
$resourceClass = $this->resourceNameMapping[lcfirst($shortName)] ?? null;
40+
4041
if (!$resourceClass) {
4142
return [null, false];
4243
}
44+
45+
return $this->createFromResourceClass($resourceClass);
46+
}
47+
48+
/**
49+
* Create an URI template based on the allowed parameters for the specified entity.
50+
*
51+
* @throws ResourceClassNotFoundException
52+
*
53+
* @return array contains the templated URI (or null when not successful), as well as a boolean flag which
54+
* indicates whether any template parameters are present in the URI
55+
*/
56+
public function createFromResourceClass(string $resourceClass): array {
4357
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
4458

4559
$baseUri = $this->iriConverter->getIriFromResourceClass($resourceClass);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace App\Serializer\Normalizer;
4+
5+
use ApiPlatform\Core\Api\IriConverterInterface;
6+
use App\Entity\ContentType;
7+
use App\Metadata\Resource\Factory\UriTemplateFactory;
8+
use Rize\UriTemplate;
9+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
10+
use Symfony\Component\Serializer\SerializerAwareInterface;
11+
use Symfony\Component\Serializer\SerializerInterface;
12+
13+
/**
14+
* Adds API link to contentNodes for ContentType based on the defined 'entityClass'.
15+
*/
16+
class ContentTypeNormalizer implements NormalizerInterface, SerializerAwareInterface {
17+
public function __construct(
18+
private NormalizerInterface $decorated,
19+
private UriTemplate $uriTemplate,
20+
private UriTemplateFactory $uriTemplateFactory,
21+
private IriConverterInterface $iriConverter,
22+
) {
23+
}
24+
25+
public function supportsNormalization($data, $format = null) {
26+
return $this->decorated->supportsNormalization($data, $format);
27+
}
28+
29+
public function normalize($object, $format = null, array $context = []) {
30+
$data = $this->decorated->normalize($object, $format, $context);
31+
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)]);
36+
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']);
42+
}
43+
44+
return $data;
45+
}
46+
47+
public function setSerializer(SerializerInterface $serializer) {
48+
if ($this->decorated instanceof SerializerAwareInterface) {
49+
$this->decorated->setSerializer($serializer);
50+
}
51+
}
52+
}

api/src/Serializer/Normalizer/RelatedCollectionLinkNormalizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public function getRelatedCollectionHref($object, $rel, array $context = []): st
138138
if ($annotation = $this->getRelatedCollectionLinkAnnotation($resourceClass, $rel)) {
139139
// If there is an explicit annotation, there is no need to inspect the Doctrine metadata
140140
$params = $this->extractUriParams($object, $annotation->getParams());
141-
[$uriTemplate] = $this->uriTemplateFactory->create($annotation->getRelatedEntity());
141+
[$uriTemplate] = $this->uriTemplateFactory->createFromResourceClass($annotation->getRelatedEntity());
142142

143143
return $this->uriTemplate->expand($uriTemplate, $params);
144144
}

api/src/Serializer/Normalizer/UriTemplateNormalizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function normalize($object, $format = null, array $context = []) {
3131
continue;
3232
}
3333

34-
[$uriTemplate, $templated] = $this->uriTemplateFactory->create($rel);
34+
[$uriTemplate, $templated] = $this->uriTemplateFactory->createFromShortname($rel);
3535
if (!$uriTemplate) {
3636
continue;
3737
}

api/tests/Api/ContentTypes/ReadContentTypeTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ public function testGetSingleContentTypeIsAllowedForAnonymousUser() {
1818
'id' => $contentType->getId(),
1919
'name' => $contentType->name,
2020
'active' => $contentType->active,
21+
'_links' => [
22+
'contentNodes' => [
23+
'href' => '/content_node/single_texts?contentType='.urlencode($this->getIriFor($contentType)),
24+
],
25+
],
2126
]);
2227
}
2328

@@ -30,6 +35,11 @@ public function testGetSingleContentTypeIsAllowedForLoggedInUser() {
3035
'id' => $contentType->getId(),
3136
'name' => $contentType->name,
3237
'active' => $contentType->active,
38+
'_links' => [
39+
'contentNodes' => [
40+
'href' => '/content_node/single_texts?contentType='.urlencode($this->getIriFor($contentType)),
41+
],
42+
],
3343
]);
3444
}
3545
}

api/tests/Metadata/Resource/Factory/UriTemplateFactoryTest.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public function testCantCreateUriTemplateForNonexistentResource() {
5151
$this->createFactory();
5252

5353
// when
54-
[$uri, $templated] = $this->uriTemplateFactory->create($resource);
54+
[$uri, $templated] = $this->uriTemplateFactory->createFromShortname($resource);
5555

5656
// then
5757
self::assertThat($uri, self::equalTo(null));
@@ -68,7 +68,7 @@ public function testCreatesNonTemplatedUri() {
6868
$this->createFactory();
6969

7070
// when
71-
[$uri, $templated] = $this->uriTemplateFactory->create($resource);
71+
[$uri, $templated] = $this->uriTemplateFactory->createFromShortname($resource);
7272

7373
// then
7474
self::assertThat($uri, self::equalTo('/dummys'));
@@ -81,7 +81,7 @@ public function testCreatesTemplatedUriWithIdPathParameter() {
8181
$this->createFactory();
8282

8383
// when
84-
[$uri, $templated] = $this->uriTemplateFactory->create($resource);
84+
[$uri, $templated] = $this->uriTemplateFactory->createFromShortname($resource);
8585

8686
// then
8787
self::assertThat($uri, self::equalTo('/dummys{/id}'));
@@ -100,7 +100,7 @@ public function testCreatesTemplatedUriWithFilterQueryParameter() {
100100
$this->createFactory();
101101

102102
// when
103-
[$uri, $templated] = $this->uriTemplateFactory->create($resource);
103+
[$uri, $templated] = $this->uriTemplateFactory->createFromShortname($resource);
104104

105105
// then
106106
self::assertThat($uri, self::equalTo('/dummys{/id}{?some_filter}'));
@@ -114,7 +114,7 @@ public function testCreatesTemplatedUriWithPaginationQueryParameter() {
114114
$this->createFactory();
115115

116116
// when
117-
[$uri, $templated] = $this->uriTemplateFactory->create($resource);
117+
[$uri, $templated] = $this->uriTemplateFactory->createFromShortname($resource);
118118

119119
// then
120120
self::assertThat($uri, self::equalTo('/dummys{/id}{?page}'));
@@ -132,7 +132,7 @@ public function testCreatesTemplatedUriWithAdvancedPaginationQueryParameters() {
132132
$this->createFactory();
133133

134134
// when
135-
[$uri, $templated] = $this->uriTemplateFactory->create($resource);
135+
[$uri, $templated] = $this->uriTemplateFactory->createFromShortname($resource);
136136

137137
// then
138138
self::assertThat($uri, self::equalTo('/dummys{/id}{?page,itemsPerPage,pagination}'));

0 commit comments

Comments
 (0)