From 3574c3088dc9db0a96356d12db94ec93b76bcf24 Mon Sep 17 00:00:00 2001 From: Thijs Alberts Date: Mon, 16 Nov 2015 23:25:32 +0100 Subject: [PATCH 1/3] [HttpFoundation] JsonResponse.php Changed $this->data to an container for setting or adding scalar content to ResponseObject. $this->content is a string representation of $this->data. $this->update() is an private function to sync $this->data and $this->content. When modifying either $data or $content or by setting encoding options, the two are synced. --- .../Component/HttpFoundation/JsonResponse.php | 166 ++++++++++++------ .../HttpFoundation/Tests/JsonResponseTest.php | 25 +++ 2 files changed, 137 insertions(+), 54 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/JsonResponse.php b/src/Symfony/Component/HttpFoundation/JsonResponse.php index 0445a9a6ce11a..ba0a921c0a11a 100644 --- a/src/Symfony/Component/HttpFoundation/JsonResponse.php +++ b/src/Symfony/Component/HttpFoundation/JsonResponse.php @@ -24,7 +24,16 @@ */ class JsonResponse extends Response { + /** + * Associative representation of $content. + * + * @var array + */ protected $data; + + /** + * @var string + */ protected $callback; // Encode <, >, ', &, and " for RFC4627-compliant JSON, which may also be embedded into HTML. @@ -84,22 +93,115 @@ public function setCallback($callback = null) return $this->update(); } + /** + * Sets the content to be sent as JSON. + * + * @param string $content + * + * @return Response + * + * @throws \InvalidArgumentException + */ + public function setContent($content) + { + $data = json_decode($content); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + $this->data = $data; + + return $this->update(); + } + + /** + * {@inheritdoc} + */ + public function getContent() + { + if ($this->callback !== null) { + // Not using application/javascript for compatibility reasons with older browsers. + $this->headers->set('Content-Type', 'text/javascript'); + + return sprintf('/**/%s(%s);', $this->callback, $this->content); + } + + return parent::getContent(); + } + + /** + * Add content to existing data array. + * + * @param array $data + * + * @return JsonResponse + * + * @throws \Exception + */ + public function addData($data = array()) + { + $this->data = array_merge_recursive($this->data, (array) $data); + + return $this->update(); + } + /** * Sets the data to be sent as JSON. * * @param mixed $data * * @return JsonResponse - * - * @throws \InvalidArgumentException */ public function setData($data = array()) { + $this->data = $data; + + return $this->update(); + } + + /** + * Returns options used while encoding data to JSON. + * + * @return int + */ + public function getEncodingOptions() + { + return $this->encodingOptions; + } + + /** + * Sets options used while encoding data to JSON. + * + * @param int $encodingOptions + * + * @return JsonResponse + */ + public function setEncodingOptions($encodingOptions) + { + $this->encodingOptions = (int) $encodingOptions; + + return $this->update(); + } + + /** + * Updates the content and headers according to the JSON data and callback. + * + * @return JsonResponse + * + * @throws \Exception + */ + private function update() + { + // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) + // in order to not overwrite a custom definition. + if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/json'); + } + if (defined('HHVM_VERSION')) { // HHVM does not trigger any warnings and let exceptions // thrown from a JsonSerializable object pass through. // If only PHP did the same... - $data = json_encode($data, $this->encodingOptions); + $serializedData = json_encode($this->data, $this->encodingOptions); } else { try { if (PHP_VERSION_ID < 50400) { @@ -107,7 +209,7 @@ public function setData($data = array()) // types that can't be serialized as JSON (INF, resources, etc.) // but doesn't provide the JsonSerializable interface. set_error_handler('var_dump', 0); - $data = @json_encode($data, $this->encodingOptions); + $serializedData = @json_encode($this->data, $this->encodingOptions); } else { // PHP 5.4 and up wrap exceptions thrown by JsonSerializable // objects in a new exception that needs to be removed. @@ -121,10 +223,12 @@ public function setData($data = array()) if (JSON_ERROR_NONE === json_last_error()) { return $errorHandler && false !== call_user_func_array($errorHandler, func_get_args()); } + + return; }); } - $data = json_encode($data, $this->encodingOptions); + $serializedData = json_encode($this->data, $this->encodingOptions); } if (PHP_VERSION_ID < 50500) { @@ -145,55 +249,9 @@ public function setData($data = array()) throw new \InvalidArgumentException(json_last_error_msg()); } - $this->data = $data; - - return $this->update(); - } - - /** - * Returns options used while encoding data to JSON. - * - * @return int - */ - public function getEncodingOptions() - { - return $this->encodingOptions; - } - - /** - * Sets options used while encoding data to JSON. - * - * @param int $encodingOptions - * - * @return JsonResponse - */ - public function setEncodingOptions($encodingOptions) - { - $this->encodingOptions = (int) $encodingOptions; - - return $this->setData(json_decode($this->data)); - } - - /** - * Updates the content and headers according to the JSON data and callback. - * - * @return JsonResponse - */ - protected function update() - { - if (null !== $this->callback) { - // Not using application/javascript for compatibility reasons with older browsers. - $this->headers->set('Content-Type', 'text/javascript'); - - return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data)); - } - - // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) - // in order to not overwrite a custom definition. - if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) { - $this->headers->set('Content-Type', 'application/json'); - } + // update content with serialized data + $this->content = $serializedData; - return $this->setContent($this->data); + return $this; } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php index 60d27e4d5961c..e3f91d32a4b3d 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php @@ -185,6 +185,31 @@ public function testSetEncodingOptions() $this->assertEquals('{"0":{"0":1,"1":2,"2":3}}', $response->getContent()); } + public function testSettingContentWithoutSettingData() + { + $response = new JsonResponse(); + $this->assertEquals('{}', $response->getContent()); + } + + public function testContentAfterSettingDataAndEncodingOptions() + { + $response = new JsonResponse(); + $response->setData(array(array(1, 2, 3))); + $response->setContent('{"different":{"key":"value"}}'); + $response->setEncodingOptions(JSON_FORCE_OBJECT); + + $this->assertEquals('{"different":{"key":"value"}}', $response->getContent()); + } + + public function testAddDataToExistingData() + { + $response = new JsonResponse(); + $response->setData(array('some' => array('key1' => 'foo'))); + $response->addData(array('some' => array('key2' => 'bar'))); + + $this->assertEquals('{"some":{"key1":"foo","key2":"bar"}}', $response->getContent()); + } + /** * @expectedException \InvalidArgumentException */ From 1658eef8429cb7af5e484bddb8afcdc2a6410bef Mon Sep 17 00:00:00 2001 From: Thijs Alberts Date: Mon, 16 Nov 2015 23:25:32 +0100 Subject: [PATCH 2/3] [HttpFoundation] JsonResponse.php Changed $this->update() to sync this->data and $this->content --- src/Symfony/Component/HttpFoundation/JsonResponse.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/JsonResponse.php b/src/Symfony/Component/HttpFoundation/JsonResponse.php index ba0a921c0a11a..62670c1621fec 100644 --- a/src/Symfony/Component/HttpFoundation/JsonResponse.php +++ b/src/Symfony/Component/HttpFoundation/JsonResponse.php @@ -49,8 +49,7 @@ class JsonResponse extends Response */ public function __construct($data = null, $status = 200, $headers = array()) { - parent::__construct('', $status, $headers); - + parent::__construct('{}', $status, $headers); if (null === $data) { $data = new \ArrayObject(); } From 49fc4ae6108cc49d34168e41b8b8de8757c0bcd1 Mon Sep 17 00:00:00 2001 From: Thijs Alberts Date: Thu, 26 Nov 2015 18:16:48 +0100 Subject: [PATCH 3/3] [HttpFoundation] JsonResponse.php Updated JsonResponse after feedback --- .../Component/HttpFoundation/JsonResponse.php | 97 ++++++------------- .../HttpFoundation/Tests/JsonResponseTest.php | 11 +-- 2 files changed, 28 insertions(+), 80 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/JsonResponse.php b/src/Symfony/Component/HttpFoundation/JsonResponse.php index 62670c1621fec..3091a7aaf86d9 100644 --- a/src/Symfony/Component/HttpFoundation/JsonResponse.php +++ b/src/Symfony/Component/HttpFoundation/JsonResponse.php @@ -24,16 +24,7 @@ */ class JsonResponse extends Response { - /** - * Associative representation of $content. - * - * @var array - */ protected $data; - - /** - * @var string - */ protected $callback; // Encode <, >, ', &, and " for RFC4627-compliant JSON, which may also be embedded into HTML. @@ -50,6 +41,7 @@ class JsonResponse extends Response public function __construct($data = null, $status = 200, $headers = array()) { parent::__construct('{}', $status, $headers); + if (null === $data) { $data = new \ArrayObject(); } @@ -92,57 +84,6 @@ public function setCallback($callback = null) return $this->update(); } - /** - * Sets the content to be sent as JSON. - * - * @param string $content - * - * @return Response - * - * @throws \InvalidArgumentException - */ - public function setContent($content) - { - $data = json_decode($content); - if (JSON_ERROR_NONE !== json_last_error()) { - throw new \InvalidArgumentException(json_last_error_msg()); - } - $this->data = $data; - - return $this->update(); - } - - /** - * {@inheritdoc} - */ - public function getContent() - { - if ($this->callback !== null) { - // Not using application/javascript for compatibility reasons with older browsers. - $this->headers->set('Content-Type', 'text/javascript'); - - return sprintf('/**/%s(%s);', $this->callback, $this->content); - } - - return parent::getContent(); - } - - /** - * Add content to existing data array. - * - * @param array $data - * - * @return JsonResponse - * - * @throws \Exception - */ - public function addData($data = array()) - { - $this->data = array_merge_recursive($this->data, (array) $data); - - return $this->update(); - } - /** * Sets the data to be sent as JSON. * @@ -188,14 +129,8 @@ public function setEncodingOptions($encodingOptions) * * @throws \Exception */ - private function update() + protected function update() { - // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) - // in order to not overwrite a custom definition. - if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) { - $this->headers->set('Content-Type', 'application/json'); - } - if (defined('HHVM_VERSION')) { // HHVM does not trigger any warnings and let exceptions // thrown from a JsonSerializable object pass through. @@ -248,9 +183,31 @@ private function update() throw new \InvalidArgumentException(json_last_error_msg()); } - // update content with serialized data - $this->content = $serializedData; + if (null !== $this->callback) { + // Not using application/javascript for compatibility reasons with older browsers. + $this->headers->set('Content-Type', 'text/javascript'); + + return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $serializedData)); + } + + // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) + // in order to not overwrite a custom definition. + if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/json'); + } + + return $this->setContent($serializedData); + } + + /** + * @param mixed $content + * + * @return Response + */ + public function setContent($content) + { + $this->data = json_decode($content); - return $this; + return parent::setContent($content); } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php index e3f91d32a4b3d..9575cedb7bf0e 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php @@ -196,20 +196,11 @@ public function testContentAfterSettingDataAndEncodingOptions() $response = new JsonResponse(); $response->setData(array(array(1, 2, 3))); $response->setContent('{"different":{"key":"value"}}'); - $response->setEncodingOptions(JSON_FORCE_OBJECT); + $response->setEncodingOptions(JSON_OBJECT_AS_ARRAY); $this->assertEquals('{"different":{"key":"value"}}', $response->getContent()); } - public function testAddDataToExistingData() - { - $response = new JsonResponse(); - $response->setData(array('some' => array('key1' => 'foo'))); - $response->addData(array('some' => array('key2' => 'bar'))); - - $this->assertEquals('{"some":{"key1":"foo","key2":"bar"}}', $response->getContent()); - } - /** * @expectedException \InvalidArgumentException */