24/02/2018 - DOCTRINE, SYMFONY
Doctrine entity inheritance mapping özelliği, birden fazla entity classın aynı alan isimlerini kullandığı zaman faydalı olur. Farklı alanları ait oldukları class içinde, aynı alanları ise abstract
class içinde tutarak, kod tekrarlama probleminden kurtulmamızı sağlar. Sonuç olarak elimizde tüm alanların barındırıldığı tek bir tablomuz olur. Daha fazla bilgi için Inheritence Mapping sayfasını okuyabilirsiniz.
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: services.yml }
- { resource: '@AppBundle/Resources/config/' }
...
services:
_defaults:
autowire: true
AppBundle\:
resource: '../../src/AppBundle/*'
exclude: '../../src/AppBundle/{Entity,Repository,Tests}'
declare(strict_types=1);
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="employee", indexes={@ORM\Index(name="type_idx", columns={"type"})})
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="type", type="string", length=3)
* @ORM\DiscriminatorMap({
* "Acc"="Accountant",
* "Dev"="Developer",
* "Mar"="Marketer"
* })
*/
abstract class Employee
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(name="id", type="integer")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="firstname", type="string", length=100)
*/
private $firstname;
/**
* @var string
*
* @ORM\Column(name="lastname", type="string", length=100)
*/
private $lastname;
/**
* @var string
*
* @ORM\Column(name="level", type="string", length=100)
*/
private $level;
public function getId(): int
{
return $this->id;
}
public function setFirstname(string $firstname): self
{
$this->firstname = $firstname;
return $this;
}
public function getFirstname(): string
{
return $this->firstname;
}
public function setLastname(string $lastname): self
{
$this->lastname = $lastname;
return $this;
}
public function getLastname(): string
{
return $this->lastname;
}
public function setLevel(string $level): self
{
$this->level = $level;
return $this;
}
public function getLevel(): string
{
return $this->level;
}
}
declare(strict_types=1);
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Accountant extends Employee
{
}
declare(strict_types=1);
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Developer extends Employee
{
/**
* @var string
*
* @ORM\Column(name="calibre", type="string", length=100)
*/
private $calibre;
public function setCalibre(string $calibre): self
{
$this->calibre = $calibre;
return $this;
}
public function getCalibre(): string
{
return $this->calibre;
}
}
declare(strict_types=1);
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Marketer extends Employee
{
/**
* @var bool
*
* @ORM\Column(name="is_internal", type="boolean")
*/
private $isInternal = true;
public function setIsInternal(bool $isInternal): self
{
$this->isInternal = $isInternal;
return $this;
}
public function getIsInternal(): bool
{
return $this->isInternal;
}
}
declare(strict_types=1);
namespace AppBundle\Repository;
use AppBundle\Entity\Employee;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
class EmployeeRepository
{
private $entityRepository;
private $entityManager;
public function __construct(
EntityRepository $entityRepository,
EntityManagerInterface $entityManager
) {
$this->entityRepository = $entityRepository;
$this->entityManager = $entityManager;
}
public function findOneById(int $id): ?Employee
{
return $this->entityRepository->createQueryBuilder('e')
->where('e.id = :id')
->setParameter('id', $id)
->getQuery()
->getOneOrNullResult();
}
public function insert(Employee $employee): void
{
$this->entityManager->persist($employee);
$this->entityManager->flush();
}
}
services:
_defaults:
autowire: true
autoconfigure: true
public: false
app.entity_repository.customer:
class: Doctrine\ORM\EntityRepository
factory: [ "@doctrine.orm.entity_manager", getRepository ]
arguments:
- AppBundle\Entity\Employee
AppBundle\Repository\EmployeeRepository:
arguments:
$entityRepository: "@app.entity_repository.customer"
declare(strict_types=1);
namespace AppBundle\Controller;
use AppBundle\Entity\Accountant;
use AppBundle\Entity\Developer;
use AppBundle\Entity\Employee;
use AppBundle\Entity\Marketer;
use AppBundle\Repository\EmployeeRepository;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
class DefaultController
{
private $employeeRepository;
public function __construct(
EmployeeRepository $employeeRepository
) {
$this->employeeRepository = $employeeRepository;
}
/**
* @Method({"GET"})
* @Route("/{id}")
*
* @return Response
*/
public function getOneAction(int $id)
{
$employee = $this->employeeRepository->findOneById($id);
if (!$employee instanceof Employee) {
throw new BadRequestHttpException('Employee not found.');
}
return new Response(get_class($employee), Response::HTTP_OK);
}
/**
* @Method({"POST"})
* @Route("/accountants")
*
* @return Response
*/
public function createAccountantAction(Request $request)
{
$fields = json_decode($request->getContent(), true);
$accountant = $this->createEmployee(new Accountant(), $fields);
$this->employeeRepository->insert($accountant);
return new Response(null, Response::HTTP_CREATED);
}
/**
* @Method({"POST"})
* @Route("/developers")
*
* @return Response
*/
public function createDeveloperAction(Request $request)
{
$fields = json_decode($request->getContent(), true);
/** @var Developer $developer */
$developer = $this->createEmployee(new Developer(), $fields);
$developer->setCalibre($fields['calibre']);
$this->employeeRepository->insert($developer);
return new Response(null, Response::HTTP_CREATED);
}
/**
* @Method({"POST"})
* @Route("/marketers")
*
* @return Response
*/
public function createMarketerAction(Request $request)
{
$fields = json_decode($request->getContent(), true);
/** @var Marketer $marketer */
$marketer = $this->createEmployee(new Marketer(), $fields);
$marketer->setIsInternal($fields['is_internal']);
$this->employeeRepository->insert($marketer);
return new Response(null, Response::HTTP_CREATED);
}
private function createEmployee(Employee $employee, array $fields): Employee
{
$employee->setFirstname($fields['firstname']);
$employee->setLastname($fields['lastname']);
$employee->setLevel($fields['level']);
return $employee;
}
}
POST /accountants
{
"firstname": "John",
"lastname": "Travolta",
"level": "Senior"
}
201 Created
POST /developers
{
"firstname": "Robert",
"lastname": "De Niro",
"level": "Senior",
"calibre": "Backend"
}
201 Created
POST /marketers
{
"firstname": "Al",
"lastname": "Pacino",
"level": "Junior",
"is_internal": false
}
201 Created
GET /1
200 OK
AppBundle\Entity\Accountant
GET /2
200 OK
AppBundle\Entity\Developer
GET /3
200 OK
AppBundle\Entity\Marketer
GET /4
400 Bad Request
Employee not found.
Aşağıda'da gördüğümüz gibi tüm alanların barındırıldığı tek bir tablomuz var ve type
ayırıcı alanımızdır.
CREATE TABLE `employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`firstname` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`lastname` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`level` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`type` varchar(3) COLLATE utf8_unicode_ci NOT NULL,
`calibre` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`is_internal` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `type_idx` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
mysql> SELECT * FROM employee;
+----+-----------+----------+--------+------+---------+-------------+
| id | firstname | lastname | level | type | calibre | is_internal |
+----+-----------+----------+--------+------+---------+-------------+
| 1 | John | Travolta | Senior | Acc | NULL | NULL |
| 2 | Robert | De Niro | Senior | Dev | Backend | NULL |
| 3 | Al | Pacino | Junior | Mar | NULL | 0 |
+----+-----------+----------+--------+------+---------+-------------+
3 rows in set (0.00 sec)