Add a Controller function to make it easy to return json

If the serializer component is enabled it is used to generate the json
data, if not the standard `json_encode`  function is used
This commit is contained in:
Fred Cox 2016-02-02 11:35:46 +02:00
parent 5c34ae4678
commit f904a2ba99
7 changed files with 163 additions and 25 deletions

View File

@ -1,6 +1,11 @@
CHANGELOG
=========
3.1.0
-----
* Added `Controller::json` to simplify creating JSON responses when using the Serializer component
3.0.0
-----

View File

@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
@ -97,6 +98,29 @@ abstract class Controller implements ContainerAwareInterface
return $this->redirect($this->generateUrl($route, $parameters), $status);
}
/**
* Returns a JsonResponse that uses the serializer component if enabled, or json_encode.
*
* @param mixed $data The response data
* @param int $status The status code to use for the Response
* @param array $headers Array of extra headers to add
* @param array $context Context to pass to serializer when using serializer component
*
* @return JsonResponse
*/
protected function json($data, $status = 200, $headers = array(), $context = array())
{
if ($this->container->has('serializer')) {
$json = $this->container->get('serializer')->serialize($data, 'json', array_merge(array(
'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS,
), $context));
return new JsonResponse($json, $status, $headers, true);
}
return new JsonResponse($data, $status, $headers);
}
/**
* Adds a flash message to the current session for type.
*

View File

@ -14,12 +14,14 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Serializer\SerializerInterface;
class ControllerTest extends TestCase
{
@ -124,6 +126,85 @@ class ControllerTest extends TestCase
return $container;
}
public function testJson()
{
$container = $this->getMock(ContainerInterface::class);
$container
->expects($this->once())
->method('has')
->with('serializer')
->will($this->returnValue(false));
$controller = new TestController();
$controller->setContainer($container);
$response = $controller->json(array());
$this->assertInstanceOf(JsonResponse::class, $response);
$this->assertEquals('[]', $response->getContent());
}
public function testJsonWithSerializer()
{
$container = $this->getMock(ContainerInterface::class);
$container
->expects($this->once())
->method('has')
->with('serializer')
->will($this->returnValue(true));
$serializer = $this->getMock(SerializerInterface::class);
$serializer
->expects($this->once())
->method('serialize')
->with(array(), 'json', array('json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS))
->will($this->returnValue('[]'));
$container
->expects($this->once())
->method('get')
->with('serializer')
->will($this->returnValue($serializer));
$controller = new TestController();
$controller->setContainer($container);
$response = $controller->json(array());
$this->assertInstanceOf(JsonResponse::class, $response);
$this->assertEquals('[]', $response->getContent());
}
public function testJsonWithSerializerContextOverride()
{
$container = $this->getMock(ContainerInterface::class);
$container
->expects($this->once())
->method('has')
->with('serializer')
->will($this->returnValue(true));
$serializer = $this->getMock(SerializerInterface::class);
$serializer
->expects($this->once())
->method('serialize')
->with(array(), 'json', array('json_encode_options' => 0, 'other' => 'context'))
->will($this->returnValue('[]'));
$container
->expects($this->once())
->method('get')
->with('serializer')
->will($this->returnValue($serializer));
$controller = new TestController();
$controller->setContainer($container);
$response = $controller->json(array(), 200, array(), array('json_encode_options' => 0, 'other' => 'context'));
$this->assertInstanceOf(JsonResponse::class, $response);
$this->assertEquals('[]', $response->getContent());
$response->setEncodingOptions(JSON_FORCE_OBJECT);
$this->assertEquals('{}', $response->getContent());
}
}
class TestController extends Controller
@ -137,4 +218,9 @@ class TestController extends Controller
{
return parent::getUser();
}
public function json($data, $status = 200, $headers = array(), $context = array())
{
return parent::json($data, $status, $headers, $context);
}
}

View File

@ -22,7 +22,7 @@
"symfony/dependency-injection": "~2.8|~3.0",
"symfony/config": "~2.8|~3.0",
"symfony/event-dispatcher": "~2.8|~3.0",
"symfony/http-foundation": "~2.8|~3.0",
"symfony/http-foundation": "~3.1",
"symfony/http-kernel": "~2.8|~3.0",
"symfony/polyfill-mbstring": "~1.0",
"symfony/filesystem": "~2.8|~3.0",

View File

@ -1,6 +1,11 @@
CHANGELOG
=========
3.1.0
-----
* Added support for creating `JsonResponse` with a string of JSON data
3.0.0
-----

View File

@ -29,16 +29,19 @@ class JsonResponse extends Response
// Encode <, >, ', &, and " for RFC4627-compliant JSON, which may also be embedded into HTML.
// 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
protected $encodingOptions = 15;
const DEFAULT_ENCODING_OPTIONS = 15;
protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS;
/**
* Constructor.
*
* @param mixed $data The response data
* @param int $status The response status code
* @param array $headers An array of response headers
* @param mixed $data The response data
* @param int $status The response status code
* @param array $headers An array of response headers
* @param bool $preEncoded If the data is already a JSON string
*/
public function __construct($data = null, $status = 200, $headers = array())
public function __construct($data = null, $status = 200, $headers = array(), $preEncoded = false)
{
parent::__construct('', $status, $headers);
@ -46,7 +49,7 @@ class JsonResponse extends Response
$data = new \ArrayObject();
}
$this->setData($data);
$this->setData($data, $preEncoded);
}
/**
@ -88,34 +91,37 @@ class JsonResponse extends Response
* Sets the data to be sent as JSON.
*
* @param mixed $data
* @param bool $preEncoded If the data is already a JSON string
*
* @return JsonResponse
*
* @throws \InvalidArgumentException
*/
public function setData($data = array())
public function setData($data = array(), $preEncoded = false)
{
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);
} else {
try {
// PHP 5.4 and up wrap exceptions thrown by JsonSerializable
// objects in a new exception that needs to be removed.
// Fortunately, PHP 5.5 and up do not trigger any warning anymore.
if (!$preEncoded) {
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);
} catch (\Exception $e) {
if ('Exception' === get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) {
throw $e->getPrevious() ?: $e;
} else {
try {
// PHP 5.4 and up wrap exceptions thrown by JsonSerializable
// objects in a new exception that needs to be removed.
// Fortunately, PHP 5.5 and up do not trigger any warning anymore.
$data = json_encode($data, $this->encodingOptions);
} catch (\Exception $e) {
if ('Exception' === get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) {
throw $e->getPrevious() ?: $e;
}
throw $e;
}
throw $e;
}
}
if (JSON_ERROR_NONE !== json_last_error()) {
throw new \InvalidArgumentException(json_last_error_msg());
if (JSON_ERROR_NONE !== json_last_error()) {
throw new \InvalidArgumentException(json_last_error_msg());
}
}
$this->data = $data;

View File

@ -75,6 +75,18 @@ class JsonResponseTest extends \PHPUnit_Framework_TestCase
$this->assertSame('application/vnd.acme.blog-v1+json', $response->headers->get('Content-Type'));
}
public function testConstructorWithPreEncoded()
{
$response = new JsonResponse('1', 200, array(), true);
$this->assertEquals('1', $response->getContent());
$response = new JsonResponse('[1]', 200, array(), true);
$this->assertEquals('[1]', $response->getContent());
$response = new JsonResponse('true', 200, array(), true);
$this->assertEquals('true', $response->getContent());
}
public function testCreate()
{
$response = JsonResponse::create(array('foo' => 'bar'), 204);