23/07/2015 - SYMFONY
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 ile "jms/serializer-bundle": "0.13.0"
paketini yükleyin.
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
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()];
}
}
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);
}
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
# 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();
}
}
}
<?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>
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
)
)
)
# 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."
}
}
Bu basit bir XML istek ve model class örneğidir.
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>ABC</item>
<item>DEF</item>
<item>GHI</item>
</items>
/**
* @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 = [];
}