Skip to content

Commit fbb3c6d

Browse files
committed
Merge pull request #903 from florianv/normalizer-fix
Added an option to normalize form data
2 parents 2fb6d70 + dbae326 commit fbb3c6d

File tree

6 files changed

+139
-54
lines changed

6 files changed

+139
-54
lines changed

DependencyInjection/Configuration.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,16 @@ private function addBodyListenerSection(ArrayNodeDefinition $rootNode)
177177
->defaultValue(array('json' => 'fos_rest.decoder.json', 'xml' => 'fos_rest.decoder.xml'))
178178
->prototype('scalar')->end()
179179
->end()
180-
->scalarNode('array_normalizer')->defaultNull()->end()
180+
->arrayNode('array_normalizer')
181+
->addDefaultsIfNotSet()
182+
->beforeNormalization()
183+
->ifString()->then(function($v) { return array('service' => $v); })
184+
->end()
185+
->children()
186+
->scalarNode('service')->defaultNull()->end()
187+
->booleanNode('forms')->defaultFalse()->end()
188+
->end()
189+
->end()
181190
->end()
182191
->end()
183192
->end();

DependencyInjection/FOSRestExtension.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,11 @@ public function load(array $configs, ContainerBuilder $container)
152152
$container->setParameter($this->getAlias().'.decoders', $config['body_listener']['decoders']);
153153

154154
$arrayNormalizer = $config['body_listener']['array_normalizer'];
155-
if (null !== $arrayNormalizer) {
156-
$container->getDefinition($this->getAlias().'.body_listener')
157-
->addMethodCall('setArrayNormalizer', array(new Reference($arrayNormalizer)));
155+
156+
if (null !== $arrayNormalizer['service']) {
157+
$bodyListener = $container->getDefinition($this->getAlias().'.body_listener');
158+
$bodyListener->addArgument(new Reference($arrayNormalizer['service']));
159+
$bodyListener->addArgument($arrayNormalizer['forms']);
158160
}
159161
}
160162

EventListener/BodyListener.php

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,28 +29,35 @@ class BodyListener
2929
private $decoderProvider;
3030
private $throwExceptionOnUnsupportedContentType;
3131
private $defaultFormat;
32-
33-
/**
34-
* @var ArrayNormalizerInterface
35-
*/
3632
private $arrayNormalizer;
33+
private $normalizeForms;
3734

3835
/**
3936
* Constructor.
4037
*
4138
* @param DecoderProviderInterface $decoderProvider
4239
* @param bool $throwExceptionOnUnsupportedContentType
40+
* @param ArrayNormalizerInterface $arrayNormalizer
41+
* @param bool $normalizeForms
4342
*/
44-
public function __construct(DecoderProviderInterface $decoderProvider, $throwExceptionOnUnsupportedContentType = false)
45-
{
43+
public function __construct(
44+
DecoderProviderInterface $decoderProvider,
45+
$throwExceptionOnUnsupportedContentType = false,
46+
ArrayNormalizerInterface $arrayNormalizer = null,
47+
$normalizeForms = false
48+
) {
4649
$this->decoderProvider = $decoderProvider;
4750
$this->throwExceptionOnUnsupportedContentType = $throwExceptionOnUnsupportedContentType;
51+
$this->arrayNormalizer = $arrayNormalizer;
52+
$this->normalizeForms = $normalizeForms;
4853
}
4954

5055
/**
5156
* Sets the array normalizer.
5257
*
5358
* @param ArrayNormalizerInterface $arrayNormalizer
59+
*
60+
* @deprecated To be removed in FOSRestBundle 2.0.0 (constructor injection is used instead).
5461
*/
5562
public function setArrayNormalizer(ArrayNormalizerInterface $arrayNormalizer)
5663
{
@@ -79,12 +86,11 @@ public function onKernelRequest(GetResponseEvent $event)
7986
{
8087
$request = $event->getRequest();
8188
$method = $request->getMethod();
89+
$contentType = $request->headers->get('Content-Type');
90+
$isFormPostRequest = in_array($contentType, array('multipart/form-data', 'application/x-www-form-urlencoded'), true) && 'POST' === $method;
91+
$normalizeRequest = $this->normalizeForms && $isFormPostRequest;
8292

83-
if (!count($request->request->all())
84-
&& in_array($method, array('POST', 'PUT', 'PATCH', 'DELETE'))
85-
) {
86-
$contentType = $request->headers->get('Content-Type');
87-
93+
if (!$isFormPostRequest && in_array($method, array('POST', 'PUT', 'PATCH', 'DELETE'))) {
8894
$format = null === $contentType
8995
? $request->getRequestFormat()
9096
: $request->getFormat($contentType);
@@ -108,19 +114,25 @@ public function onKernelRequest(GetResponseEvent $event)
108114
$decoder = $this->decoderProvider->getDecoder($format);
109115
$data = $decoder->decode($content);
110116
if (is_array($data)) {
111-
if (null !== $this->arrayNormalizer) {
112-
try {
113-
$data = $this->arrayNormalizer->normalize($data);
114-
} catch (NormalizationException $e) {
115-
throw new BadRequestHttpException($e->getMessage());
116-
}
117-
}
118117
$request->request = new ParameterBag($data);
118+
$normalizeRequest = true;
119119
} else {
120120
throw new BadRequestHttpException('Invalid '.$format.' message received');
121121
}
122122
}
123123
}
124+
125+
if (null !== $this->arrayNormalizer && $normalizeRequest) {
126+
$data = $request->request->all();
127+
128+
try {
129+
$data = $this->arrayNormalizer->normalize($data);
130+
} catch (NormalizationException $e) {
131+
throw new BadRequestHttpException($e->getMessage());
132+
}
133+
134+
$request->request = new ParameterBag($data);
135+
}
124136
}
125137

126138
private function isNotAnEmptyDeleteRequestWithNoSetContentType($method, $content, $contentType)

Resources/doc/3-listener-support.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,18 @@ You can also create your own array normalizer by implementing the
255255
body_listener:
256256
array_normalizer: acme.normalizer.custom
257257
258+
By default, the array normalizer is only applied to requests with a decodable format.
259+
If you want form data to be normalized, you can use the ``forms`` flag:
260+
261+
.. code-block:: yaml
262+
263+
# app/config/config.yml
264+
fos_rest:
265+
body_listener:
266+
array_normalizer:
267+
service: fos_rest.normalizer.camel_keys
268+
forms: true
269+
258270
Request Body Converter Listener
259271
-------------------------------
260272

Tests/DependencyInjection/FOSRestExtensionTest.php

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -89,23 +89,65 @@ public function testLoadBodyListenerWithDefaults()
8989
$this->assertTrue($this->container->hasDefinition('fos_rest.body_listener'));
9090
$this->assertParameter($decoders, 'fos_rest.decoders');
9191
$this->assertParameter(false, 'fos_rest.throw_exception_on_unsupported_content_type');
92-
$this->assertFalse($this->container->getDefinition('fos_rest.body_listener')->hasMethodCall('setArrayNormalizer'));
92+
$this->assertCount(2, $this->container->getDefinition('fos_rest.body_listener')->getArguments());
9393
}
9494

95-
public function testLoadBodyListenerWithNormalizer()
95+
public function testLoadBodyListenerWithNormalizerString()
9696
{
9797
$config = array(
9898
'fos_rest' => array('body_listener' => array(
9999
'array_normalizer' => 'fos_rest.normalizer.camel_keys',
100100
)),
101101
);
102+
103+
$this->extension->load($config, $this->container);
104+
$normalizerArgument = $this->container->getDefinition('fos_rest.body_listener')->getArgument(2);
105+
106+
$this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $normalizerArgument);
107+
$this->assertEquals('fos_rest.normalizer.camel_keys', (string) $normalizerArgument);
108+
}
109+
110+
public function testLoadBodyListenerWithNormalizerArray()
111+
{
112+
$config = array(
113+
'fos_rest' => array('body_listener' => array(
114+
'array_normalizer' => array(
115+
'service' => 'fos_rest.normalizer.camel_keys',
116+
)
117+
)),
118+
);
119+
102120
$this->extension->load($config, $this->container);
121+
$bodyListener = $this->container->getDefinition('fos_rest.body_listener');
122+
$normalizerArgument = $bodyListener->getArgument(2);
123+
$normalizeForms = $bodyListener->getArgument(3);
103124

104-
$this->assertServiceHasMethodCall(
105-
'fos_rest.body_listener',
106-
'setArrayNormalizer',
107-
array(new Reference('fos_rest.normalizer.camel_keys'))
125+
$this->assertCount(4, $bodyListener->getArguments());
126+
$this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $normalizerArgument);
127+
$this->assertEquals('fos_rest.normalizer.camel_keys', (string) $normalizerArgument);
128+
$this->assertEquals(false, $normalizeForms);
129+
}
130+
131+
public function testLoadBodyListenerWithNormalizerArrayAndForms()
132+
{
133+
$config = array(
134+
'fos_rest' => array('body_listener' => array(
135+
'array_normalizer' => array(
136+
'service' => 'fos_rest.normalizer.camel_keys',
137+
'forms' => true,
138+
)
139+
)),
108140
);
141+
142+
$this->extension->load($config, $this->container);
143+
$bodyListener = $this->container->getDefinition('fos_rest.body_listener');
144+
$normalizerArgument = $bodyListener->getArgument(2);
145+
$normalizeForms = $bodyListener->getArgument(3);
146+
147+
$this->assertCount(4, $bodyListener->getArguments());
148+
$this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $normalizerArgument);
149+
$this->assertEquals('fos_rest.normalizer.camel_keys', (string) $normalizerArgument);
150+
$this->assertEquals(true, $normalizeForms);
109151
}
110152

111153
public function testDisableFormatListener()
@@ -534,25 +576,4 @@ public function testExceptionThrownIfCallbackFilterIsUsed()
534576
{
535577
$this->extension->load(array('fos_rest' => array('view' => array('jsonp_handler' => array('callback_filter' => 'foo')))), $this->container);
536578
}
537-
538-
/**
539-
* Asserts the service definition has the method call with the specified arguments.
540-
*
541-
* @param string $serviceId The service id
542-
* @param string $methodName The name of the method
543-
* @param array $arguments The arguments of the method
544-
*/
545-
private function assertServiceHasMethodCall($serviceId, $methodName, array $arguments = array())
546-
{
547-
$message = sprintf('The service "%s" has the method call "%s"', $serviceId, $methodName);
548-
foreach ($this->container->getDefinition($serviceId)->getMethodCalls() as $methodCall) {
549-
if ($methodCall[0] === $methodName) {
550-
$this->assertEquals($arguments, $methodCall[1], $message);
551-
552-
return;
553-
}
554-
}
555-
556-
$this->assertTrue(false, $message);
557-
}
558579
}

Tests/EventListener/BodyListenerTest.php

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public static function testOnKernelRequestDataProvider()
8383
'Empty PATCH request' => array(true, new Request(array(), array(), array(), array(), array(), array(), array('foo')), 'PATCH', array('foo'), 'application/json'),
8484
'Empty DELETE request' => array(true, new Request(array(), array(), array(), array(), array(), array(), array('foo')), 'DELETE', array('foo'), 'application/json'),
8585
'Empty GET request' => array(false, new Request(array(), array(), array(), array(), array(), array(), array('foo')), 'GET', array(), 'application/json'),
86-
'POST request with parameters' => array(false, new Request(array(), array('bar'), array(), array(), array(), array(), array('foo')), 'POST', array('bar'), 'application/json'),
86+
'POST request with parameters' => array(false, new Request(array(), array('bar'), array(), array(), array(), array('CONTENT_TYPE' => 'application/x-www-form-urlencoded'), array('foo')), 'POST', array('bar'), 'application/x-www-form-urlencoded'),
8787
'POST request with unallowed format' => array(false, new Request(array(), array(), array(), array(), array(), array(), array('foo')), 'POST', array(), 'application/fooformat'),
8888
'POST request with no Content-Type' => array(true, new Request(array(), array(), array('_format' => 'json'), array(), array(), array(), array('foo')), 'POST', array('foo')),
8989
);
@@ -129,8 +129,38 @@ public function testOnKernelRequestWithNormalizer()
129129
->method('getRequest')
130130
->will($this->returnValue($request));
131131

132-
$listener = new BodyListener($decoderProvider, false);
133-
$listener->setArrayNormalizer($normalizer);
132+
$listener = new BodyListener($decoderProvider, false, $normalizer);
133+
$listener->onKernelRequest($event);
134+
135+
$this->assertEquals($normalizedData, $request->request->all());
136+
}
137+
138+
public function testOnKernelRequestNormalizationWithForms()
139+
{
140+
$data = array('foo_bar' => 'foo_bar');
141+
$normalizedData = array('fooBar' => 'foo_bar');
142+
$decoderProvider = $this->getMock('FOS\RestBundle\Decoder\DecoderProviderInterface');
143+
144+
$normalizer = $this->getMock('FOS\RestBundle\Normalizer\ArrayNormalizerInterface');
145+
$normalizer
146+
->expects($this->once())
147+
->method('normalize')
148+
->with($data)
149+
->will($this->returnValue($normalizedData));
150+
151+
$request = new Request(array(), $data, array(), array(), array(), array(), 'foo');
152+
$request->headers->set('Content-Type', 'multipart/form-data');
153+
$request->setMethod('POST');
154+
155+
$event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')
156+
->disableOriginalConstructor()
157+
->getMock();
158+
159+
$event->expects($this->once())
160+
->method('getRequest')
161+
->will($this->returnValue($request));
162+
163+
$listener = new BodyListener($decoderProvider, false, $normalizer, true);
134164
$listener->onKernelRequest($event);
135165

136166
$this->assertEquals($normalizedData, $request->request->all());
@@ -175,8 +205,7 @@ public function testOnKernelRequestNormalizationException()
175205
->method('getRequest')
176206
->will($this->returnValue($request));
177207

178-
$listener = new BodyListener($decoderProvider, false);
179-
$listener->setArrayNormalizer($normalizer);
208+
$listener = new BodyListener($decoderProvider, false, $normalizer);
180209
$listener->onKernelRequest($event);
181210
}
182211

0 commit comments

Comments
 (0)