Skip to content

Commit 43b7cb2

Browse files
Simperfitdunglas
authored andcommitted
fix #582 itemOperation Normalization/Denormalization in doc (#587)
* feature: fix #582 add itemOperation Normalization/Denormalization into the doc * hotfix: take comments in account * hotfix: fix test * hotfix: refactor parser * hotfix: refactor parser * hotfix: fix test * hotfix: fix cs * feat: add tests on attributes.
1 parent 8bebffd commit 43b7cb2

File tree

4 files changed

+134
-21
lines changed

4 files changed

+134
-21
lines changed

src/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,11 @@ private function getApiDoc(bool $collection, string $resourceClass, ResourceMeta
106106
];
107107

108108
if (isset($operationHydraDoc['expects']) && 'owl:Nothing' !== $operationHydraDoc['expects']) {
109-
$data['input'] = sprintf('%s:%s', ApiPlatformParser::IN_PREFIX, $resourceClass);
109+
$data['input'] = sprintf('%s:%s:%s', ApiPlatformParser::IN_PREFIX, $resourceClass, $operationName);
110110
}
111111

112112
if (isset($operationHydraDoc['returns']) && 'owl:Nothing' !== $operationHydraDoc['returns']) {
113-
$data['output'] = sprintf('%s:%s', ApiPlatformParser::OUT_PREFIX, $resourceClass);
113+
$data['output'] = sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, $resourceClass, $operationName);
114114
}
115115

116116
if ($collection && Request::METHOD_GET === $method) {

src/Bridge/NelmioApiDoc/Parser/ApiPlatformParser.php

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFa
5555
*/
5656
public function supports(array $item)
5757
{
58-
$data = explode(':', $item['class'], 2);
58+
$data = explode(':', $item['class'], 3);
5959
if (!in_array($data[0], [self::IN_PREFIX, self::OUT_PREFIX])) {
6060
return false;
6161
}
@@ -79,10 +79,15 @@ public function supports(array $item)
7979
*/
8080
public function parse(array $item) : array
8181
{
82-
list($io, $resourceClass) = explode(':', $item['class'], 2);
82+
list($io, $resourceClass, $operationName) = explode(':', $item['class'], 3);
8383
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
8484

85-
return $this->parseClass($resourceMetadata, $resourceClass, $io);
85+
$classOperations = $this->getGroupsForItemAndCollectionOperation($resourceMetadata, $resourceClass, $io, $operationName);
86+
if (!empty($classOperations['serializer_groups'])) {
87+
return $this->getPropertyMetadata($resourceMetadata, $resourceClass, $io, [], $classOperations);
88+
}
89+
90+
return $this->parseResource($resourceMetadata, $resourceClass, $io);
8691
}
8792

8893
/**
@@ -95,13 +100,12 @@ public function parse(array $item) : array
95100
*
96101
* @return array
97102
*/
98-
private function parseClass(ResourceMetadata $resourceMetadata, string $resourceClass, string $io, array $visited = []) : array
103+
private function parseResource(ResourceMetadata $resourceMetadata, string $resourceClass, string $io, array $visited = []) : array
99104
{
100105
$visited[] = $resourceClass;
101106

102-
$attributes = $resourceMetadata->getAttributes();
103107
$options = [];
104-
$data = [];
108+
$attributes = $resourceMetadata->getAttributes();
105109

106110
if (isset($attributes['normalization_context']['groups'])) {
107111
$options['serializer_groups'] = $attributes['normalization_context']['groups'];
@@ -111,6 +115,53 @@ private function parseClass(ResourceMetadata $resourceMetadata, string $resource
111115
$options['serializer_groups'] = isset($options['serializer_groups']) ? array_merge($options['serializer_groups'], $attributes['denormalization_context']['groups']) : $options['serializer_groups'];
112116
}
113117

118+
return $this->getPropertyMetadata($resourceMetadata, $resourceClass, $io, $visited, $options);
119+
}
120+
121+
/**
122+
* Returns groups of item & collection.
123+
*
124+
* @param ResourceMetadata $resourceMetadata
125+
* @param string $resourceClass
126+
* @param string $io
127+
* @param string[] $visited
128+
*
129+
* @return array
130+
*/
131+
private function getGroupsForItemAndCollectionOperation(ResourceMetadata $resourceMetadata, string $resourceClass, string $io, string $operationName, array $visited = []) : array
132+
{
133+
$visited[] = $resourceClass;
134+
135+
$options = [];
136+
137+
$operation['denormalization_context'] = array_merge($resourceMetadata->getItemOperationAttribute($operationName, 'denormalization_context', []), $resourceMetadata->getCollectionOperationAttribute($operationName, 'denormalization_context', []));
138+
$operation['normalization_context'] = array_merge($resourceMetadata->getItemOperationAttribute($operationName, 'normalization_context', []), $resourceMetadata->getCollectionOperationAttribute($operationName, 'normalization_context', []));
139+
140+
$options['serializer_groups'] = !empty($operation['normalization_context']) ? $operation['normalization_context']['groups'] : [];
141+
142+
$options['serializer_groups'] = array_merge(
143+
$options['serializer_groups'],
144+
!empty($operation['denormalization_context']) ? $operation['denormalization_context']['groups'] : []
145+
);
146+
147+
return $options;
148+
}
149+
150+
/**
151+
* Returns a property metadata.
152+
*
153+
* @param ResourceMetadata $resourceMetadata
154+
* @param string $resourceClass
155+
* @param string $io
156+
* @param string[] $visited
157+
* @param string[] $options
158+
*
159+
* @return array
160+
*/
161+
private function getPropertyMetadata(ResourceMetadata $resourceMetadata, string $resourceClass, string $io, array $visited, array $options) : array
162+
{
163+
$data = [];
164+
114165
foreach ($this->propertyNameCollectionFactory->create($resourceClass, $options) as $propertyName) {
115166
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
116167

@@ -209,7 +260,7 @@ private function parseProperty(ResourceMetadata $resourceMetadata, PropertyMetad
209260

210261
$data['actualType'] = DataTypes::MODEL;
211262
$data['subType'] = $className;
212-
$data['children'] = in_array($className, $visited) ? [] : $this->parseClass($resourceMetadata, $className, $io, $visited);
263+
$data['children'] = in_array($className, $visited) ? [] : $this->parseResource($resourceMetadata, $className, $io);
213264

214265
return $data;
215266
}

tests/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProviderTest.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public function testGetAnnotations()
132132
$this->assertEquals('Retrieves the collection of Dummy resources.', $actual[0]->getDescription());
133133
$this->assertEquals('Dummy', $actual[0]->getResourceDescription());
134134
$this->assertEquals('Dummy', $actual[0]->getSection());
135-
$this->assertEquals(sprintf('%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class), $actual[0]->getOutput());
135+
$this->assertEquals(sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'get'), $actual[0]->getOutput());
136136
$this->assertEquals([
137137
'name' => [
138138
'property' => 'name',
@@ -150,8 +150,8 @@ public function testGetAnnotations()
150150
$this->assertEquals('Creates a Dummy resource.', $actual[1]->getDescription());
151151
$this->assertEquals('Dummy', $actual[1]->getResourceDescription());
152152
$this->assertEquals('Dummy', $actual[1]->getSection());
153-
$this->assertEquals(sprintf('%s:%s', ApiPlatformParser::IN_PREFIX, Dummy::class), $actual[1]->getInput());
154-
$this->assertEquals(sprintf('%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class), $actual[1]->getOutput());
153+
$this->assertEquals(sprintf('%s:%s:%s', ApiPlatformParser::IN_PREFIX, Dummy::class, 'post'), $actual[1]->getInput());
154+
$this->assertEquals(sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'post'), $actual[1]->getOutput());
155155
$this->assertInstanceOf(Route::class, $actual[1]->getRoute());
156156
$this->assertEquals('/dummies', $actual[1]->getRoute()->getPath());
157157
$this->assertEquals(['POST'], $actual[1]->getRoute()->getMethods());
@@ -161,7 +161,7 @@ public function testGetAnnotations()
161161
$this->assertEquals('Retrieves Dummy resource.', $actual[2]->getDescription());
162162
$this->assertEquals('Dummy', $actual[2]->getResourceDescription());
163163
$this->assertEquals('Dummy', $actual[2]->getSection());
164-
$this->assertEquals(sprintf('%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class), $actual[2]->getOutput());
164+
$this->assertEquals(sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'get'), $actual[2]->getOutput());
165165
$this->assertInstanceOf(Route::class, $actual[2]->getRoute());
166166
$this->assertEquals('/dummies/{id}', $actual[2]->getRoute()->getPath());
167167
$this->assertEquals(['GET'], $actual[2]->getRoute()->getMethods());
@@ -171,8 +171,8 @@ public function testGetAnnotations()
171171
$this->assertEquals('Replaces the Dummy resource.', $actual[3]->getDescription());
172172
$this->assertEquals('Dummy', $actual[3]->getResourceDescription());
173173
$this->assertEquals('Dummy', $actual[3]->getSection());
174-
$this->assertEquals(sprintf('%s:%s', ApiPlatformParser::IN_PREFIX, Dummy::class), $actual[3]->getInput());
175-
$this->assertEquals(sprintf('%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class), $actual[3]->getOutput());
174+
$this->assertEquals(sprintf('%s:%s:%s', ApiPlatformParser::IN_PREFIX, Dummy::class, 'put'), $actual[3]->getInput());
175+
$this->assertEquals(sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'put'), $actual[3]->getOutput());
176176
$this->assertInstanceOf(Route::class, $actual[3]->getRoute());
177177
$this->assertEquals('/dummies/{id}', $actual[3]->getRoute()->getPath());
178178
$this->assertEquals(['PUT'], $actual[3]->getRoute()->getMethods());

tests/Bridge/NelmioApiDoc/Parser/ApiPlatformParserTest.php

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,63 @@ public function testSupports()
6767
]));
6868
}
6969

70+
public function testSupportsAttributeNormalization()
71+
{
72+
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
73+
$resourceMetadataFactoryProphecy->create(CustomAttributeDummy::class)->willReturn(new ResourceMetadata('dummy', 'dummy', null, [
74+
'get' => ['method' => 'GET', 'normalization_context' => ['groups' => ['custom_attr_dummy_get']]],
75+
'put' => ['method' => 'PUT', 'denormalization_context' => ['groups' => ['custom_attr_dummy_put']]],
76+
'delete' => ['method' => 'DELETE'],
77+
], []))->shouldBeCalled();
78+
$resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal();
79+
80+
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
81+
$propertyNameCollectionFactoryProphecy->create(CustomAttributeDummy::class, Argument::cetera())->willReturn(new PropertyNameCollection([
82+
'id',
83+
'name',
84+
]))->shouldBeCalled();
85+
$propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal();
86+
87+
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
88+
$idPropertyMetadata = (new PropertyMetadata())
89+
->withType(new Type(Type::BUILTIN_TYPE_INT, false))
90+
->withDescription('The id.')
91+
->withReadable(true)
92+
->withWritable(false)
93+
->withRequired(true);
94+
$propertyMetadataFactoryProphecy->create(CustomAttributeDummy::class, 'id')->willReturn($idPropertyMetadata)->shouldBeCalled();
95+
$namePropertyMetadata = (new PropertyMetadata())
96+
->withType(new Type(Type::BUILTIN_TYPE_STRING, false))
97+
->withDescription('The dummy name.')
98+
->withReadable(true)
99+
->withWritable(true)
100+
->withRequired(true);
101+
$propertyMetadataFactoryProphecy->create(CustomAttributeDummy::class, 'name')->willReturn($namePropertyMetadata)->shouldBeCalled();
102+
103+
$propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal();
104+
105+
$apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory);
106+
107+
$actual = $apiPlatformParser->parse([
108+
'class' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, CustomAttributeDummy::class, 'get'),
109+
]);
110+
111+
$this->assertEquals([
112+
'id' => [
113+
'dataType' => DataTypes::INTEGER,
114+
'required' => true,
115+
'description' => 'The id.',
116+
'readonly' => true,
117+
],
118+
'name' => [
119+
'dataType' => DataTypes::STRING,
120+
'required' => true,
121+
'description' => 'The dummy name.',
122+
'readonly' => false,
123+
],
124+
], $actual);
125+
}
126+
70127
public function testSupportsUnknownResource()
71128
{
72129
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
@@ -108,7 +165,12 @@ public function testSupportsUnsupportedClassFormat()
108165
public function testParse()
109166
{
110167
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
111-
$resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata())->shouldBeCalled();
168+
$resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata('dummy', 'dummy', null, [
169+
'get' => ['method' => 'GET', 'normalization_context' => ['groups' => ['custom_attr_dummy_get']]],
170+
'put' => ['method' => 'PUT', 'denormalization_context' => ['groups' => ['custom_attr_dummy_put']]],
171+
'gerard' => ['method' => 'get', 'path' => '/gerard', 'denormalization_context' => ['groups' => ['custom_attr_dummy_put']]],
172+
'delete' => ['method' => 'DELETE'],
173+
], []))->shouldBeCalled();
112174
$resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal();
113175

114176
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
@@ -146,7 +208,7 @@ public function testParse()
146208
$apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory);
147209

148210
$actual = $apiPlatformParser->parse([
149-
'class' => sprintf('%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class),
211+
'class' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'gerard'),
150212
]);
151213

152214
$this->assertEquals([
@@ -174,7 +236,7 @@ public function testParse()
174236
public function testParseDateTime()
175237
{
176238
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
177-
$resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata())->shouldBeCalled();
239+
$resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata('dummy', 'dummy', null, [], []))->shouldBeCalled();
178240
$resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal();
179241

180242
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
@@ -196,7 +258,7 @@ public function testParseDateTime()
196258
$apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory);
197259

198260
$actual = $apiPlatformParser->parse([
199-
'class' => sprintf('%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class),
261+
'class' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'get'),
200262
]);
201263

202264
$this->assertEquals([
@@ -213,7 +275,7 @@ public function testParseDateTime()
213275
public function testParseRelation()
214276
{
215277
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
216-
$resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata())->shouldBeCalled();
278+
$resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata('dummy', 'dummy', null, [], []))->shouldBeCalled();
217279
$resourceMetadataFactoryProphecy->create(RelatedDummy::class)->willReturn(new ResourceMetadata())->shouldBeCalled();
218280
$resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal();
219281

@@ -262,7 +324,7 @@ public function testParseRelation()
262324
$apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory);
263325

264326
$actual = $apiPlatformParser->parse([
265-
'class' => sprintf('%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class),
327+
'class' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'get'),
266328
]);
267329

268330
$this->assertEquals([

0 commit comments

Comments
 (0)