SlideShare a Scribd company logo
CODEMEAHR!
HI,IAMARNAUD!
ANEMICMODELVERSUSMODELRICH
PLAYGROUND
Business: Human resources management
Technical: Symfony / Doctrine ORM
WHATDOWEUSUALLYDO?CRUD!
Create Read Update Delete
WESTARTWITHCREATINGANEMICMODEL
namespace AlDomain;
final class Employee
{
/** @var Uuid */
private $id;
/** @var string */
private $name;
/** @var string */
private $position;
/** @var DateTimeInterface */
private $createdAt;
/** @var DateTimeInterface */
private $deletedAt;
// Getter and Setter
}
THENAFORMANDACONTROLLER
namespace AlPresenterForm;
final class EmployeeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
$builder->add('position');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Employee::class,
));
}
}
namespace AlPresenterController;
class EmployeeController extends Controller
{
public function indexAction(Request $request)
{
}
public function showAction(Request $request)
{
}
public function createAction(Request $request)
{
}
public function updateAction(Request $request)
{
}
public function deleteAction(Request $request)
{
}
}
namespace AlPresenterController;
class EmployeeController extends Controller
{
public function createAction(Request $request)
{
$form = $this->createForm(EmployeeType::class, new Employee());
$form->handleRequest($request);
// First, data are mapped to the model and it is validated thereafter.
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->get('doctrine.orm.entity_manager');
$em->persist($form->getData());
$em->flush();
return $this->redirectToRoute('employee_list');
}
return $this->render('employee/hire.html.twig', [
'form' => $form->createView()
]);
}
}
LET'SREFACTOROURANEMICMODEL
HOWDOESESTELLETALKABOUTHER
WORK?
namespace AlDomain;
final class Employee implements EmployeeInterface
{
public function hire(Uuid $identifier, string $name, string $forPosition)
{
}
public function promote(string $toNewPosition)
{
}
public function fire()
{
}
public function retire()
{
}
}
namespace AlDomain;
final class Employee implements EmployeeInterface
{
/** @var Uuid */
private $id;
/** @var string */
private $name;
/** @var string */
private $position;
/** @var DateTimeInterface */
private $hiredAt;
/** @var DateTimeInterface */
private $firedAt = null;
/** @var DateTimeInterface */
private $retiredAt = null;
}
namespace AlDomain;
final class Employee implements EmployeeInterface
{
private function __construct(Uuid $identifier, string $name, string $position)
{
$this->id = $identifier;
$this->name = $name;
$this->position = $position;
$this->hireAt = new DateTime();
}
public static function hire(Uuid $identifier, string $name, string $forPosition)
{
return new self($identifier, $name, $forPosition, $hiredAt);
}
public function promote(string $toNewPosition)
{
$this->position = $toNewPosition;
}
public function fire()
{
$this->firedAt = new DateTime();
}
public function retire()
{
$this->retiredAt = new DateTime();
}
}
namespace AlDomain;
final class Employee implements EmployeeInterface
{
private function __construct(Uuid $identifier, string $name, string $position)
{
if ($hiredAt < new DateTime('2013-01-01')) {
throw new OutOfRangeException(
'The company did not exist before 2013-01-01'
);
}
$this->id = $identifier;
$this->name = $name;
$this->position = $position;
$this->hireAt = new DateTime();
}
public function retire()
{
if (null !== $this->fired) {
throw new Exception(
sprint('%s employee has been fired!', $this->name)
);
}
$this->retiredAt = new DateTime();
}
}
HOWCANWEUSEITINOURAPPLICATION?
FIRSTPROBLEM:HOWDOWEUSE
DOCTRINE?
We use them as query repositories
interface EmployeeRepository
{
public function findByNameAndPositionWithoutFiredPeople(
string $name,
string $position
);
}
WHATISAREPOSITORY?
«A repository behaves like a collection of unique entities
without taking care about the storage»
WHATDOESITLOOKLIKE?
namespace AlInfrastructure;
final class EmployeeRepository implements EmployeeRepositoryInterface
{
public function get(Uuid $identifier)
{
}
public function add(EmployeeInterface $employee)
{
}
public function remove(EmployeeInterface $employee)
{
}
}
namespace AlInfrastructure;
final class EmployeeRepository implements EmployeeRepositoryInterface
{
/** @var EntityManagerInterface */
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
}
namespace AlInfrastructure;
final class EmployeeRepository implements EmployeeRepositoryInterface
{
public function get(Uuid $identifier)
{
$employee = $this->entityManager->find(
Employee::class,
$identifier->toString()
);
if (null === $employee) {
throw new NonExistingEmployee($identifier->toString());
}
return $employee;
}
public function add(EmployeeInterface $employee)
{
$this->entityManager->persist($employee);
$this->entityManager->flush($employee);
}
public function remove(EmployeeInterface $employee)
{
$this->entityManager->remove($employee);
$this->entityManager->flush($employee);
}
}
ISITMANDATORYTOUSESETTER?NO!
Doctrine uses the re ection to map data
Doctrine does not instantiate objects (Ocramius/Instantiator)
SECONDPROBLEM:FORMCOMPONENT
PropertyAccessor is used to map data, it needs public properties or setter.
COMMANDTOTHERESCUE!
« A Command is an object that represents all the information
needed to call a method.»
LET’SCREATEACOMMAND
namespace AlApplication;
final class HireEmployeeCommand
{
/** @var string */
public $name = '';
/** @var string */
public $position = '';
}
LET’SUPDATEOURCONTROLLER
namespace AlPresenterController;
class EmployeeController extends Controller
{
public function hireAction(Request $request)
{
$form = $this->createForm(EmployeeType::class, new HireEmployeeCommand());
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$employeeCommand = $form->getData();
$employee = Employee::hire(
Uuid::uuid4(),
$employeeCommand->getName(),
$employeeCommand->getPosition()
);
$this->get('employee.repository')->add($employee);
return $this->redirectToRoute('employee_list');
}
return $this->render('employee/hire.html.twig', [
'form' => $form->createView()
]);
}
}
NOW,ESTELLEWANTSTOIMPORT
EMPLOYEES!
COMMANDBUSTOTHERESCUE
«A Command Bus accepts a Command object and delegates it
to a Command Handler.»
LET’SUPDATEOURCONTROLLER
Here, we are going to use simple-bus/message-bus
namespace AlPresenterController;
class EmployeeController extends Controller
{
public function hireAction(Request $request)
{
$form = $this->createForm(EmployeeType::class, new HireEmployeeCommand());
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$employeeCommand = $form->getData();
try {
$this->get('command_bus')->handle($employeeCommand);
} catch (Exception $e) {
$this->addFlash('error', 'An error occurs!');
}
return $this->redirectToRoute('employee_list');
}
return $this->render('employee/hire.html.twig', [
'form' => $form->createView()
]);
}
}
LET’SCREATEACOMMANDHANDLER
namespace AlApplicationHandler;
final class HireEmployeeHandler
{
/** @var EmployeeRepositoryInterface */
private $employeeRepository;
public function __construct(EmployeeRepositoryInterface $employeeRepository)
{
$this->employeeRepository = $employeeRepository;
}
public function handle(HireEmployee $command)
{
$employee = Employee::hire(
Uuid::uuid4(),
$command->getName(),
$command->getPosition()
);
$this->employeeRepository->add($employee);
}
}
ESTELLEWON'TUSEPHPMYADMINTOREAD
DATA!
DOOURMODELSNEEDGETTER?NOT
NECESSARILY!
DTO(DATATRANSFEROBJECT)TOTHE
RESCUE!
«A Data Transfer Object is an object that is used to
encapsulate data, and to send it from one subsystem of an
application to another.»
DOCTRINE(>=2.4):OPERATORNEW
namespace AlApplication;
final class EmployeeDTO
{
/** string */
private $id;
/** string */
private $name;
/** string */
private $position;
public function __construct(string $id, string $name, string $position)
{
$this->id = $id;
$this->name = $name;
$this->position = $position;
}
// Define an accessor for each property
}}
namespace AlInfrastructure;
final class EmployeeSearcher
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function findAll()
{
$queryBuilder = $this->entityManager->createQueryBuilder()
->from(Employee::class, 'e')
->select(
sprintf('NEW %s(e.id, e.name, e.position)', EmployeeDTO::class)
);
// NEW AlApplicationEmployeeDTO(e.id, e.name, e.position)
return $queryBuilder->getQuery()->getResult();
}
}
WECANSEARCHANDDISPLAYEMPLOYEE
DATA!
MISREADINGOFDOCTRINE/DDD/CQRS
Domain Driven Design
Command Query Responsibility Segregation
WHATISTHEBESTSOLUTION?
It depends on what you want!
THAT'STHEEND!
https://joind.in/talk/6c974
THANKYOU!QUESTIONS?
aRn0D _aRn0D
https://joind.in/talk/6c974

More Related Content

What's hot

R57shell
R57shellR57shell
R57shell
ady36
 
Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixtures
Bill Chang
 

What's hot (20)

Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
 
Writing Sensible Code
Writing Sensible CodeWriting Sensible Code
Writing Sensible Code
 
Pim Elshoff "Technically DDD"
Pim Elshoff "Technically DDD"Pim Elshoff "Technically DDD"
Pim Elshoff "Technically DDD"
 
11. CodeIgniter vederea unei singure inregistrari
11. CodeIgniter vederea unei singure inregistrari11. CodeIgniter vederea unei singure inregistrari
11. CodeIgniter vederea unei singure inregistrari
 
Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
 
購物車程式架構簡介
購物車程式架構簡介購物車程式架構簡介
購物車程式架構簡介
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
 
R57shell
R57shellR57shell
R57shell
 
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016
 
Taming Command Bus
Taming Command BusTaming Command Bus
Taming Command Bus
 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes
 
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHPKonf
 
Object Calisthenics Applied to PHP
Object Calisthenics Applied to PHPObject Calisthenics Applied to PHP
Object Calisthenics Applied to PHP
 
Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixtures
 
PhpUnit - The most unknown Parts
PhpUnit - The most unknown PartsPhpUnit - The most unknown Parts
PhpUnit - The most unknown Parts
 
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHP Yo...
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHP Yo...“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHP Yo...
“Writing code that lasts” … or writing code you won’t hate tomorrow. - PHP Yo...
 
Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5
 
画像Hacks
画像Hacks画像Hacks
画像Hacks
 
The command dispatcher pattern
The command dispatcher patternThe command dispatcher pattern
The command dispatcher pattern
 
Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)
Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)
Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)
 

Similar to Code moi une RH! (PHP tour 2017)

Similar to Code moi une RH! (PHP tour 2017) (20)

Et si on en finissait avec CRUD ?
Et si on en finissait avec CRUD ?Et si on en finissait avec CRUD ?
Et si on en finissait avec CRUD ?
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your Code
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hacking
 
Drupal csu-open atriumname
Drupal csu-open atriumnameDrupal csu-open atriumname
Drupal csu-open atriumname
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form component
 
14. CodeIgniter adaugarea inregistrarilor
14. CodeIgniter adaugarea inregistrarilor14. CodeIgniter adaugarea inregistrarilor
14. CodeIgniter adaugarea inregistrarilor
 
Command Bus To Awesome Town
Command Bus To Awesome TownCommand Bus To Awesome Town
Command Bus To Awesome Town
 
laravel tricks in 50minutes
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutes
 
Unittests für Dummies
Unittests für DummiesUnittests für Dummies
Unittests für Dummies
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2
 
Drupal Development (Part 2)
Drupal Development (Part 2)Drupal Development (Part 2)
Drupal Development (Part 2)
 
Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
 
Михаил Крайнюк - Form API + Drupal 8: Form and AJAX
Михаил Крайнюк - Form API + Drupal 8: Form and AJAXМихаил Крайнюк - Form API + Drupal 8: Form and AJAX
Михаил Крайнюк - Form API + Drupal 8: Form and AJAX
 
Oops in php
Oops in phpOops in php
Oops in php
 
BEAR DI
BEAR DIBEAR DI
BEAR DI
 
Drupal 8: Forms
Drupal 8: FormsDrupal 8: Forms
Drupal 8: Forms
 
Migrare da symfony 1 a Symfony2
 Migrare da symfony 1 a Symfony2  Migrare da symfony 1 a Symfony2
Migrare da symfony 1 a Symfony2
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web services
 

More from Arnaud Langlade

More from Arnaud Langlade (6)

What is the difference between a good and a bad repository? (Forum PHP 2018)
What is the difference between a good and a bad repository? (Forum PHP 2018)What is the difference between a good and a bad repository? (Forum PHP 2018)
What is the difference between a good and a bad repository? (Forum PHP 2018)
 
Php trollons mais trollons bien (Bdx.io 2015)
Php trollons mais trollons bien (Bdx.io 2015)Php trollons mais trollons bien (Bdx.io 2015)
Php trollons mais trollons bien (Bdx.io 2015)
 
Programmation STUPID vs SOLID (PHP Meetup)
Programmation STUPID vs SOLID (PHP Meetup)Programmation STUPID vs SOLID (PHP Meetup)
Programmation STUPID vs SOLID (PHP Meetup)
 
Php spec en 5 minutes
Php spec en 5 minutesPhp spec en 5 minutes
Php spec en 5 minutes
 
Sylius en 5 minutes
Sylius en 5 minutesSylius en 5 minutes
Sylius en 5 minutes
 
Développer avec le sylius resourcebundle (Symfony live Paris 2015)
Développer avec le sylius resourcebundle (Symfony live Paris 2015) Développer avec le sylius resourcebundle (Symfony live Paris 2015)
Développer avec le sylius resourcebundle (Symfony live Paris 2015)
 

Recently uploaded

AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...
AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...
AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...
Alluxio, Inc.
 

Recently uploaded (20)

Field Employee Tracking System| MiTrack App| Best Employee Tracking Solution|...
Field Employee Tracking System| MiTrack App| Best Employee Tracking Solution|...Field Employee Tracking System| MiTrack App| Best Employee Tracking Solution|...
Field Employee Tracking System| MiTrack App| Best Employee Tracking Solution|...
 
Agnieszka Andrzejewska - BIM School Course in Kraków
Agnieszka Andrzejewska - BIM School Course in KrakówAgnieszka Andrzejewska - BIM School Course in Kraków
Agnieszka Andrzejewska - BIM School Course in Kraków
 
Exploring Innovations in Data Repository Solutions - Insights from the U.S. G...
Exploring Innovations in Data Repository Solutions - Insights from the U.S. G...Exploring Innovations in Data Repository Solutions - Insights from the U.S. G...
Exploring Innovations in Data Repository Solutions - Insights from the U.S. G...
 
Climate Science Flows: Enabling Petabyte-Scale Climate Analysis with the Eart...
Climate Science Flows: Enabling Petabyte-Scale Climate Analysis with the Eart...Climate Science Flows: Enabling Petabyte-Scale Climate Analysis with the Eart...
Climate Science Flows: Enabling Petabyte-Scale Climate Analysis with the Eart...
 
Breaking the Code : A Guide to WhatsApp Business API.pdf
Breaking the Code : A Guide to WhatsApp Business API.pdfBreaking the Code : A Guide to WhatsApp Business API.pdf
Breaking the Code : A Guide to WhatsApp Business API.pdf
 
How Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptxHow Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptx
 
Into the Box 2024 - Keynote Day 2 Slides.pdf
Into the Box 2024 - Keynote Day 2 Slides.pdfInto the Box 2024 - Keynote Day 2 Slides.pdf
Into the Box 2024 - Keynote Day 2 Slides.pdf
 
Globus Connect Server Deep Dive - GlobusWorld 2024
Globus Connect Server Deep Dive - GlobusWorld 2024Globus Connect Server Deep Dive - GlobusWorld 2024
Globus Connect Server Deep Dive - GlobusWorld 2024
 
AI/ML Infra Meetup | ML explainability in Michelangelo
AI/ML Infra Meetup | ML explainability in MichelangeloAI/ML Infra Meetup | ML explainability in Michelangelo
AI/ML Infra Meetup | ML explainability in Michelangelo
 
Designing for Privacy in Amazon Web Services
Designing for Privacy in Amazon Web ServicesDesigning for Privacy in Amazon Web Services
Designing for Privacy in Amazon Web Services
 
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...
 
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
 
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.ILBeyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
 
Gamify Your Mind; The Secret Sauce to Delivering Success, Continuously Improv...
Gamify Your Mind; The Secret Sauce to Delivering Success, Continuously Improv...Gamify Your Mind; The Secret Sauce to Delivering Success, Continuously Improv...
Gamify Your Mind; The Secret Sauce to Delivering Success, Continuously Improv...
 
2024 RoOUG Security model for the cloud.pptx
2024 RoOUG Security model for the cloud.pptx2024 RoOUG Security model for the cloud.pptx
2024 RoOUG Security model for the cloud.pptx
 
AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...
AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...
AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...
 
A Comprehensive Look at Generative AI in Retail App Testing.pdf
A Comprehensive Look at Generative AI in Retail App Testing.pdfA Comprehensive Look at Generative AI in Retail App Testing.pdf
A Comprehensive Look at Generative AI in Retail App Testing.pdf
 
Vitthal Shirke Microservices Resume Montevideo
Vitthal Shirke Microservices Resume MontevideoVitthal Shirke Microservices Resume Montevideo
Vitthal Shirke Microservices Resume Montevideo
 
BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024
 
Cyaniclab : Software Development Agency Portfolio.pdf
Cyaniclab : Software Development Agency Portfolio.pdfCyaniclab : Software Development Agency Portfolio.pdf
Cyaniclab : Software Development Agency Portfolio.pdf
 

Code moi une RH! (PHP tour 2017)

  • 3.
  • 5. PLAYGROUND Business: Human resources management Technical: Symfony / Doctrine ORM
  • 8. namespace AlDomain; final class Employee { /** @var Uuid */ private $id; /** @var string */ private $name; /** @var string */ private $position; /** @var DateTimeInterface */ private $createdAt; /** @var DateTimeInterface */ private $deletedAt; // Getter and Setter }
  • 10. namespace AlPresenterForm; final class EmployeeType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('name'); $builder->add('position'); } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( 'data_class' => Employee::class, )); } }
  • 11. namespace AlPresenterController; class EmployeeController extends Controller { public function indexAction(Request $request) { } public function showAction(Request $request) { } public function createAction(Request $request) { } public function updateAction(Request $request) { } public function deleteAction(Request $request) { } }
  • 12. namespace AlPresenterController; class EmployeeController extends Controller { public function createAction(Request $request) { $form = $this->createForm(EmployeeType::class, new Employee()); $form->handleRequest($request); // First, data are mapped to the model and it is validated thereafter. if ($form->isSubmitted() && $form->isValid()) { $em = $this->get('doctrine.orm.entity_manager'); $em->persist($form->getData()); $em->flush(); return $this->redirectToRoute('employee_list'); } return $this->render('employee/hire.html.twig', [ 'form' => $form->createView() ]); } }
  • 13.
  • 16.
  • 17. namespace AlDomain; final class Employee implements EmployeeInterface { public function hire(Uuid $identifier, string $name, string $forPosition) { } public function promote(string $toNewPosition) { } public function fire() { } public function retire() { } }
  • 18. namespace AlDomain; final class Employee implements EmployeeInterface { /** @var Uuid */ private $id; /** @var string */ private $name; /** @var string */ private $position; /** @var DateTimeInterface */ private $hiredAt; /** @var DateTimeInterface */ private $firedAt = null; /** @var DateTimeInterface */ private $retiredAt = null; }
  • 19. namespace AlDomain; final class Employee implements EmployeeInterface { private function __construct(Uuid $identifier, string $name, string $position) { $this->id = $identifier; $this->name = $name; $this->position = $position; $this->hireAt = new DateTime(); } public static function hire(Uuid $identifier, string $name, string $forPosition) { return new self($identifier, $name, $forPosition, $hiredAt); } public function promote(string $toNewPosition) { $this->position = $toNewPosition; } public function fire() { $this->firedAt = new DateTime(); } public function retire() { $this->retiredAt = new DateTime(); } }
  • 20. namespace AlDomain; final class Employee implements EmployeeInterface { private function __construct(Uuid $identifier, string $name, string $position) { if ($hiredAt < new DateTime('2013-01-01')) { throw new OutOfRangeException( 'The company did not exist before 2013-01-01' ); } $this->id = $identifier; $this->name = $name; $this->position = $position; $this->hireAt = new DateTime(); } public function retire() { if (null !== $this->fired) { throw new Exception( sprint('%s employee has been fired!', $this->name) ); } $this->retiredAt = new DateTime(); } }
  • 22. FIRSTPROBLEM:HOWDOWEUSE DOCTRINE? We use them as query repositories interface EmployeeRepository { public function findByNameAndPositionWithoutFiredPeople( string $name, string $position ); }
  • 23. WHATISAREPOSITORY? «A repository behaves like a collection of unique entities without taking care about the storage»
  • 25. namespace AlInfrastructure; final class EmployeeRepository implements EmployeeRepositoryInterface { public function get(Uuid $identifier) { } public function add(EmployeeInterface $employee) { } public function remove(EmployeeInterface $employee) { } }
  • 26. namespace AlInfrastructure; final class EmployeeRepository implements EmployeeRepositoryInterface { /** @var EntityManagerInterface */ private $entityManager; public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; } }
  • 27. namespace AlInfrastructure; final class EmployeeRepository implements EmployeeRepositoryInterface { public function get(Uuid $identifier) { $employee = $this->entityManager->find( Employee::class, $identifier->toString() ); if (null === $employee) { throw new NonExistingEmployee($identifier->toString()); } return $employee; } public function add(EmployeeInterface $employee) { $this->entityManager->persist($employee); $this->entityManager->flush($employee); } public function remove(EmployeeInterface $employee) { $this->entityManager->remove($employee); $this->entityManager->flush($employee); } }
  • 28. ISITMANDATORYTOUSESETTER?NO! Doctrine uses the re ection to map data Doctrine does not instantiate objects (Ocramius/Instantiator)
  • 29. SECONDPROBLEM:FORMCOMPONENT PropertyAccessor is used to map data, it needs public properties or setter.
  • 30. COMMANDTOTHERESCUE! « A Command is an object that represents all the information needed to call a method.»
  • 32. namespace AlApplication; final class HireEmployeeCommand { /** @var string */ public $name = ''; /** @var string */ public $position = ''; }
  • 34. namespace AlPresenterController; class EmployeeController extends Controller { public function hireAction(Request $request) { $form = $this->createForm(EmployeeType::class, new HireEmployeeCommand()); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $employeeCommand = $form->getData(); $employee = Employee::hire( Uuid::uuid4(), $employeeCommand->getName(), $employeeCommand->getPosition() ); $this->get('employee.repository')->add($employee); return $this->redirectToRoute('employee_list'); } return $this->render('employee/hire.html.twig', [ 'form' => $form->createView() ]); } }
  • 35.
  • 37. COMMANDBUSTOTHERESCUE «A Command Bus accepts a Command object and delegates it to a Command Handler.»
  • 38. LET’SUPDATEOURCONTROLLER Here, we are going to use simple-bus/message-bus
  • 39. namespace AlPresenterController; class EmployeeController extends Controller { public function hireAction(Request $request) { $form = $this->createForm(EmployeeType::class, new HireEmployeeCommand()); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $employeeCommand = $form->getData(); try { $this->get('command_bus')->handle($employeeCommand); } catch (Exception $e) { $this->addFlash('error', 'An error occurs!'); } return $this->redirectToRoute('employee_list'); } return $this->render('employee/hire.html.twig', [ 'form' => $form->createView() ]); } }
  • 41. namespace AlApplicationHandler; final class HireEmployeeHandler { /** @var EmployeeRepositoryInterface */ private $employeeRepository; public function __construct(EmployeeRepositoryInterface $employeeRepository) { $this->employeeRepository = $employeeRepository; } public function handle(HireEmployee $command) { $employee = Employee::hire( Uuid::uuid4(), $command->getName(), $command->getPosition() ); $this->employeeRepository->add($employee); } }
  • 42.
  • 45. DTO(DATATRANSFEROBJECT)TOTHE RESCUE! «A Data Transfer Object is an object that is used to encapsulate data, and to send it from one subsystem of an application to another.»
  • 47. namespace AlApplication; final class EmployeeDTO { /** string */ private $id; /** string */ private $name; /** string */ private $position; public function __construct(string $id, string $name, string $position) { $this->id = $id; $this->name = $name; $this->position = $position; } // Define an accessor for each property }}
  • 48. namespace AlInfrastructure; final class EmployeeSearcher { private $entityManager; public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; } public function findAll() { $queryBuilder = $this->entityManager->createQueryBuilder() ->from(Employee::class, 'e') ->select( sprintf('NEW %s(e.id, e.name, e.position)', EmployeeDTO::class) ); // NEW AlApplicationEmployeeDTO(e.id, e.name, e.position) return $queryBuilder->getQuery()->getResult(); } }
  • 50.