19/07/2015 - SYMFONY
This abstract controller is used to validate the request content type and the request payload itself. It accepts json or xml requests and responses with json. For more information about how whole process is done, please check other examples in other posts.
Install "jms/serializer-bundle": "0.13.0"
with composer.
services:
application_backend.controller.abstract:
class: Application\BackendBundle\Controller\AbstractController
abstract: true
arguments:
- @serializer
- @validator
- @doctrine_common_inflector
application_backend.controller.api:
class: Application\BackendBundle\Controller\ApiController
parent: application_backend.controller.abstract
doctrine_common_inflector:
class: Doctrine\Common\Inflector\Inflector
namespace Application\BackendBundle\Controller;
use Doctrine\Common\Inflector\Inflector;
use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
abstract class AbstractController
{
private $validContentTypes = ['application/json' => 'json', 'application/xml' => 'xml'];
protected $serializer;
protected $validator;
protected $inflector;
public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator,
Inflector $inflector
) {
$this->serializer = $serializer;
$this->validator = $validator;
$this->inflector = $inflector;
}
/**
* @param string $contentType
*
* @return string|Response
*/
protected function validateContentType($contentType)
{
if (!isset($this->validContentTypes[$contentType])) {
return $this->createErrorResponse(
['content_type' => sprintf('Invalid content type %s.', $contentType)],
415
);
}
return $this->validContentTypes[$contentType];
}
/**
* @param string $payload
* @param string $model
* @param string $format
*
* @return object|Response
*/
protected function validatePayload($payload, $model, $format)
{
$payload = $this->serializer->deserialize($payload, $model, $format);
$errors = $this->validator->validate($payload);
if (count($errors)) {
return $this->createErrorResponse($errors, 400);
}
return $payload;
}
/**
* @param array|object $content
* @param int $status
*
* @return Response
*/
protected function createJsonResponse($content, $status = 200)
{
$context = new SerializationContext();
$context->setSerializeNull(false);
$response = $this->serializer->serialize($content, 'json', $context);
return new Response($response, $status, ['Content-Type' => 'application/json']);
}
/**
* @param array|ConstraintViolationListInterface $errors
* @param int $status
*
* @return Response
*/
private function createErrorResponse($errors, $status)
{
$errorList = null;
if ($errors instanceof ConstraintViolationList) {
foreach ($errors as $error) {
$error = $this->getErrorFromValidation($error);
$errorList[$error['key']] = $error['value'];
}
} else {
$errorList = $errors;
}
return $this->createJsonResponse(['errors' => $errorList], $status);
}
/**
* @param ConstraintViolationInterface $error
*
* @return array
*/
private function getErrorFromValidation($error)
{
$properties = $this->inflector->tableize($error->getPropertyPath());
return ['key' => $properties, 'value' => $error->getMessage()];
}
}
namespace Application\BackendBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use JMS\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Doctrine\Common\Inflector\Inflector;
/**
* @Route("api", service="application_backend.controller.api")
*/
class ApiController extends AbstractController
{
public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator,
Inflector $inflector
) {
parent::__construct($serializer, $validator, $inflector);
}
/**
* @param Request $request
*
* @Method({"POST"})
* @Route("")
*
* @return JsonResponse|Response
*/
public function indexAction(Request $request)
{
$format = $this->validateContentType($request->headers->get('content_type'));
if ($format instanceof Response) {
return $format;
}
$device = $this->validatePayload(
$request->getContent(),
'Application\BackendBundle\Model\Api\Device',
$format
);
if ($device instanceof Response) {
return $device;
}
return $this->createJsonResponse($device);
}
}
{
"errors": {
"content_type": "Invalid content type application/jsonnnnn."
}
}
{
"errors": {
"price": "The price field must have a valid format.",
"datetime": "The datetime must be in yyyy-mm-dd H:i:s format."
}
}