Aşağıdaki örnekte kullanıcıdan gelen json ve XML isteklerini, model kullanarak kontrol edip doğruladıktan sonra, serileştirip model classın alanları ile ilişkilendiriyoruz. İstek örneklerini hazırlarken, her zaman XML versiyonundan başlayın çünkü, json versiyonun XML versiyonuna adapte edilmesi daha kolay olur. 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 data ..." hatası geri gönderilir.


Composer.json


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


Controllers.xml


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_xml")
*
* @return JsonResponse|Response
*/
public function jsonXmlAction(Request $request)
{
$format = $this->validateContentType($request->headers->get('content_type'));
if ($format instanceof Response) {
return $format;
}

$car = $this->validatePayload(
$request->getContent(),
'Application\BackendBundle\Model\Api\JsonXml\Car',
$format
);
if ($car instanceof Response) {
return $car;
}

//print_r($car); exit;

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

Models


Car


namespace Application\BackendBundle\Model\Api\JsonXml;

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

/**
* @Serializer\XmlRoot("car")
*/
class Car
{
/**
* @var string
*
* @Assert\NotBlank(message="The registration attribute is required.")
* @Assert\Length(
* max=10,
* maxMessage="The registration attribute cannot be longer than {{ limit }} character(s)."
* )
*
* @Serializer\Type("string")
* @Serializer\XmlAttribute()
*/
public $registration;

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

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

/**
* @var string
*
* @Assert\NotBlank(message="The year field is required.")
* @Assert\Length(
* min=4,
* max=4,
* exactMessage="The year field should have exactly {{ limit }} character(s)."
* )
*
* @Serializer\Type("string")
*/
public $year;

/**
* @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 Image[]
*
* @Assert\Valid(traverse="true")
* @Assert\Count(
* max="2",
* maxMessage="You can provide maximum {{ limit }} image(s)."
* )
*
* @Serializer\XmlList(inline=false, entry="uri")
* @Serializer\Type("array<Application\BackendBundle\Model\Api\JsonXml\Image>")
*/
public $images = [];

/**
* @var Cost
*
* @Assert\Valid(traverse="true")
*
* @Serializer\Type("Application\BackendBundle\Model\Api\JsonXml\Cost")
*/
public $cost;

/**
* @var Identifier[]
*
* @Assert\Valid(traverse="true")
*
* @Serializer\XmlList(inline=false, entry="identifier")
* @Serializer\Type("array<Application\BackendBundle\Model\Api\JsonXml\Identifier>")
*/
public $identifiers = [];

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

Model


namespace Application\BackendBundle\Model\Api\JsonXml;

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

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

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

Image


namespace Application\BackendBundle\Model\Api\JsonXml;

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

class Image
{
/**
* @var string
*
* @Assert\NotBlank(message="The image uri is required.")
*
* @Serializer\Type("string")
* @Serializer\XmlValue
*/
public $uri;
}

Cost


namespace Application\BackendBundle\Model\Api\JsonXml;

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

class Cost
{
/**
* @var string
*
* @Assert\NotBlank(message="The currency attribute is required.")
* @Assert\Regex(
* pattern="/^[A-Z]{3}$/",
* message="The currency attribute must comply with ISO 4217 standards."
* )
*
* @Serializer\Type("string")
* @Serializer\XmlAttribute
*/
public $currency;

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

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

Identifier


namespace Application\BackendBundle\Model\Api\JsonXml;

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

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

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

Property


namespace Application\BackendBundle\Model\Api\JsonXml;

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

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

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

Manufacturer


namespace Application\BackendBundle\Model\Api\JsonXml;

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

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

/**
* @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 website field is required.")
* @Assert\Url(message="The website field must contain a valid URL.")
*
* @Serializer\Type("string")
*/
public $website;
}

Örnek istekler


Json


{
"registration": "AB15 CDE",
"make": "BMW",
"model": {
"series": "3.18",
"value": "Coupe"
},
"year": 2015,
"is_available": true,
"images": [
{
"uri": "http://www.inanzzz.com/123.jpg"
},
{
"uri": "http://www.inanzzz.com/456.jpg"
}
],
"cost": {
"currency": "GBP",
"basic": 29053.50,
"sport": 39269.99
},
"identifiers": [
{
"code": "BMW1",
"value": "3.18"
},
{
"code": "BMW2",
"value": "3.18TS"
}
],
"properties": {
"colour": "Silver",
"manufacturer": {
"origin": "Germany",
"name": "BMW Group",
"website": "http://www.bmw.com"
}
}
}

XML


<?xml version="1.0" encoding="UTF-8"?>
<car registration="AB15 CDE">
<make>BMW</make>
<model series="3.18">Coupe</model>
<year>2015</year>
<is_available>true</is_available>
<images>
<uri>http://www.inanzzz.com/123.jpg</uri>
<uri>http://www.inanzzz.com/456.jpg</uri>
</images>
<cost currency="GBP">
<basic>29053.50</basic>
<sport>39269.99</sport>
</cost>
<identifiers>
<identifier code="BMW1">3.18</identifier>
<identifier code="BMW2">3.18TS</identifier>
</identifiers>
<properties>
<colour>Silver</colour>
<manufacturer origin="Germany">
<name>BMW Group</name>
<website>http://www.bmw.com</website>
</manufacturer>
</properties>
</car>

Test


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


Application\BackendBundle\Model\Api\JsonXml\Car Object
(
[registration] => AB15 CDE
[make] => BMW
[model] => Application\BackendBundle\Model\Api\JsonXml\Model Object
(
[series] => 3.18
[value] => Coupe
)
[year] => 2015
[isAvailable] => 1
[images] => Array
(
[0] => Application\BackendBundle\Model\Api\JsonXml\Image Object
(
[uri] => http://www.inanzzz.com/123.jpg
)
[1] => Application\BackendBundle\Model\Api\JsonXml\Image Object
(
[uri] => http://www.inanzzz.com/456.jpg
)
)
[cost] => Application\BackendBundle\Model\Api\JsonXml\Cost Object
(
[currency] => GBP
[basic] => 29053.5
[sport] => 39269.99
)
[identifiers] => Array
(
[0] => Application\BackendBundle\Model\Api\JsonXml\Identifier Object
(
[code] => BMW1
[value] => 3.18
)
[1] => Application\BackendBundle\Model\Api\JsonXml\Identifier Object
(
[code] => BMW2
[value] => 3.18TS
)
)
[properties] => Application\BackendBundle\Model\Api\JsonXml\Property Object
(
[colour] => Silver
[manufacturer] => Application\BackendBundle\Model\Api\JsonXml\Manufacturer Object
(
[origin] => Germany
[name] => BMW Group
[website] => http://www.bmw.com
)
)
)

Başarılı sonuçlar


Json


{
"registration": "AB15 CDE",
"make": "BMW",
"model": {
"series": "3.18",
"value": "Coupe"
},
"year": "2015",
"is_available": true,
"images": [
{
"uri": "http://www.inanzzz.com/123.jpg"
},
{
"uri": "http://www.inanzzz.com/456.jpg"
}
],
"cost": {
"currency": "GBP",
"basic": 29053.5,
"sport": 39269.99
},
"identifiers": [
{
"code": "BMW1",
"value": "3.18"
},
{
"code": "BMW2",
"value": "3.18TS"
}
],
"properties": {
"colour": "Silver",
"manufacturer": {
"origin": "Germany",
"name": "BMW Group",
"website": "http://www.bmw.com"
}
}
}

XML


<?xml version="1.0" encoding="UTF-8"?>
<car registration="AB15 CDE">
<make>
<![CDATA[BMW]]>
</make>
<model series="3.18">
<![CDATA[Coupe]]>
</model>
<year>
<![CDATA[2015]]>
</year>
<is_available>true</is_available>
<images>
<uri>
<![CDATA[http://www.inanzzz.com/123.jpg]]>
</uri>
<uri>
<![CDATA[http://www.inanzzz.com/456.jpg]]>
</uri>
</images>
<cost currency="GBP">
<basic>29053.5</basic>
<sport>39269.99</sport>
</cost>
<identifiers>
<identifier code="BMW1">
<![CDATA[3.18]]>
</identifier>
<identifier code="BMW2">
<![CDATA[3.18TS]]>
</identifier>
</identifiers>
<properties>
<colour>
<![CDATA[Silver]]>
</colour>
<manufacturer origin="Germany">
<name>
<![CDATA[BMW Group]]>
</name>
<website>
<![CDATA[http://www.bmw.com]]>
</website>
</manufacturer>
</properties>
</car>

Hata sonuçları


Json


{
"errors": {
"make": "The make field is required.",
"images": "You can provide maximum 2 image(s).",
"cost.currency": "The currency attribute must comply with ISO 4217 standards.",
"cost.basic": "The basic field is required.",
"identifiers[0].value": "The identifier value is required.",
"identifiers[1].code": "The code attribute is required.",
"properties.colour": "The colour field is required.",
"properties.manufacturer.name": "The name field is required."
}
}

XML


<?xml version="1.0" encoding="UTF-8"?>
<result>
<entry>
<entry>
<![CDATA[The make field is required.]]>
</entry>
<entry>
<![CDATA[You can provide maximum 2 image(s).]]>
</entry>
<entry>
<![CDATA[The currency attribute must comply with ISO 4217 standards.]]>
</entry>
<entry>
<![CDATA[The basic field is required.]]>
</entry>
<entry>
<![CDATA[The identifier value is required.]]>
</entry>
<entry>
<![CDATA[The code attribute is required.]]>
</entry>
<entry>
<![CDATA[The colour field is required.]]>
</entry>
<entry>
<![CDATA[The name field is required.]]>
</entry>
</entry>
</result>