Skip to content

Commit 33ad6d4

Browse files
committed
Allow to disable input and output class
1 parent 6ca30b6 commit 33ad6d4

File tree

7 files changed

+98
-25
lines changed

7 files changed

+98
-25
lines changed

src/Annotation/ApiResource.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@
3737
* @Attribute("filters", type="string[]"),
3838
* @Attribute("graphql", type="array"),
3939
* @Attribute("hydraContext", type="array"),
40-
* @Attribute("inputClass", type="string"),
40+
* @Attribute("inputClass", type="mixed"),
4141
* @Attribute("iri", type="string"),
4242
* @Attribute("itemOperations", type="array"),
4343
* @Attribute("maximumItemsPerPage", type="int"),
4444
* @Attribute("mercure", type="mixed"),
4545
* @Attribute("normalizationContext", type="array"),
4646
* @Attribute("order", type="array"),
47-
* @Attribute("outputClass", type="string"),
47+
* @Attribute("outputClass", type="mixed"),
4848
* @Attribute("paginationClientEnabled", type="bool"),
4949
* @Attribute("paginationClientItemsPerPage", type="bool"),
5050
* @Attribute("paginationClientPartial", type="bool"),
@@ -277,14 +277,14 @@ final class ApiResource
277277
/**
278278
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
279279
*
280-
* @var string
280+
* @var string|false
281281
*/
282282
private $inputClass;
283283

284284
/**
285285
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
286286
*
287-
* @var string
287+
* @var string|false
288288
*/
289289
private $outputClass;
290290

src/EventListener/DeserializeListener.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public function onKernelRequest(GetResponseEvent $event)
6666
$request->isMethodSafe(false)
6767
|| 'DELETE' === $method
6868
|| !($attributes = RequestAttributesExtractor::extractAttributes($request))
69+
|| false === ($attributes['input_class'] ?? null)
6970
|| !$attributes['receive']
7071
|| (
7172
'' === ($requestContent = $request->getContent())

src/EventListener/SerializeListener.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,21 +57,30 @@ public function onKernelView(GetResponseForControllerResultEvent $event)
5757
return;
5858
}
5959

60+
$request->attributes->set('_api_respond', true);
6061
$context = $this->serializerContextBuilder->createFromRequest($request, true, $attributes);
62+
63+
if (isset($context['output_class'])) {
64+
if (false === $context['output_class']) {
65+
// If the output class is explicitly set to false, the response must be empty
66+
$event->setControllerResult('');
67+
68+
return;
69+
}
70+
71+
$context['resource_class'] = $context['output_class'];
72+
}
73+
6174
if ($included = $request->attributes->get('_api_included')) {
6275
$context['api_included'] = $included;
6376
}
6477
$resources = new ResourceList();
6578
$context['resources'] = &$resources;
66-
if (isset($context['output_class'])) {
67-
$context['resource_class'] = $context['output_class'];
68-
}
6979

7080
$request->attributes->set('_api_normalization_context', $context);
7181

7282
$event->setControllerResult($this->serializer->serialize($controllerResult, $request->getRequestFormat(), $context));
7383

74-
$request->attributes->set('_api_respond', true);
7584
$request->attributes->set('_resources', $request->attributes->get('_resources', []) + (array) $resources);
7685
}
7786

src/EventListener/WriteListener.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use ApiPlatform\Core\Api\IriConverterInterface;
1717
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
18+
use ApiPlatform\Core\Util\RequestAttributesExtractor;
1819
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
1920

2021
/**
@@ -40,7 +41,7 @@ public function __construct(DataPersisterInterface $dataPersister, IriConverterI
4041
public function onKernelView(GetResponseForControllerResultEvent $event)
4142
{
4243
$request = $event->getRequest();
43-
if ($request->isMethodSafe(false) || !$request->attributes->has('_api_resource_class') || !$request->attributes->getBoolean('_api_persist', true)) {
44+
if ($request->isMethodSafe(false) || !$request->attributes->getBoolean('_api_persist', true) || !$attributes = RequestAttributesExtractor::extractAttributes($request)) {
4445
return;
4546
}
4647

@@ -64,7 +65,7 @@ public function onKernelView(GetResponseForControllerResultEvent $event)
6465
// Controller result must be immutable for _api_write_item_iri
6566
// if it's class changed compared to the base class let's avoid calling the IriConverter
6667
// especially that the Output class could be a DTO that's not referencing any route
67-
if (null !== $this->iriConverter && \get_class($controllerResult) === \get_class($event->getControllerResult())) {
68+
if (null !== $this->iriConverter && (false !== $attributes['output_class'] ?? null) && \get_class($controllerResult) === \get_class($event->getControllerResult())) {
6869
$request->attributes->set('_api_write_item_iri', $this->iriConverter->getIriFromItem($controllerResult));
6970
}
7071
break;

tests/EventListener/DeserializeListenerTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,27 @@ public function testDoNotCallWhenReceiveFlagIsFalse()
124124
$listener->onKernelRequest($eventProphecy->reveal());
125125
}
126126

127+
public function testDoNotCallWhenInputClassDisabled()
128+
{
129+
$eventProphecy = $this->prophesize(GetResponseEvent::class);
130+
131+
$request = new Request([], [], ['data' => new \stdClass(), '_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'post', '_api_input_class' => false]);
132+
$request->setMethod('POST');
133+
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
134+
135+
$serializerProphecy = $this->prophesize(SerializerInterface::class);
136+
$serializerProphecy->deserialize()->shouldNotBeCalled();
137+
138+
$serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class);
139+
$serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), false, Argument::type('array'))->shouldNotBeCalled();
140+
141+
$formatsProviderProphecy = $this->prophesize(FormatsProviderInterface::class);
142+
$formatsProviderProphecy->getFormatsFromAttributes(Argument::type('array'))->shouldNotBeCalled();
143+
144+
$listener = new DeserializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal(), $formatsProviderProphecy->reveal());
145+
$listener->onKernelRequest($eventProphecy->reveal());
146+
}
147+
127148
/**
128149
* @dataProvider methodProvider
129150
*/

tests/EventListener/SerializeListenerTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,26 @@ public function testSerializeCollectionOperation()
138138
$listener->onKernelView($eventProphecy->reveal());
139139
}
140140

141+
public function testSerializeCollectionOperationWithOutputClassDisabled()
142+
{
143+
$expectedContext = ['request_uri' => '', 'resource_class' => 'Foo', 'collection_operation_name' => 'post', 'output_class' => false];
144+
$serializerProphecy = $this->prophesize(SerializerInterface::class);
145+
146+
$request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'get', '_api_output_class' => false]);
147+
$request->setRequestFormat('xml');
148+
149+
$eventProphecy = $this->prophesize(GetResponseForControllerResultEvent::class);
150+
$eventProphecy->getControllerResult()->willReturn(new \stdClass())->shouldBeCalled();
151+
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
152+
$eventProphecy->setControllerResult('')->shouldBeCalled();
153+
154+
$serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class);
155+
$serializerContextBuilderProphecy->createFromRequest(Argument::type(Request::class), true, Argument::type('array'))->willReturn($expectedContext)->shouldBeCalled();
156+
157+
$listener = new SerializeListener($serializerProphecy->reveal(), $serializerContextBuilderProphecy->reveal());
158+
$listener->onKernelView($eventProphecy->reveal());
159+
}
160+
141161
public function testSerializeItemOperation()
142162
{
143163
$expectedContext = ['request_uri' => '', 'resource_class' => 'Foo', 'item_operation_name' => 'get'];

tests/EventListener/WriteListenerTest.php

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ public function testOnKernelViewWithControllerResultAndPersist()
3939
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
4040
$iriConverterProphecy->getIriFromItem($dummy)->willReturn('/dummy/1')->shouldBeCalled();
4141

42-
$request = new Request();
43-
$request->attributes->set('_api_resource_class', Dummy::class);
42+
$request = new Request([], [], ['_api_resource_class' => Dummy::class]);
4443

4544
$event = new GetResponseForControllerResultEvent(
4645
$this->prophesize(HttpKernelInterface::class)->reveal(),
@@ -51,6 +50,7 @@ public function testOnKernelViewWithControllerResultAndPersist()
5150

5251
foreach (['PATCH', 'PUT', 'POST'] as $httpMethod) {
5352
$request->setMethod($httpMethod);
53+
$request->attributes->set(sprintf('_api_%s_operation_name', 'POST' === $httpMethod ? 'collection' : 'item'), strtolower($httpMethod));
5454

5555
(new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event);
5656
$this->assertSame($dummy, $event->getControllerResult());
@@ -71,8 +71,7 @@ public function testOnKernelViewWithControllerResultAndPersistReturningVoid()
7171
$dataPersisterProphecy->supports($dummy)->willReturn(true)->shouldBeCalled();
7272
$dataPersisterProphecy->persist($dummy)->shouldBeCalled();
7373

74-
$request = new Request();
75-
$request->attributes->set('_api_resource_class', Dummy::class);
74+
$request = new Request([], [], ['_api_resource_class' => Dummy::class]);
7675

7776
$event = new GetResponseForControllerResultEvent(
7877
$this->prophesize(HttpKernelInterface::class)->reveal(),
@@ -83,6 +82,7 @@ public function testOnKernelViewWithControllerResultAndPersistReturningVoid()
8382

8483
foreach (['PATCH', 'PUT', 'POST'] as $httpMethod) {
8584
$request->setMethod($httpMethod);
85+
$request->attributes->set(sprintf('_api_%s_operation_name', 'POST' === $httpMethod ? 'collection' : 'item'), strtolower($httpMethod));
8686

8787
(new WriteListener($dataPersisterProphecy->reveal()))->onKernelView($event);
8888
$this->assertSame($dummy, $event->getControllerResult());
@@ -112,8 +112,7 @@ public function testOnKernelViewWithControllerResultAndPersistWithImmutableResou
112112
->shouldBeCalled()
113113
;
114114

115-
$request = new Request();
116-
$request->attributes->set('_api_resource_class', Dummy::class);
115+
$request = new Request([], [], ['_api_resource_class' => Dummy::class]);
117116

118117
foreach (['PATCH', 'PUT', 'POST'] as $httpMethod) {
119118
$event = new GetResponseForControllerResultEvent(
@@ -124,6 +123,7 @@ public function testOnKernelViewWithControllerResultAndPersistWithImmutableResou
124123
);
125124

126125
$request->setMethod($httpMethod);
126+
$request->attributes->set(sprintf('_api_%s_operation_name', 'POST' === $httpMethod ? 'collection' : 'item'), strtolower($httpMethod));
127127

128128
(new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event);
129129

@@ -132,6 +132,32 @@ public function testOnKernelViewWithControllerResultAndPersistWithImmutableResou
132132
}
133133
}
134134

135+
public function testOnKernelViewDoNotCallIriConverterWhenOutputClassDisabled()
136+
{
137+
$dummy = new Dummy();
138+
$dummy->setName('Dummyrino');
139+
140+
$dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class);
141+
$dataPersisterProphecy->supports($dummy)->willReturn(true)->shouldBeCalled();
142+
143+
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
144+
$iriConverterProphecy->getIriFromItem($dummy)->shouldNotBeCalled();
145+
146+
$dataPersisterProphecy->persist($dummy)->willReturn($dummy)->shouldBeCalled();
147+
148+
$request = new Request([], [], ['_api_resource_class' => Dummy::class, '_api_collection_operation_name' => 'post', '_api_output_class' => false]);
149+
$request->setMethod('POST');
150+
151+
$event = new GetResponseForControllerResultEvent(
152+
$this->prophesize(HttpKernelInterface::class)->reveal(),
153+
$request,
154+
HttpKernelInterface::MASTER_REQUEST,
155+
$dummy
156+
);
157+
158+
(new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event);
159+
}
160+
135161
public function testOnKernelViewWithControllerResultAndRemove()
136162
{
137163
$dummy = new Dummy();
@@ -144,9 +170,8 @@ public function testOnKernelViewWithControllerResultAndRemove()
144170
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
145171
$iriConverterProphecy->getIriFromItem($dummy)->shouldNotBeCalled();
146172

147-
$request = new Request();
173+
$request = new Request([], [], ['_api_resource_class' => Dummy::class, '_api_item_operation_name' => 'delete']);
148174
$request->setMethod('DELETE');
149-
$request->attributes->set('_api_resource_class', Dummy::class);
150175

151176
$event = new GetResponseForControllerResultEvent(
152177
$this->prophesize(HttpKernelInterface::class)->reveal(),
@@ -171,9 +196,8 @@ public function testOnKernelViewWithSafeMethod()
171196
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
172197
$iriConverterProphecy->getIriFromItem($dummy)->shouldNotBeCalled();
173198

174-
$request = new Request();
199+
$request = new Request([], [], ['_api_resource_class' => Dummy::class, '_api_item_operation_name' => 'head']);
175200
$request->setMethod('HEAD');
176-
$request->attributes->set('_api_resource_class', Dummy::class);
177201

178202
$event = new GetResponseForControllerResultEvent(
179203
$this->prophesize(HttpKernelInterface::class)->reveal(),
@@ -198,10 +222,8 @@ public function testOnKernelViewWithPersistFlagOff()
198222
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
199223
$iriConverterProphecy->getIriFromItem($dummy)->shouldNotBeCalled();
200224

201-
$request = new Request();
225+
$request = new Request([], [], ['_api_resource_class' => Dummy::class, '_api_item_operation_name' => 'head', '_api_persist' => false]);
202226
$request->setMethod('HEAD');
203-
$request->attributes->set('_api_resource_class', Dummy::class);
204-
$request->attributes->set('_api_persist', false);
205227

206228
$event = new GetResponseForControllerResultEvent(
207229
$this->prophesize(HttpKernelInterface::class)->reveal(),
@@ -252,9 +274,8 @@ public function testOnKernelViewWithNoDataPersisterSupport()
252274
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
253275
$iriConverterProphecy->getIriFromItem($dummy)->shouldNotBeCalled();
254276

255-
$request = new Request();
277+
$request = new Request([], [], ['_api_resource_class' => 'Dummy', '_api_collection_operation_name' => 'post']);
256278
$request->setMethod('POST');
257-
$request->attributes->set('_api_resource_class', 'Dummy');
258279

259280
$event = new GetResponseForControllerResultEvent(
260281
$this->prophesize(HttpKernelInterface::class)->reveal(),

0 commit comments

Comments
 (0)