Skip to content

Commit 1e5b1ca

Browse files
author
abluchet
committed
fix #1479
1 parent d683d00 commit 1e5b1ca

File tree

3 files changed

+169
-23
lines changed

3 files changed

+169
-23
lines changed

src/Bridge/Symfony/Routing/ApiLoader.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ public function load($data, $type = null): RouteCollection
8787

8888
if (null !== $collectionOperations = $resourceMetadata->getCollectionOperations()) {
8989
foreach ($collectionOperations as $operationName => $operation) {
90+
if ('subresource' === substr($operationName, -11)) {
91+
continue;
92+
}
9093
$this->addRoute($routeCollection, $resourceClass, $operationName, $operation, $resourceShortName, OperationType::COLLECTION);
9194
}
9295
}
@@ -115,12 +118,13 @@ public function load($data, $type = null): RouteCollection
115118
'collection' => $operation['collection'],
116119
'operationId' => $operationId,
117120
],
118-
],
119-
$operation['requirements'] ?? [],
120-
[],
121-
'',
122-
[],
123-
['GET']
121+
] + $operations['defaults'],
122+
$operation['requirements'],
123+
$operation['options'],
124+
$operation['host'],
125+
$operation['schemes'],
126+
['GET'],
127+
$operation['condition']
124128
));
125129
}
126130
}

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+
$collectionOperation = $rootResourceMetadata->getCollectionOperations()[$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'] = $collectionOperation['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+
$collectionOperation = $rootResourceMetadata->getCollectionOperations()[$operation['operation_name']] ?? [];
138+
139+
if (isset($collectionOperation['path'])) {
140+
$operation['path'] = $collectionOperation['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] = $collectionOperation[$routeOption] ?? $defaultValue;
132153
}
133154

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

tests/Operation/Factory/SubresourceOperationFactoryTest.php

Lines changed: 126 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ public function testCreate()
7171
],
7272
'route_name' => 'api_dummy_entities_subresource_get_subresource',
7373
'path' => '/dummy_entities/{id}/subresource.{_format}',
74-
],
74+
'operation_name' => 'subresource_get_subresource',
75+
] + SubresourceOperationFactory::ROUTE_OPTIONS,
7576
'api_dummy_entities_subresource_another_subresource_get_subresource' => [
7677
'property' => 'anotherSubresource',
7778
'collection' => false,
@@ -83,7 +84,8 @@ public function testCreate()
8384
],
8485
'route_name' => 'api_dummy_entities_subresource_another_subresource_get_subresource',
8586
'path' => '/dummy_entities/{id}/subresource/another_subresource.{_format}',
86-
],
87+
'operation_name' => 'subresource_another_subresource_get_subresource',
88+
] + SubresourceOperationFactory::ROUTE_OPTIONS,
8789
'api_dummy_entities_subresource_another_subresource_subcollections_get_subresource' => [
8890
'property' => 'subcollection',
8991
'collection' => true,
@@ -96,7 +98,8 @@ public function testCreate()
9698
],
9799
'route_name' => 'api_dummy_entities_subresource_another_subresource_subcollections_get_subresource',
98100
'path' => '/dummy_entities/{id}/subresource/another_subresource/subcollections.{_format}',
99-
],
101+
'operation_name' => 'subresource_another_subresource_subcollections_get_subresource',
102+
] + SubresourceOperationFactory::ROUTE_OPTIONS,
100103
'api_dummy_entities_subcollections_get_subresource' => [
101104
'property' => 'subcollection',
102105
'collection' => true,
@@ -107,7 +110,8 @@ public function testCreate()
107110
],
108111
'route_name' => 'api_dummy_entities_subcollections_get_subresource',
109112
'path' => '/dummy_entities/{id}/subcollections.{_format}',
110-
],
113+
'operation_name' => 'subcollections_get_subresource',
114+
] + SubresourceOperationFactory::ROUTE_OPTIONS,
111115
'api_dummy_entities_subcollections_another_subresource_get_subresource' => [
112116
'property' => 'anotherSubresource',
113117
'collection' => false,
@@ -119,7 +123,8 @@ public function testCreate()
119123
],
120124
'route_name' => 'api_dummy_entities_subcollections_another_subresource_get_subresource',
121125
'path' => '/dummy_entities/{id}/subcollections/{subcollection}/another_subresource.{_format}',
122-
],
126+
'operation_name' => 'subcollections_another_subresource_get_subresource',
127+
] + SubresourceOperationFactory::ROUTE_OPTIONS,
123128
'api_dummy_entities_subcollections_another_subresource_subresource_get_subresource' => [
124129
'property' => 'subresource',
125130
'collection' => false,
@@ -132,7 +137,123 @@ public function testCreate()
132137
],
133138
'route_name' => 'api_dummy_entities_subcollections_another_subresource_subresource_get_subresource',
134139
'path' => '/dummy_entities/{id}/subcollections/{subcollection}/another_subresource/subresource.{_format}',
140+
'operation_name' => 'subcollections_another_subresource_subresource_get_subresource',
141+
] + SubresourceOperationFactory::ROUTE_OPTIONS,
142+
], $subresourceOperationFactory->create(DummyEntity::class));
143+
}
144+
145+
public function testCreateByOverriding()
146+
{
147+
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
148+
$resourceMetadataFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('relatedDummyEntity'));
149+
$resourceMetadataFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn((new ResourceMetadata('dummyEntity'))->withCollectionOperations([
150+
'subcollections_get_subresource' => [
151+
'path' => '/dummy_entities/{id}/foobars',
135152
],
153+
]));
154+
155+
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
156+
$propertyNameCollectionFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['foo', 'subresource', 'subcollection']));
157+
$propertyNameCollectionFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['bar', 'anotherSubresource']));
158+
159+
$subresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class));
160+
$subresourceMetadataCollection = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class, true));
161+
$anotherSubresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyEntity::class, false));
162+
163+
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
164+
$propertyMetadataFactoryProphecy->create(DummyEntity::class, 'foo')->shouldBeCalled()->willReturn(new PropertyMetadata());
165+
$propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subresource')->shouldBeCalled()->willReturn($subresourceMetadata);
166+
$propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subcollection')->shouldBeCalled()->willReturn($subresourceMetadataCollection);
167+
$propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'bar')->shouldBeCalled()->willReturn(new PropertyMetadata());
168+
$propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'anotherSubresource')->shouldBeCalled()->willReturn($anotherSubresourceMetadata);
169+
170+
$pathSegmentNameGeneratorProphecy = $this->prophesize(PathSegmentNameGeneratorInterface::class);
171+
$pathSegmentNameGeneratorProphecy->getSegmentName('subresource', false)->shouldBeCalled()->willReturn('subresource');
172+
$pathSegmentNameGeneratorProphecy->getSegmentName('subcollection', true)->shouldBeCalled()->willReturn('subcollections');
173+
$pathSegmentNameGeneratorProphecy->getSegmentName('dummyEntity', true)->shouldBeCalled()->willReturn('dummy_entities');
174+
$pathSegmentNameGeneratorProphecy->getSegmentName('anotherSubresource', false)->shouldBeCalled()->willReturn('another_subresource');
175+
176+
$subresourceOperationFactory = new SubresourceOperationFactory($resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $pathSegmentNameGeneratorProphecy->reveal());
177+
178+
$this->assertEquals([
179+
'api_dummy_entities_subresource_get_subresource' => [
180+
'property' => 'subresource',
181+
'collection' => false,
182+
'resource_class' => RelatedDummyEntity::class,
183+
'shortNames' => ['relatedDummyEntity', 'dummyEntity'],
184+
'identifiers' => [
185+
['id', DummyEntity::class, true],
186+
],
187+
'route_name' => 'api_dummy_entities_subresource_get_subresource',
188+
'path' => '/dummy_entities/{id}/subresource.{_format}',
189+
'operation_name' => 'subresource_get_subresource',
190+
] + SubresourceOperationFactory::ROUTE_OPTIONS,
191+
'api_dummy_entities_subresource_another_subresource_get_subresource' => [
192+
'property' => 'anotherSubresource',
193+
'collection' => false,
194+
'resource_class' => DummyEntity::class,
195+
'shortNames' => ['dummyEntity', 'relatedDummyEntity'],
196+
'identifiers' => [
197+
['id', DummyEntity::class, true],
198+
['subresource', RelatedDummyEntity::class, false],
199+
],
200+
'route_name' => 'api_dummy_entities_subresource_another_subresource_get_subresource',
201+
'path' => '/dummy_entities/{id}/subresource/another_subresource.{_format}',
202+
'operation_name' => 'subresource_another_subresource_get_subresource',
203+
] + SubresourceOperationFactory::ROUTE_OPTIONS,
204+
'api_dummy_entities_subresource_another_subresource_subcollections_get_subresource' => [
205+
'property' => 'subcollection',
206+
'collection' => true,
207+
'resource_class' => RelatedDummyEntity::class,
208+
'shortNames' => ['relatedDummyEntity', 'dummyEntity'],
209+
'identifiers' => [
210+
['id', DummyEntity::class, true],
211+
['subresource', RelatedDummyEntity::class, false],
212+
['anotherSubresource', DummyEntity::class, false],
213+
],
214+
'route_name' => 'api_dummy_entities_subresource_another_subresource_subcollections_get_subresource',
215+
'path' => '/dummy_entities/{id}/subresource/another_subresource/subcollections.{_format}',
216+
'operation_name' => 'subresource_another_subresource_subcollections_get_subresource',
217+
] + SubresourceOperationFactory::ROUTE_OPTIONS,
218+
'api_dummy_entities_subcollections_get_subresource' => [
219+
'property' => 'subcollection',
220+
'collection' => true,
221+
'resource_class' => RelatedDummyEntity::class,
222+
'shortNames' => ['relatedDummyEntity', 'dummyEntity'],
223+
'identifiers' => [
224+
['id', DummyEntity::class, true],
225+
],
226+
'route_name' => 'api_dummy_entities_subcollections_get_subresource',
227+
'path' => '/dummy_entities/{id}/foobars',
228+
'operation_name' => 'subcollections_get_subresource',
229+
] + SubresourceOperationFactory::ROUTE_OPTIONS,
230+
'api_dummy_entities_subcollections_another_subresource_get_subresource' => [
231+
'property' => 'anotherSubresource',
232+
'collection' => false,
233+
'resource_class' => DummyEntity::class,
234+
'shortNames' => ['dummyEntity', 'relatedDummyEntity'],
235+
'identifiers' => [
236+
['id', DummyEntity::class, true],
237+
['subcollection', RelatedDummyEntity::class, true],
238+
],
239+
'route_name' => 'api_dummy_entities_subcollections_another_subresource_get_subresource',
240+
'path' => '/dummy_entities/{id}/foobars/{subcollection}/another_subresource.{_format}',
241+
'operation_name' => 'subcollections_another_subresource_get_subresource',
242+
] + SubresourceOperationFactory::ROUTE_OPTIONS,
243+
'api_dummy_entities_subcollections_another_subresource_subresource_get_subresource' => [
244+
'property' => 'subresource',
245+
'collection' => false,
246+
'resource_class' => RelatedDummyEntity::class,
247+
'shortNames' => ['relatedDummyEntity', 'dummyEntity'],
248+
'identifiers' => [
249+
['id', DummyEntity::class, true],
250+
['subcollection', RelatedDummyEntity::class, true],
251+
['anotherSubresource', DummyEntity::class, false],
252+
],
253+
'route_name' => 'api_dummy_entities_subcollections_another_subresource_subresource_get_subresource',
254+
'path' => '/dummy_entities/{id}/foobars/{subcollection}/another_subresource/subresource.{_format}',
255+
'operation_name' => 'subcollections_another_subresource_subresource_get_subresource',
256+
] + SubresourceOperationFactory::ROUTE_OPTIONS,
136257
], $subresourceOperationFactory->create(DummyEntity::class));
137258
}
138259
}

0 commit comments

Comments
 (0)