Skip to content

Commit 091865f

Browse files
Merge pull request #1483 from soyuka/fix-subresource-override-operation
fix #1479 allow to override SubresourceOperations through metadata
2 parents d683d00 + 17c321f commit 091865f

16 files changed

+255
-67
lines changed

src/Annotation/ApiResource.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ final class ApiResource
4848
*/
4949
public $collectionOperations;
5050

51+
/**
52+
* @var array
53+
*/
54+
public $subresourceOperations;
55+
5156
/**
5257
* @var array
5358
*/

src/Bridge/Symfony/Routing/ApiLoader.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,13 @@ public function load($data, $type = null): RouteCollection
115115
'collection' => $operation['collection'],
116116
'operationId' => $operationId,
117117
],
118-
],
118+
] + $operation['defaults'] ?? [],
119119
$operation['requirements'] ?? [],
120-
[],
121-
'',
122-
[],
123-
['GET']
120+
$operation['options'] ?? [],
121+
$operation['host'] ?? '',
122+
$operation['schemes'] ?? [],
123+
['GET'],
124+
$operation['condition'] ?? ''
124125
));
125126
}
126127
}

src/Metadata/Extractor/XmlExtractor.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ protected function extractPath(string $path)
4747
'iri' => $this->phpize($resource, 'iri', 'string'),
4848
'itemOperations' => $this->getOperations($resource, 'itemOperation'),
4949
'collectionOperations' => $this->getOperations($resource, 'collectionOperation'),
50+
'subresourceOperations' => $this->getOperations($resource, 'subresourceOperation'),
5051
'attributes' => $this->getAttributes($resource, 'attribute') ?: null,
5152
'properties' => $this->getProperties($resource) ?: null,
5253
];

src/Metadata/Extractor/YamlExtractor.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ private function extractResources(array $resourcesYaml, string $path)
6767
'iri' => $this->phpize($resourceYaml, 'iri', 'string'),
6868
'itemOperations' => $resourceYaml['itemOperations'] ?? null,
6969
'collectionOperations' => $resourceYaml['collectionOperations'] ?? null,
70+
'subresourceOperations' => $resourceYaml['subresourceOperations'] ?? null,
7071
'attributes' => $resourceYaml['attributes'] ?? null,
7172
];
7273

src/Metadata/Resource/Factory/AnnotationResourceMetadataFactory.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,13 @@ private function createMetadata(ApiResource $annotation, ResourceMetadata $paren
9090
$annotation->iri,
9191
$annotation->itemOperations,
9292
$annotation->collectionOperations,
93-
$annotation->attributes
93+
$annotation->attributes,
94+
$annotation->subresourceOperations
9495
);
9596
}
9697

9798
$resourceMetadata = $parentResourceMetadata;
98-
foreach (['shortName', 'description', 'iri', 'itemOperations', 'collectionOperations', 'attributes'] as $property) {
99+
foreach (['shortName', 'description', 'iri', 'itemOperations', 'collectionOperations', 'subresourceOperations', 'attributes'] as $property) {
99100
$resourceMetadata = $this->createWith($resourceMetadata, $property, $annotation->$property);
100101
}
101102

src/Metadata/Resource/Factory/ExtractorResourceMetadataFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ private function handleNotFound(ResourceMetadata $parentPropertyMetadata = null,
8484
*/
8585
private function update(ResourceMetadata $resourceMetadata, array $metadata): ResourceMetadata
8686
{
87-
foreach (['shortName', 'description', 'iri', 'itemOperations', 'collectionOperations', 'attributes'] as $property) {
87+
foreach (['shortName', 'description', 'iri', 'itemOperations', 'collectionOperations', 'subresourceOperations', 'attributes'] as $property) {
8888
if (null === $metadata[$property] || null !== $resourceMetadata->{'get'.ucfirst($property)}()) {
8989
continue;
9090
}

src/Metadata/Resource/ResourceMetadata.php

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,17 @@ final class ResourceMetadata
2525
private $iri;
2626
private $itemOperations;
2727
private $collectionOperations;
28+
private $subresourceOperations;
2829
private $attributes;
2930

30-
public function __construct(string $shortName = null, string $description = null, string $iri = null, array $itemOperations = null, array $collectionOperations = null, array $attributes = null)
31+
public function __construct(string $shortName = null, string $description = null, string $iri = null, array $itemOperations = null, array $collectionOperations = null, array $attributes = null, array $subresourceOperations = null)
3132
{
3233
$this->shortName = $shortName;
3334
$this->description = $description;
3435
$this->iri = $iri;
3536
$this->itemOperations = $itemOperations;
3637
$this->collectionOperations = $collectionOperations;
38+
$this->subresourceOperations = $subresourceOperations;
3739
$this->attributes = $attributes;
3840
}
3941

@@ -49,10 +51,6 @@ public function getShortName()
4951

5052
/**
5153
* Returns a new instance with the given short name.
52-
*
53-
* @param string $shortName
54-
*
55-
* @return self
5654
*/
5755
public function withShortName(string $shortName): self
5856
{
@@ -74,10 +72,6 @@ public function getDescription()
7472

7573
/**
7674
* Returns a new instance with the given description.
77-
*
78-
* @param string $description
79-
*
80-
* @return self
8175
*/
8276
public function withDescription(string $description): self
8377
{
@@ -99,10 +93,6 @@ public function getIri()
9993

10094
/**
10195
* Returns a new instance with the given IRI.
102-
*
103-
* @param string $iri
104-
*
105-
* @return self
10696
*/
10797
public function withIri(string $iri): self
10898
{
@@ -124,10 +114,6 @@ public function getItemOperations()
124114

125115
/**
126116
* Returns a new instance with the given item operations.
127-
*
128-
* @param array $itemOperations
129-
*
130-
* @return self
131117
*/
132118
public function withItemOperations(array $itemOperations): self
133119
{
@@ -149,10 +135,6 @@ public function getCollectionOperations()
149135

150136
/**
151137
* Returns a new instance with the given collection operations.
152-
*
153-
* @param array $collectionOperations
154-
*
155-
* @return self
156138
*/
157139
public function withCollectionOperations(array $collectionOperations): self
158140
{
@@ -162,13 +144,31 @@ public function withCollectionOperations(array $collectionOperations): self
162144
return $metadata;
163145
}
164146

147+
/**
148+
* Gets subresource operations.
149+
*
150+
* @return array|null
151+
*/
152+
public function getSubresourceOperations()
153+
{
154+
return $this->subresourceOperations;
155+
}
156+
157+
/**
158+
* Returns a new instance with the given subresource operations.
159+
*/
160+
public function withSubresourceOperations(array $subresourceOperations): self
161+
{
162+
$metadata = clone $this;
163+
$metadata->subresourceOperations = $subresourceOperations;
164+
165+
return $metadata;
166+
}
167+
165168
/**
166169
* Gets a collection operation attribute, optionally fallback to a resource attribute.
167170
*
168-
* @param string|null $operationName
169-
* @param string $key
170-
* @param mixed $defaultValue
171-
* @param bool $resourceFallback
171+
* @param mixed $defaultValue
172172
*
173173
* @return mixed
174174
*/
@@ -180,10 +180,7 @@ public function getCollectionOperationAttribute(string $operationName = null, st
180180
/**
181181
* Gets an item operation attribute, optionally fallback to a resource attribute.
182182
*
183-
* @param string|null $operationName
184-
* @param string $key
185-
* @param mixed $defaultValue
186-
* @param bool $resourceFallback
183+
* @param mixed $defaultValue
187184
*
188185
* @return mixed
189186
*/
@@ -192,14 +189,22 @@ public function getItemOperationAttribute(string $operationName = null, string $
192189
return $this->getOperationAttribute($this->itemOperations, $operationName, $key, $defaultValue, $resourceFallback);
193190
}
194191

192+
/**
193+
* Gets a subresource operation attribute, optionally fallback to a resource attribute.
194+
*
195+
* @param mixed $defaultValue
196+
*
197+
* @return mixed
198+
*/
199+
public function getSubresourceOperationAttribute(string $operationName = null, string $key, $defaultValue = null, bool $resourceFallback = false)
200+
{
201+
return $this->getOperationAttribute($this->subresourceOperations, $operationName, $key, $defaultValue, $resourceFallback);
202+
}
203+
195204
/**
196205
* Gets an operation attribute, optionally fallback to a resource attribute.
197206
*
198-
* @param array|null $operations
199-
* @param string|null $operationName
200-
* @param string $key
201-
* @param mixed $defaultValue
202-
* @param bool $resourceFallback
207+
* @param mixed $defaultValue
203208
*
204209
* @return mixed
205210
*/
@@ -229,8 +234,7 @@ public function getAttributes()
229234
/**
230235
* Gets an attribute.
231236
*
232-
* @param string $key
233-
* @param mixed $defaultValue
237+
* @param mixed $defaultValue
234238
*
235239
* @return mixed
236240
*/
@@ -245,10 +249,6 @@ public function getAttribute(string $key, $defaultValue = null)
245249

246250
/**
247251
* Returns a new instance with the given attribute.
248-
*
249-
* @param array $attributes
250-
*
251-
* @return self
252252
*/
253253
public function withAttributes(array $attributes): self
254254
{

src/Metadata/schema/metadata.xsd

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<xsd:element name="property" minOccurs="0" maxOccurs="unbounded" type="property"/>
2323
<xsd:element name="itemOperations" minOccurs="0" maxOccurs="unbounded" type="itemOperations"/>
2424
<xsd:element name="collectionOperations" minOccurs="0" maxOccurs="unbounded" type="collectionOperations"/>
25+
<xsd:element name="subresourceOperations" minOccurs="0" maxOccurs="unbounded" type="subresourceOperations"/>
2526
</xsd:sequence>
2627

2728
<xsd:attribute type="xsd:string" name="class" use="required"/>
@@ -56,6 +57,19 @@
5657
<xsd:attribute type="xsd:string" name="name"/>
5758
</xsd:complexType>
5859

60+
<xsd:complexType name="subresourceOperations">
61+
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
62+
<xsd:element name="subresourceOperation" minOccurs="0" maxOccurs="unbounded" type="subresourceOperation"/>
63+
</xsd:sequence>
64+
</xsd:complexType>
65+
66+
<xsd:complexType name="subresourceOperation">
67+
<xsd:sequence minOccurs="1" maxOccurs="unbounded">
68+
<xsd:element name="attribute" minOccurs="1" maxOccurs="unbounded" type="attribute"/>
69+
</xsd:sequence>
70+
<xsd:attribute type="xsd:string" name="name"/>
71+
</xsd:complexType>
72+
5973
<xsd:complexType name="attribute" mixed="true">
6074
<xsd:choice maxOccurs="unbounded">
6175
<xsd:element name="attribute" type="attribute" minOccurs="0" maxOccurs="unbounded" />

src/Operation/Factory/SubresourceOperationFactory.php

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ final class SubresourceOperationFactory implements SubresourceOperationFactoryIn
2626
{
2727
const SUBRESOURCE_SUFFIX = '_subresource';
2828
const FORMAT_SUFFIX = '.{_format}';
29+
const ROUTE_OPTIONS = ['defaults' => [], 'requirements' => [], 'options' => [], 'host' => '', 'schemes' => [], 'condition' => ''];
2930

3031
private $resourceMetadataFactory;
3132
private $propertyNameCollectionFactory;
@@ -82,6 +83,7 @@ private function computeSubresourceOperations(string $resourceClass, array &$tre
8283
continue;
8384
}
8485

86+
$rootResourceMetadata = $this->resourceMetadataFactory->create($rootResourceClass);
8587
$operationName = 'get';
8688
$operation = [
8789
'property' => $property,
@@ -91,19 +93,25 @@ private function computeSubresourceOperations(string $resourceClass, array &$tre
9193
];
9294

9395
if (null === $parentOperation) {
94-
$rootResourceMetadata = $this->resourceMetadataFactory->create($rootResourceClass);
9596
$rootShortname = $rootResourceMetadata->getShortName();
9697
$operation['identifiers'] = [['id', $rootResourceClass, true]];
97-
$operation['route_name'] = sprintf(
98-
'%s%s_%s_%s%s',
99-
RouteNameGenerator::ROUTE_NAME_PREFIX,
100-
RouteNameGenerator::inflector($rootShortname),
98+
$operation['operation_name'] = sprintf(
99+
'%s_%s%s',
101100
RouteNameGenerator::inflector($operation['property'], $operation['collection'] ?? false),
102101
$operationName,
103102
self::SUBRESOURCE_SUFFIX
104103
);
105104

106-
$operation['path'] = sprintf(
105+
$subresourceOperation = $rootResourceMetadata->getSubresourceOperations()[$operation['operation_name']] ?? [];
106+
107+
$operation['route_name'] = sprintf(
108+
'%s%s_%s',
109+
RouteNameGenerator::ROUTE_NAME_PREFIX,
110+
RouteNameGenerator::inflector($rootShortname),
111+
$operation['operation_name']
112+
);
113+
114+
$operation['path'] = $subresourceOperation['path'] ?? sprintf(
107115
'/%s/{id}/%s%s',
108116
$this->pathSegmentNameGenerator->getSegmentName($rootShortname, true),
109117
$this->pathSegmentNameGenerator->getSegmentName($operation['property'], $operation['collection']),
@@ -117,18 +125,31 @@ private function computeSubresourceOperations(string $resourceClass, array &$tre
117125
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
118126
$operation['identifiers'] = $parentOperation['identifiers'];
119127
$operation['identifiers'][] = [$parentOperation['property'], $resourceClass, $parentOperation['collection']];
120-
$operation['route_name'] = str_replace('get'.self::SUBRESOURCE_SUFFIX, RouteNameGenerator::inflector($property, $operation['collection']).'_get'.self::SUBRESOURCE_SUFFIX, $parentOperation['route_name']);
128+
129+
$operation['operation_name'] = str_replace('get'.self::SUBRESOURCE_SUFFIX, RouteNameGenerator::inflector($property, $operation['collection']).'_get'.self::SUBRESOURCE_SUFFIX, $parentOperation['operation_name']);
130+
131+
$operation['route_name'] = str_replace($parentOperation['operation_name'], $operation['operation_name'], $parentOperation['route_name']);
121132

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

126-
$operation['path'] = str_replace(self::FORMAT_SUFFIX, '', $parentOperation['path']);
127-
if ($parentOperation['collection']) {
128-
list($key) = end($operation['identifiers']);
129-
$operation['path'] .= sprintf('/{%s}', $key);
137+
$subresourceOperation = $rootResourceMetadata->getSubresourceOperations()[$operation['operation_name']] ?? [];
138+
139+
if (isset($subresourceOperation['path'])) {
140+
$operation['path'] = $subresourceOperation['path'];
141+
} else {
142+
$operation['path'] = str_replace(self::FORMAT_SUFFIX, '', $parentOperation['path']);
143+
if ($parentOperation['collection']) {
144+
list($key) = end($operation['identifiers']);
145+
$operation['path'] .= sprintf('/{%s}', $key);
146+
}
147+
$operation['path'] .= sprintf('/%s%s', $this->pathSegmentNameGenerator->getSegmentName($property, $operation['collection']), self::FORMAT_SUFFIX);
130148
}
131-
$operation['path'] .= sprintf('/%s%s', $this->pathSegmentNameGenerator->getSegmentName($property, $operation['collection']), self::FORMAT_SUFFIX);
149+
}
150+
151+
foreach (self::ROUTE_OPTIONS as $routeOption => $defaultValue) {
152+
$operation[$routeOption] = $subresourceOperation[$routeOption] ?? $defaultValue;
132153
}
133154

134155
$tree[$operation['route_name']] = $operation;

tests/Fixtures/FileConfigurations/resources.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
<attribute name="path">the/collection/path</attribute>
2626
</collectionOperation>
2727
</collectionOperations>
28+
<subresourceOperations>
29+
<subresourceOperation name="my_collection_subresource">
30+
<attribute name="path">the/subresource/path</attribute>
31+
</subresourceOperation>
32+
</subresourceOperations>
2833
<attribute name="normalization_context">
2934
<attribute name="groups">
3035
<attribute>default</attribute>

tests/Fixtures/FileConfigurations/resources.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ resources:
1212
my_collection_op:
1313
method: 'POST'
1414
path: 'the/collection/path'
15+
subresourceOperations:
16+
my_collection_subresource:
17+
path: 'the/subresource/path'
1518
attributes:
1619
normalization_context:
1720
groups: ['default']

tests/Fixtures/FileConfigurations/single_resource.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
my_collection_op:
1111
method: 'POST'
1212
path: 'the/collection/path'
13+
subresourceOperations:
14+
my_collection_subresource:
15+
path: 'the/subresource/path'
1316
attributes:
1417
normalization_context:
1518
groups: ['default']

0 commit comments

Comments
 (0)