Aşağıdaki örnekte kullanıcıdan gelen json isteği, model kullanarak kontrol edip doğruladıktan sonra, serileştirip model classın alanları ile ilişkilendiriyoruz. Eğer istekte herhangi bir problem var ise, "400 Bad request" hatası geri gönderilir. @Serializer\Type("....") otomatik olarak bazı verileri otomatik olarak işler, ama işleyememe durumunda ise "500 Internal Server Error - Could not decode JSON, syntax error - malformed JSON." hatası geri gönderilir. Örnek ayrıca datetime için özel olarak yaratılmış olan bir validator constraint kullanır.


Composer.json


Composer ile "jms/serializer-bundle": "0.13.0" paketini yükleyin.


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

Controllers


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 = ['json' => 'application/json', 'xml' => 'application/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 (!in_array($contentType, $this->validContentTypes)) {
return $this->createFailureResponse(
['content_type' => sprintf('Invalid content type [%s].', $contentType)],
'json',
415
);
}

return array_search($contentType, $this->validContentTypes);
}

/**
* @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->createFailureResponse($errors, $format);
}

return $payload;
}

/**
* @param array|object $content
* @param string $format
* @param int $status
*
* @return Response
*/
protected function createSuccessResponse($content, $format = 'json', $status = 200)
{
return $this->getResponse($content, $format, $status);
}

/**
* @param array|ConstraintViolationListInterface $content
* @param string $format
* @param int $status
*
* @return Response
*/
protected function createFailureResponse($content, $format = 'json', $status = 400)
{
$errorList = null;

if ($content instanceof ConstraintViolationList) {
foreach ($content as $error) {
$error = $this->getErrorFromValidation($error);
$errorList[$error['key']] = $error['value'];
}
} else {
$errorList = $content;
}

return $this->getResponse(['errors' => $errorList], $format, $status);
}

/**
* @param array|object $content
* @param string $format
* @param int $status
*
* @return Response
*/
private function getResponse($content, $format, $status)
{
$context = new SerializationContext();
$context->setSerializeNull(false);

$response = $this->serializer->serialize($content, $format, $context);

return new Response($response, $status, ['Content-Type' => $this->validContentTypes[$format]]);
}

/**
* @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("/json")
*
* @return JsonResponse|Response
*/
public function jsonAction(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\Json\Device',
$format
);
if ($device instanceof Response) {
return $device;
}

//print_r($device); exit;

return $this->createSuccessResponse($device, $format);
}
}

Models


Device


namespace Application\BackendBundle\Model\Api\Json;

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

class Device
{
/**
* @var int
*
* @Assert\NotBlank(message="The id field is required.")
* @Assert\Regex(
* pattern="/^[0-9]+$/",
* message="The id field must contain only numeric character(s)."
* )
*
* @Serializer\Type("integer")
*/
public $id;

/**
* @var string
*
* @Assert\NotBlank(message="The name field is required.")
* @Assert\Length(
* max=255,
* maxMessage="The name field cannot be longer than {{ limit }} character(s)."
* )
*
* @Serializer\Type("string")
*/
public $name;

/**
* @var float
*
* @Assert\NotBlank(message="The price field is required.")
* @Assert\Regex(
* pattern="/^(?!0\d)\d{1,8}(\.[0-9]{1,2})?$/",
* message="The price field must have a valid format.")
*
* @Serializer\Type("float")
*/
public $price;

/**
* @var bool
*
* @Assert\NotBlank(message="The is_available field is required.")
* @Assert\Type(
* type="bool",
* message="The is_available field must contain only boolean value."
* )
*
* @Serializer\Type("boolean")
*/
public $isAvailable;

/**
* @var string
*
* @Assert\NotBlank(message="The datetime field is required.")
* @BackendBundleAssert\DateTime
*
* @Serializer\Type("string")
*/
public $datetime;

/**
* @var array
*
* @Assert\Count(
* min="1",
* max="2",
* minMessage="You must provide at least {{ limit }} image(s).",
* maxMessage="You can provide maximum {{ limit }} image(s)."
* )
*
* @Serializer\Type("array")
*/
public $images;

/**
* @var Property
*
* @Assert\Valid(traverse="true")
*
* @Serializer\Type("Application\BackendBundle\Model\Api\Json\Property")
*/
public $properties;

/**
* @var Repair[]
*
* @Assert\Valid(traverse="true")
*
* @Serializer\Type("array<Application\BackendBundle\Model\Api\Json\Repair>")
*/
public $repairs = [];

/**
* @var array
*
* @Assert\Count(
* max="10",
* maxMessage="You can provide maximum {{ limit }} random info."
* )
*
* @Serializer\Type("array")
*/
public $randoms = [];
}

Property


namespace Application\BackendBundle\Model\Api\Json;

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

class Property
{
/**
* @var string
*
* @Assert\NotBlank(message="The made_in field is required.")
* @Assert\Length(
* max=100,
* maxMessage="The made_in field cannot be longer than {{ limit }} character(s)."
* )
*
* @Serializer\Type("string")
*/
public $madeIn;

/**
* @var array
*
* @Assert\Count(
* max="5",
* maxMessage="You can provide maximum {{ limit }} colour(s)."
* )
*
* @Serializer\Type("array")
*/
public $colours;

/**
* @var Manufacturer
*
* @Assert\Valid(traverse="true")
*
* @Serializer\Type("Application\BackendBundle\Model\Api\Json\Manufacturer")
*/
public $manufacturer;
}

Manufacturer


namespace Application\BackendBundle\Model\Api\Json;

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

class Manufacturer
{
/**
* @var string
*
* @Assert\NotBlank(message="The name field is required.")
* @Assert\Length(
* max=100,
* maxMessage="The name field cannot be longer than {{ limit }} character(s)."
* )
*
* @Serializer\Type("string")
*/
public $name;

/**
* @var string
*
* @Assert\NotBlank(message="The phone field is required.")
* @Assert\Length(
* max=20,
* maxMessage="The phone field cannot be longer than {{ limit }} character(s)."
* )
*
* @Serializer\Type("string")
*/
public $phone;

/**
* @var string
*
* @Assert\NotBlank(message="The email_address field is required.")
* @Assert\Email(checkMX=true, message="The email_address must have a valid format.")
*
* @Serializer\Type("string")
*/
public $emailAddress;
}

Repair


namespace Application\BackendBundle\Model\Api\Json;

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

class Repair
{
/**
* @var string
*
* @Assert\NotBlank(message="The reason field is required.")
* @Assert\Length(
* max=255,
* maxMessage="The reason field cannot be longer than {{ limit }} character(s)."
* )
*
* @Serializer\Type("string")
*/
public $reason;

/**
* @var string
*
* @Assert\NotBlank(message="The datetime field is required.")
* @BackendBundleAssert\DateTime
*
* @Serializer\Type("string")
*/
public $datetime;
}

Özel DateTimeValidator


# DateTime.php
namespace Application\BackendBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

/**
* @Annotation
*/
class DateTime extends Constraint
{
const MESSAGE = 'The datetime must be in yyyy-mm-dd H:i:s format.';

public function validatedBy()
{
return get_class($this).'Validator';
}
}

# DateTimeValidator.php
namespace Application\BackendBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use DateTime;

class DateTimeValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if (trim($value) == '') {
return;
}

$datetime = DateTime::createFromFormat('Y-m-d H:i:s', $value);

if (!$datetime || $datetime->format('Y-m-d H:i:s') != $value) {
$this->context
->buildViolation($constraint::MESSAGE)
->addViolation();
}
}
}

Örnek json istek


{
"id": 123,
"name": "Samsung Galaxy S4",
"price": 243.11,
"is_available": true,
"datetime": "2015-07-17 20:36:13",
"images": [
"1.jpg",
"2.jpg"
],
"properties": {
"made_in": "South Korea",
"colours": [
"black",
"white",
"gold"
],
"manufacturer": {
"name": "Samsung Group",
"phone": "123567890",
"email_address": "samsung@samsung.com"
}
},
"repairs": [
{
"reason": "Cracked screen",
"datetime": "2014-07-17 12:00:00"
},
{
"reason": "Bubbles in screen",
"datetime": "2014-02-21 17:30:50"
}
],
"randoms": {
"review": "9/10",
"out_of_date": "false",
"stock": 987
}
}

Test


Yukarıdaki json isteği http://football.local/app_dev.php/backend/api/json adresine POSTalarsak ve $device değişkenini response gönderilmeden önce print edersek, aşağıdaki geçerli sonucu alırız.


Application\BackendBundle\Model\Api\Json\Device Object
(
[id] => 123
[name] => Samsung Galaxy S4
[price] => 243.11
[isAvailable] => 1
[datetime] => 2015-07-17 20:36:13
[images] => Array
(
[0] => 1.jpg
[1] => 2.jpg
)
[properties] => Application\BackendBundle\Model\Api\Json\Property Object
(
[madeIn] => South Korea
[colours] => Array
(
[0] => black
[1] => white
[2] => gold
)
[manufacturer] => Application\BackendBundle\Model\Api\Json\Manufacturer Object
(
[name] => Samsung Group
[phone] => 123567890
[emailAddress] => samsung@samsung.com
)
)
[repairs] => Array
(
[0] => Application\BackendBundle\Model\Api\Json\Repair Object
(
[reason] => Cracked screen
[datetime] => 2014-07-17 12:00:00
)
[1] => Application\BackendBundle\Model\Api\Json\Repair Object
(
[reason] => Bubbles in screen
[datetime] => 2014-02-21 17:30:50
)
)
[randoms] => Array
(
[review] => 9/10
[out_of_date] => false
[stock] => 987
)
)

Geçersiz isteğin sonucu


# 400 Bad request
{
"errors": {
"datetime": "The datetime must be in yyyy-mm-dd H:i:s format.",
"properties.made_in": "The made_in field cannot be longer than 100 character(s).",
"properties.colours": "You can provide maximum 3 colour(s).",
"properties.manufacturer.email_address": "The email_address must have a valid format."
}
}

Bir tanede basit json istek örneği


Bu basit bir json istek ve model class örneğidir.


İstek


{
"items": [
"ABC",
"DEF",
"GHI"
]
}

Model


class Item
{
/**
* @var array[]
*
* @Assert\Type(type="array", message="You must provide an array of items.")
* @Assert\Count(
* max="5",
* maxMessage="You can provide maximum {{ limit }} item(s)."
* )
*
* @Serializer\Type("array")
*/
public $items = [];
}