Hello everyone!

We have been investing plenty of personal time and energy for many years to share our knowledge with you all. However, we now need your help to keep this blog running. All you have to do is just click one of the adverts on the site, otherwise it will sadly be taken down due to hosting etc. costs. Thank you.

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.


Composer.json


Install "jms/serializer-bundle": "0.13.0" with composer.


Controllers.yml


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

AbstractController


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()];
}
}

ApiController


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);
}
}

Example error responses


415 Invalid content-type


{
"errors": {
"content_type": "Invalid content type application/jsonnnnn."
}
}

400 Bad request


{
"errors": {
"price": "The price field must have a valid format.",
"datetime": "The datetime must be in yyyy-mm-dd H:i:s format."
}
}