Aşağıdaki örnekte kullanıcıdan gelen XML 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 data ..." 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("/xml")
*
* @return JsonResponse|Response
*/
public function xmlAction(Request $request)
{
$format = $this->validateContentType($request->headers->get('content_type'));
if ($format instanceof Response) {
return $format;
}

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

//print_r($product); exit;

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

Models


Product


namespace Application\BackendBundle\Model\Api\Xml;

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

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

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

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

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

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

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

/**
* @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\Xml\Image>")
*/
public $images = [];

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

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

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

Category


namespace Application\BackendBundle\Model\Api\Xml;

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

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

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

Image


namespace Application\BackendBundle\Model\Api\Xml;

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

DeliveryCost


namespace Application\BackendBundle\Model\Api\Xml;

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

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

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

Identifier


namespace Application\BackendBundle\Model\Api\Xml;

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

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

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

Property


namespace Application\BackendBundle\Model\Api\Xml;

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\Xml\Manufacturer")
*/
public $manufacturer;
}

Manufacturer


namespace Application\BackendBundle\Model\Api\Xml;

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

Custom 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 XML istek


<?xml version="1.0" encoding="UTF-8"?>
<product id="1">
<title>Samsung Galaxy S4</title>
<stock>5</stock>
<is_active>truse</is_active>
<datetime>2015-07-17 13:25:41</datetime>
<category id="MP">Mobile Phone</category>
<images>
<uri>http://www.inanzzz.com/123.jpg</uri>
<uri>http://www.inanzzz.com/456.jpg</uri>
</images>
<delivery_cost>
<standard>253.50</standard>
<express>269.99</express>
</delivery_cost>
<identifiers>
<identifier code="A1">12345</identifier>
<identifier code="C6">C6-123</identifier>
</identifiers>
<properties>
<colour>Black</colour>
<manufacturer>
<name>Samsung Group</name>
<website>http://www.samsung.com</website>
</manufacturer>
</properties>
</product>

Test


Yukarıdaki XML isteği http://football.local/app_dev.php/backend/api/xml adresine POSTalarsak ve $product değişkenini response gönderilmeden önce print edersek, aşağıdaki geçerli sonucu alırız. Geri verilen XML sonucundaki değerler <![CDATA[değer burada olur]]> etiketi ile sarılır.


Application\BackendBundle\Model\Api\Xml\Product Object
(
[id] => 1
[title] => Samsung Galaxy S4
[stock] => 5
[isActive] => 1
[datetime] => 2015-07-17 13:25:41
[category] => Application\BackendBundle\Model\Api\Xml\Category Object
(
[id] => MP
[value] => Mobile Phone
)
[images] => Array
(
[0] => Application\BackendBundle\Model\Api\Xml\Image Object
(
[uri] => http://www.inanzzz.com/123.jpg
)

[1] => Application\BackendBundle\Model\Api\Xml\Image Object
(
[uri] => http://www.inanzzz.com/456.jpg
)
)
[deliveryCost] => Application\BackendBundle\Model\Api\Xml\DeliveryCost Object
(
[standard] => 253.5
[express] => 269.99
)
[identifiers] => Array
(
[0] => Application\BackendBundle\Model\Api\Xml\Identifier Object
(
[code] => A1
[value] => 12345
)
[1] => Application\BackendBundle\Model\Api\Xml\Identifier Object
(
[code] => C6
[value] => C6-123
)
)
[properties] => Application\BackendBundle\Model\Api\Xml\Property Object
(
[colour] => Black
[manufacturer] => Application\BackendBundle\Model\Api\Xml\Manufacturer Object
(
[name] => Samsung Group
[website] => http://www.samsung.com
)
)
)

Geçersiz isteğin sonucu


# 400 Bad request
{
"errors": {
"images[1].uri": "The image uri is required.",
"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.colour": "The colour field is required.",
"properties.manufacturer.email_address": "The email_address must have a valid format."
}
}

Bir tanede basit XML istek örneği


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


İstek


<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>ABC</item>
<item>DEF</item>
<item>GHI</item>
</items>

Model


/**
* @Serializer\XmlRoot("items")
*/
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\XmlList(inline=true, entry="item")
* @Serializer\Type("array<string>")
*/
public $items = [];
}