Eğer model ve entity class özelliklerini manuel olarak ilişkilendirmek istemiyorsanız, BCCAutoMapper ile bu işlemi otomatik olarak yapabilirsiniz. Bu ayrıca kodu daha temiz gösterir ve mantığı gizler. Aşağıdaki örnekte model classları one-to-many ilişkili entitilere bağlayacağız. BCCAutoMapperBundle hakkında daha fazla bilgi için, GitHub sayfasını ziyaret edebilirsiniz.


Composer.json


Composer ile bcc/auto-mapper-bundle paketini yükleyin.


AppKernel.php


Kernel içinde new BCC\AutoMapperBundle\BCCAutoMapperBundle() kaydını yapın.


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
arguments:
- @doctrine.orm.entity_manager
- @bcc_auto_mapper.mapper

AbstractController.php


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.php


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 Application\BackendBundle\Entity\Message;
use Application\BackendBundle\Entity\Student;
use Application\BackendBundle\Model\Student\StudentModel;
use BCC\AutoMapperBundle\Mapper\FieldAccessor\Closure;
use BCC\AutoMapperBundle\Mapper\Mapper;
use Doctrine\ORM\EntityManager;
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
{
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("/student_message")
*
* @return JsonResponse|Response
*/
public function studentMessageAction(Request $request)
{
$format = $this->validateContentType($request->headers->get('content_type'));
if ($format instanceof Response) {
return $format;
}

$studentModel = $this->validatePayload(
$request->getContent(),
'Application\BackendBundle\Model\Student\StudentModel',
$format
);
if ($studentModel instanceof Response) {
return $studentModel;
}

//print_r($studentModel);exit;

// START of MAPPING
// Student
$this->mapper->createMap(
'Application\BackendBundle\Model\Student\StudentModel',
'Application\BackendBundle\Entity\Student'
)
// Entity property "id" does not exist in model so ignore it
->ignoreMember('id')
// We like to store entity property "schoolId" as upper case in DB
->forMember('schoolId', new Closure(function(StudentModel $source){
return strtoupper($source->schoolId);
}))
// Entity property "fullName" is associated with model property "name" so map them manually
->route('fullName', 'name')
// We have to handle entity relationship property "message" manually with its own mapping
->ignoreMember('message')
->setSkipNull(true);

$student = new Student();
$this->mapper->map($studentModel, $student);

// Message
$this->mapper->createMap(
'Application\BackendBundle\Model\Student\MessageModel',
'Application\BackendBundle\Entity\Message'
)
// Entity property "id" does not exist in model so ignore it
->ignoreMember('id')
// Entity property "fullName" is associated with model property "name" so map them manually
->route('title', 'subject')
// Entity property "note" is associated with model property "additionalNote" so map them manually
->route('note', 'additionalNote')
// Entity property "open" is associated with model property "isOpen" so map them manually
->route('open', 'isOpen')
->setSkipNull(true);

$message = new Message();
$this->mapper->map($studentModel->message, $message);

//print_r($student);
//print_r($message);

// END of MAPPING

$message->setStudent($student);
$this->entityManager->persist($student);
$this->entityManager->persist($message);
$this->entityManager->flush();

//print_r($studentModel);

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

Student entity


namespace Application\BackendBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table(name="student")
*/
class Student
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;

/**
* @ORM\Column(name="schoolId", type="string", length=10)
*/
protected $schoolId;

/**
* @ORM\Column(name="full_name", type="string", length=100)
*/
protected $fullName;

/**
* @ORM\Column(name="nickname", type="string", length=20, nullable=true)
*/
protected $nickname;

/**
* @ORM\Column(name="gender", type="string", length=1, nullable=true)
*/
protected $gender;

/**
* @ORM\OneToMany(targetEntity="Message", mappedBy="student", cascade={"persist", "remove"})
*/
protected $message;

/**
* Constructor
*/
public function __construct()
{
$this->message = new ArrayCollection();
}

/**
* @return integer
*/
public function getId()
{
return $this->id;
}

/**
* @param string $schoolId
* @return Student
*/
public function setSchoolId($schoolId)
{
$this->schoolId = $schoolId;

return $this;
}

/**
* @return string
*/
public function getSchoolId()
{
return $this->schoolId;
}

/**
* @param string $fullName
* @return Student
*/
public function setFullName($fullName)
{
$this->fullName = $fullName;

return $this;
}

/**
* @return string
*/
public function getFullName()
{
return $this->fullName;
}

/**
* @param string $nickname
* @return Student
*/
public function setNickname($nickname)
{
$this->nickname = $nickname;

return $this;
}

/**
* @return string
*/
public function getNickname()
{
return $this->nickname;
}

/**
* @param string $gender
* @return Student
*/
public function setGender($gender)
{
$this->gender = $gender;

return $this;
}

/**
* @return string
*/
public function getGender()
{
return $this->gender;
}

/**
* @param Message $message
* @return Student
*/
public function addMessage(Message $message)
{
$this->message[] = $message;

return $this;
}

/**
* @param Message $message
*/
public function removeMessage(Message $message)
{
$this->message->removeElement($message);
}

/**
* @return Collection
*/
public function getMessage()
{
return $this->message;
}
}

Message entity


namespace Application\BackendBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table(name="message")
*/
class Message
{
/**
* @var int
*
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;

/**
* @var string
*
* @ORM\Column(name="title", type="string", length=255)
*/
protected $title;

/**
* @var string
*
* @ORM\Column(name="body", type="text")
*/
protected $body;

/**
* @var string
*
* @ORM\Column(name="note", type="string", length=100, nullable=true)
*/
protected $note;

/**
* @var string
*
* @ORM\Column(name="is_open", type="boolean")
*/
protected $open;

/**
* @ORM\ManyToOne(targetEntity="Student", inversedBy="message")
* @ORM\JoinColumn(name="student_id", referencedColumnName="id", onDelete="CASCADE", nullable=false)
*/
protected $student;

/**
* @return integer
*/
public function getId()
{
return $this->id;
}

/**
* @param string $title
* @return Message
*/
public function setTitle($title)
{
$this->title = $title;

return $this;
}

/**
* @return string
*/
public function getTitle()
{
return $this->title;
}

/**
* @param string $body
* @return Message
*/
public function setBody($body)
{
$this->body = $body;

return $this;
}

/**
* @return string
*/
public function getBody()
{
return $this->body;
}

/**
* @param string $note
* @return Message
*/
public function setNote($note)
{
$this->note = $note;

return $this;
}

/**
* @return string
*/
public function getNote()
{
return $this->note;
}

/**
* @param string $open
* @return Message
*/
public function setOpen($open)
{
$this->open = $open;

return $this;
}

/**
* @return string
*/
public function getOpen()
{
return $this->open;
}

/**
* @param Student $student
* @return Message
*/
public function setStudent(Student $student)
{
$this->student = $student;

return $this;
}

/**
* @return Student
*/
public function getStudent()
{
return $this->student;
}
}

StudentModel class


namespace Application\BackendBundle\Model\Student;

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

/**
* @Serializer\XmlRoot("student")
*/
class StudentModel
{
/**
* @var string
*
* @Assert\NotBlank(message="The SchoolID field is required.")
*
* @Serializer\Type("string")
*/
public $schoolId;

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

/**
* @var string
*
* @Serializer\Type("string")
*/
public $nickname;

/**
* @var string
*
* @Serializer\Type("string")
*/
public $gender;

/**
* @var MessageModel
*
* @Assert\NotBlank(message="The Message field is required.")
* @Assert\Valid(traverse="true")
*
* @Serializer\Type("Application\BackendBundle\Model\Student\MessageModel")
*/
public $message;
}

MessageModel class


namespace Application\BackendBundle\Model\Student;

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

class MessageModel
{
/**
* @var string
*
* @Assert\NotBlank(message="The Subject field is required.")
*
* @Serializer\Type("string")
*/
public $subject;

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

/**
* @var string
*
* @Serializer\Type("string")
*/
public $additionalNote;

/**
* @var bool
*
* @Serializer\Type("boolean")
*/
public $isOpen = true;
}

Request payload


Bu örnek Json ve XML isteklerini kabul edebildiği için, siz isterseniz XML ile deneme yapabilirsiniz.


{
"school_id": "abc123",
"name": "Inanzzz Blog",
"nickname": "hello",
"gender": "M",
"message": {
"subject": "Test",
"body": "This is a test message",
"additional_note": "No note",
"is_open": true
}
}

Mapping sonucu


Eğer yukarıdaki isteği http://...../api/student_message adresine gönderirseniz, aşağıdaki sonucu alırsınız.


// print_r($studentModel);
Application\BackendBundle\Model\Student\StudentModel Object
(
[schoolId] => abc123
[name] => Inanzzz Blog
[nickname] => hello
[gender] => M
[message] => Application\BackendBundle\Model\Student\MessageModel Object
(
[subject] => Test
[body] => This is a test message
[additionalNote] => No note
[isOpen] => 1
)
)

// print_r($student);
Application\BackendBundle\Entity\Student Object
(
[id:protected] =>
[schoolId:protected] => ABC123
[fullName:protected] => Inanzzz Blog
[nickname:protected] => hello
[gender:protected] => M
[message:protected] => Doctrine\Common\Collections\ArrayCollection Object
(
[elements:Doctrine\Common\Collections\ArrayCollection:private] => Array
(
)
)
)

// print_r($message);
Application\BackendBundle\Entity\Message Object
(
[id:protected] =>
[title:protected] => Test
[body:protected] => This is a test message
[note:protected] => No note
[open:protected] => 1
[student:protected] =>
)

Database sonucu


mysql> SELECT student.*, message.* FROM student INNER JOIN message ON message.student_id = student.id;
Empty set (0.00 sec)

mysql> SELECT student.*, message.* FROM student INNER JOIN message ON message.student_id = student.id;
+----+----------+--------------+----------+--------+----+------------+-------+------------------------+---------+---------+
| id | schoolId | full_name | nickname | gender | id | student_id | title | body | note | is_open |
+----+----------+--------------+----------+--------+----+------------+-------+------------------------+---------+---------+
| 1 | ABC123 | Inanzzz Blog | hello | M | 1 | 1 | Test | This is a test message | No note | 1 |
+----+----------+--------------+----------+--------+----+------------+-------+------------------------+---------+---------+
1 row in set (0.00 sec)