In this example, we're going to create an API style base project which is good for AJAX based application. Json request comes in and json response goes out. Request validation rules are defined in model classes. There is no form types. Json serialisation and deserialisation takes place in abstract controller with the help of JMS serialiser bundle. Key properties of serialised class is "tableised" (e.g. $fullName => full_name) with Doctrine_Inflector which also has many useful methods.


Abstract Controller


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\Validator\ValidatorInterface;

abstract class AbstractController
{
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 $responseData
* @param int $status
*
* @return Response
*/
protected function createJsonResponse($responseData = '', $status = 200)
{
$context = new SerializationContext();
$context->setSerializeNull(false);

$jsonResponse = $this->serializer->serialize($responseData, 'json', $context);

return (new Response(
$jsonResponse,
$status,
['Content-Type' => 'application/json']
));
}

/**
* @param $errors
* @param int $status
*
* @return Response
*/
protected function createJsonErrorResponse($errors, $status = 400)
{
$errorData = ['errors' => []];

foreach ($errors as $error) {
if ($error instanceof ConstraintViolationInterface) {
$preparedError = $this->getErrorFromValidation($error, $errorData);
} else {
$preparedError = ['key' => count($errorData['errors']), 'value' => $error];
}
$errorData['errors'][$preparedError['key']] = $preparedError['value'];
}

return $this->createJsonResponse($errorData, $status);
}

/**
* @param string $content
* @param string $class
*
* @return mixed|Response
*/
protected function validate($content, $class)
{
$content = $this->serializer->deserialize(
$content,
$class,
'json'
);

$errors = $this->validator->validate($content);
if (count($errors)) {
return $this->createJsonErrorResponse($errors);
}

return $content;
}

/**
* @param ConstraintViolationInterface $error
*
* @return mixed
*/
private function getErrorFromValidation($error)
{
$properties = $this->inflector->tableize($error->getPropertyPath());

return ['key' => $properties, 'value' => $error->getMessage()];
}
}

User Controller


namespace Application\BackendBundle\Controller;

use Application\BackendBundle\Service\UserServiceInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use JMS\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Doctrine\Common\Inflector\Inflector;

/**
* @Route("user", service="application_backend.controller.user")
*/
class UserController extends AbstractController
{
const ROUTER_PREFIX = 'application_backend_user_';

private $router;
private $userService;

public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator,
Inflector $inflector,
RouterInterface $router,
UserServiceInterface $userService
) {
parent::__construct($serializer, $validator, $inflector);

$this->router = $router;
$this->userService = $userService;
}

/**
* @param Request $request
*
* @Route("")
* @Method({"GET"})
*
* @return Response
*/
public function listAction(Request $request)
{
$page = $request->query->get('page', 1);
$limit = $request->query->get('limit', 2);

return $this->createJsonResponse("USER list: $page - $limit");
}

/**
* @param Request $request
*
* @Route("")
* @Method({"POST"})
*
* @return Response
*/
public function createAction(Request $request)
{
$user = $this->validate(
$request->getContent(),
'Application\BackendBundle\Model\User\Create'
);
if ($user instanceof Response) {
return $user;
}

return $this->createJsonResponse("USER create");
}
}

Services.yml


services:
doctrine_common_inflector:
class: Doctrine\Common\Inflector\Inflector

Controllers.yml


services:
application_backend.controller.abstract:
class: Application\BackendBundle\Controller\AbstractController
abstract: true # Abstract enabled
arguments:
- @serializer # Enabled by JMS Serializer Bundle
- @validator # Enabled by the application by default
- @doctrine_common_inflector # Enabled by user

application_backend.controller.user:
class: Application\BackendBundle\Controller\UserController
parent: application_backend.controller.abstract # Parent abstract class
arguments:
- @router # Compulsory for urls
- @application_backend.service.user # User service

Install JMS Serialiser Bundle


Install "jms/serializer-bundle": "0.13.0" and enable it in AppKernel.php by adding new JMS\SerializerBundle\JMSSerializerBundle() into $bundles array.


User entity


namespace Application\BackendBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
* @ORM\Entity(repositoryClass="Application\BackendBundle\Repository\UserRepository")
* @ORM\Table(name="user")
* @UniqueEntity(fields="username", message="Username is already in use.")
*/
class User
{
/**
* @var int
*
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;

/**
* @ORM\Column(name="username", type="string", length=20, unique=true)
*/
protected $username;

/**
* @ORM\Column(name="password", type="string", length=40)
*/
protected $password;
}

User model


namespace Application\BackendBundle\Model\User;

use JMS\Serializer\Annotation as Serializer;
use Symfony\Component\Validator\Constraints as Assert;

class Create
{
/**
* @var string
*
* @Assert\NotBlank(message="Username is required")
* @Assert\Length(
* min="6", minMessage="Username cannot be less than {{ limit }} characters.",
* max="20", maxMessage="Username cannot be longer than {{ limit }} characters."
* )
*
* @Serializer\Type("string")
*/
public $username;

/**
* @var string
*
* @Assert\NotBlank(message="Password is required")
*
* @Serializer\Type("string")
*/
public $password;
}

Tests


You can use Postman browser extension to do same tests.


GET examples for listAction() method




POST examples for createAction() method