16/08/2015 - SYMFONY
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 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
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 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);
}
}
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;
}
}
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;
}
}
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;
}
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;
}
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
}
}
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] =>
)
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)