16/08/2015 - SYMFONY
Aşağıdaki örnekte olduğu gibi, kodumuzu temiz tutmak için bir tane map servis class oluşturup, model ve entity ilişkilendirme işlemini gizli olarak yapabiliriz. BCCAutoMapperBundle hakkında daha fazla bilgi için, GitHub sayfasını ziyaret edebilirsiniz.
Composer ile bcc/auto-mapper-bundle
paketini yükleyin.
Kernel içinde new BCC\AutoMapperBundle\BCCAutoMapperBundle()
kaydını yapın.
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
arguments:
- @doctrine.orm.entity_manager
- @bcc_auto_mapper.mapper
services:
application_backend.map.address:
class: Application\BackendBundle\Model\AddressMap
tags:
- { name: bcc_auto_mapper.map }
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()];
}
}
Gerçek hayatta aşağıdaki işlemlerin çoğu, servis ve factory classlar içinde yapılır. Controller mümkün olduğunca sade olmalıdır ama şu an için bu tavsiyeye uymayacağım.
namespace Application\BackendBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Application\BackendBundle\Entity\Address;
use BCC\AutoMapperBundle\Mapper\Mapper;
use Doctrine\ORM\EntityManager;
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
{
private $entityManager;
private $mapper;
public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator,
Inflector $inflector,
EntityManager $entityManager,
Mapper $mapper
) {
parent::__construct($serializer, $validator, $inflector);
$this->entityManager = $entityManager;
$this->mapper = $mapper;
}
/**
* @param Request $request
*
* @Method({"POST"})
* @Route("/address")
*
* @return JsonResponse|Response
*/
public function addressAction(Request $request)
{
$format = $this->validateContentType($request->headers->get('content_type'));
if ($format instanceof Response) {
return $format;
}
$address = $this->validatePayload(
$request->getContent(),
'Application\BackendBundle\Model\Address',
$format
);
if ($address instanceof Response) {
return $address;
}
$addressEntity = new Address();
$this->mapper->map($address, $addressEntity);
$this->entityManager->persist($addressEntity);
$this->entityManager->flush();
//print_r($address); exit;
//print_r($addressEntity); exit;
return $this->createSuccessResponse($address, $format);
}
}
namespace Application\BackendBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="address")
*/
class Address
{
/**
* @var int
*
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var string
*
* @ORM\Column(name="building_number", type="string", length=10)
*/
protected $buildingNumber;
/**
* @var string
*
* @ORM\Column(name="flatNumber", type="string", length=10, nullable=true)
*/
protected $flatNumber;
/**
* @var string
*
* @ORM\Column(name="line1", type="string", length=100)
*/
protected $line1;
/**
* @var string
*
* @ORM\Column(name="line2", type="string", length=100, nullable=true)
*/
protected $line2;
/**
* @var string
*
* @ORM\Column(name="town", type="string", length=100, nullable=true)
*/
protected $town;
/**
* @var string
*
* @ORM\Column(name="city", type="string", length=100)
*/
protected $city;
/**
* @var string
*
* @ORM\Column(name="postcode", type="string", length=10)
*/
protected $postcode;
/**
* @var string
*
* @ORM\Column(name="country", type="string", length=100)
*/
protected $country;
/**
* @var string
*
* @ORM\Column(name="tenant", type="json_array", nullable=true)
*/
protected $tenants;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set buildingNumber
*
* @param integer $buildingNumber
* @return Address
*/
public function setBuildingNumber($buildingNumber)
{
$this->buildingNumber = $buildingNumber;
return $this;
}
/**
* Get buildingNumber
*
* @return integer
*/
public function getBuildingNumber()
{
return $this->buildingNumber;
}
/**
* Set flatNumber
*
* @param integer $flatNumber
* @return Address
*/
public function setFlatNumber($flatNumber)
{
$this->flatNumber = $flatNumber;
return $this;
}
/**
* Get flatNumber
*
* @return integer
*/
public function getFlatNumber()
{
return $this->flatNumber;
}
/**
* Set line1
*
* @param string $line1
* @return Address
*/
public function setLine1($line1)
{
$this->line1 = $line1;
return $this;
}
/**
* Get line1
*
* @return string
*/
public function getLine1()
{
return $this->line1;
}
/**
* Set line2
*
* @param string $line2
* @return Address
*/
public function setLine2($line2)
{
$this->line2 = $line2;
return $this;
}
/**
* Get line2
*
* @return string
*/
public function getLine2()
{
return $this->line2;
}
/**
* Set line3
*
* @param string $line3
* @return Address
*/
public function setLine3($line3)
{
$this->line3 = $line3;
return $this;
}
/**
* Get line3
*
* @return string
*/
public function getLine3()
{
return $this->line3;
}
/**
* Set town
*
* @param string $town
* @return Address
*/
public function setTown($town)
{
$this->town = $town;
return $this;
}
/**
* Get town
*
* @return string
*/
public function getTown()
{
return $this->town;
}
/**
* Set postcode
*
* @param string $postcode
* @return Address
*/
public function setPostcode($postcode)
{
$this->postcode = $postcode;
return $this;
}
/**
* Get postcode
*
* @return string
*/
public function getPostcode()
{
return $this->postcode;
}
/**
* Set country
*
* @param string $country
* @return Address
*/
public function setCountry($country)
{
$this->country = $country;
return $this;
}
/**
* Get country
*
* @return string
*/
public function getCountry()
{
return $this->country;
}
/**
* Set city
*
* @param string $city
* @return Address
*/
public function setCity($city)
{
$this->city = $city;
return $this;
}
/**
* Get city
*
* @return string
*/
public function getCity()
{
return $this->city;
}
/**
* Set tenants
*
* @param array $tenants
* @return Address
*/
public function setTenants($tenants)
{
$this->tenants = $tenants;
return $this;
}
/**
* Get tenants
*
* @return array
*/
public function getTenants()
{
return $this->tenants;
}
}
namespace Application\BackendBundle\Model;
use Symfony\Component\Validator\Constraints as Assert;
use JMS\Serializer\Annotation as Serializer;
/**
* @Serializer\XmlRoot("address")
*/
class Address
{
/**
* @var string
*
* @Assert\NotBlank(message="The building_no field is required.")
* @Assert\Length(
* max=10,
* maxMessage="The building_no field cannot be longer than {{ limit }} characters."
* )
*
* @Serializer\Type("string")
*/
public $buildingNo;
/**
* @var string
*
* @Assert\Length(
* max=10,
* maxMessage="The flat_number field cannot be longer than {{ limit }} characters."
* )
*
* @Serializer\Type("string")
*/
public $flatNumber;
/**
* @var string
*
* @Assert\NotBlank(message="The line1 field is required.")
* @Assert\Length(
* max=100,
* maxMessage="The line1 field cannot be longer than {{ limit }} characters."
* )
*
* @Serializer\Type("string")
*/
public $line1;
/**
* @var string
*
* @Assert\Length(
* max=100,
* maxMessage="The line2 field cannot be longer than {{ limit }} characters."
* )
*
* @Serializer\Type("string")
*/
public $line2;
/**
* @var string
*
* @Assert\Length(
* max=100,
* maxMessage="The town field cannot be longer than {{ limit }} characters."
* )
*
* @Serializer\Type("string")
*/
public $town;
/**
* @var string
*
* @Assert\NotBlank(message="The city field is required.")
* @Assert\Length(
* max=100,
* maxMessage="The city field cannot be longer than {{ limit }} characters."
* )
*
* @Serializer\Type("string")
*/
public $city;
/**
* @var string
*
* @Assert\NotBlank(message="The post_code field is required.")
* @Assert\Length(
* max=10,
* maxMessage="The post_code field cannot be longer than {{ limit }} characters."
* )
*
* @Serializer\Type("string")
*/
public $postCode;
/**
* @var string
*
* @Assert\NotBlank(message="The country field is required.")
* @Assert\Length(
* max=100,
* maxMessage="The country field cannot be longer than {{ limit }} characters."
* )
*
* @Serializer\Type("string")
*/
public $country;
/**
* @var Tenant[]
*
* @Assert\Valid(traverse="true")
* @Serializer\XmlList(inline=false, entry="tenants")
*
* @Serializer\Type("array<Application\BackendBundle\Model\Tenant>")
*/
public $tenants = [];
}
namespace Application\BackendBundle\Model;
use Symfony\Component\Validator\Constraints as Assert;
use JMS\Serializer\Annotation as Serializer;
class Tenant
{
/**
* @var string
*
* @Assert\NotBlank(message="The name field is required.")
* @Assert\Length(
* max=50,
* maxMessage="The name field cannot be longer than {{ limit }} characters."
* )
*
* @Serializer\Type("string")
*/
public $name;
/**
* @var string
*
* @Assert\NotBlank(message="The surname field is required.")
* @Assert\Length(
* max=50,
* maxMessage="The surname field cannot be longer than {{ limit }} characters."
* )
*
* @Serializer\Type("string")
*/
public $surname;
}
namespace Application\BackendBundle\Model;
use BCC\AutoMapperBundle\Mapper\AbstractMap;
use BCC\AutoMapperBundle\Mapper\FieldAccessor\Closure;
class AddressMap extends AbstractMap
{
function __construct()
{
$this
->buildDefaultMap()
->ignoreMember('id')
->route('buildingNumber', 'buildingNo')
->forMember('postcode', new Closure(function(Address $source) {
return strtoupper($source->postCode);
}))
->route('tenants', 'tenants')
->setSkipNull(true);
}
public function getDestinationType()
{
return 'Application\BackendBundle\Entity\Address';
}
public function getSourceType()
{
return 'Application\BackendBundle\Model\Address';
}
}
Bu örnek Json ve XML isteklerini kabul edebildiği için, siz isterseniz XML ile deneme yapabilirsiniz.
{
"building_no": 77,
"flat_number": 3,
"line1": "Palm Close",
"line2": "Long Lane",
"town": "London Bridge",
"city": "London",
"post_code": "EC1 2CE",
"country": "United Kingdom",
"tenants": [
{
"name": "Robert",
"surname": "DeNiro"
},
{
"name": "Al",
"surname": "Pacino"
}
]
}
Eğer yukarıdaki isteği http://...../api/address
adresine gönderirseniz, aşağıdaki sonucu alırsınız.
// print_r($address);
Application\BackendBundle\Model\Address Object
(
[buildingNo] => 77
[flatNumber] => 3
[line1] => Palm Close
[line2] => Long Lane
[town] => London Bridge
[city] => London
[postCode] => EC1 2CE
[country] => United Kingdom
[tenants] => Array
(
[0] => Application\BackendBundle\Model\Tenant Object
(
[name] => Robert
[surname] => DeNiro
)
[1] => Application\BackendBundle\Model\Tenant Object
(
[name] => Al
[surname] => Pacino
)
)
)
// print_r($addressEntity);
Application\BackendBundle\Entity\Address Object
(
[id:protected] => 3
[buildingNumber:protected] => 77
[flatNumber:protected] => 3
[line1:protected] => Palm Close
[line2:protected] => Long Lane
[town:protected] => London Bridge
[city:protected] => London
[postcode:protected] => EC1 2CE
[country:protected] => United Kingdom
[tenants:protected] => Array
(
[0] => Application\BackendBundle\Model\Tenant Object
(
[name] => Robert
[surname] => DeNiro
)
[1] => Application\BackendBundle\Model\Tenant Object
(
[name] => Al
[surname] => Pacino
)
)
)
mysql> SELECT * FROM address;
+----+-----------------+------------+------------+-----------+---------------+----------+----------------+--------+-------------------------------------------------------------------------+
| id | building_number | flatNumber | line1 | line2 | town | postcode | country | city | tenant |
+----+-----------------+------------+------------+-----------+---------------+----------+----------------+--------+-------------------------------------------------------------------------+
| 1 | 77 | 3 | Palm Close | Long Lane | London Bridge | EC1 2CE | United Kingdom | London | [{"name":"Robert","surname":"DeNiro"},{"name":"Al","surname":"Pacino"}] |
+----+-----------------+------------+------------+-----------+---------------+----------+----------------+--------+-------------------------------------------------------------------------+
1 row in set (0.00 sec)