14/06/2015 - SYMFONY
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.
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()];
}
}
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:
doctrine_common_inflector:
class: Doctrine\Common\Inflector\Inflector
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/serializer-bundle": "0.13.0"
and enable it in AppKernel.php by adding new JMS\SerializerBundle\JMSSerializerBundle()
into $bundles
array.
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;
}
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;
}
You can use Postman browser extension to do same tests.