Code moi une RH! (PHP tour 2017)

54
CODE ME A HR!

Transcript of Code moi une RH! (PHP tour 2017)

Page 1: Code moi une RH! (PHP tour 2017)

CODE ME A HR!

Page 2: Code moi une RH! (PHP tour 2017)

HI, I AM ARNAUD!

Page 3: Code moi une RH! (PHP tour 2017)
Page 4: Code moi une RH! (PHP tour 2017)

ANEMIC MODEL VERSUS MODEL RICH

Page 5: Code moi une RH! (PHP tour 2017)

PLAYGROUNDBusiness: Human resources management

Technical: Symfony / Doctrine ORM

Page 6: Code moi une RH! (PHP tour 2017)

WHAT DO WE USUALLY DO? CRUD!Create Read Update Delete

Page 7: Code moi une RH! (PHP tour 2017)

WE START WITH CREATING ANEMIC MODEL

Page 8: Code moi une RH! (PHP tour 2017)

namespace Al\Domain;

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 }

Page 9: Code moi une RH! (PHP tour 2017)

THEN A FORM AND A CONTROLLER

Page 10: Code moi une RH! (PHP tour 2017)

namespace Al\Presenter\Form;

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, )); }}

Page 11: Code moi une RH! (PHP tour 2017)

namespace Al\Presenter\Controller;

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) { }}

Page 12: Code moi une RH! (PHP tour 2017)

namespace Al\Presenter\Controller;

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() ]); }}

Page 13: Code moi une RH! (PHP tour 2017)
Page 14: Code moi une RH! (PHP tour 2017)

LET'S REFACTOR OUR ANEMIC MODEL

Page 15: Code moi une RH! (PHP tour 2017)

HOW DOES ESTELLE TALK ABOUT HERWORK?

Page 16: Code moi une RH! (PHP tour 2017)
Page 17: Code moi une RH! (PHP tour 2017)

namespace Al\Domain;

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() { }}

Page 18: Code moi une RH! (PHP tour 2017)

namespace Al\Domain;

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; }

Page 19: Code moi une RH! (PHP tour 2017)

namespace Al\Domain;

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(); }}

Page 20: Code moi une RH! (PHP tour 2017)

namespace Al\Domain;

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(); }}

Page 21: Code moi une RH! (PHP tour 2017)

HOW CAN WE USE IT IN OUR APPLICATION?

Page 22: Code moi une RH! (PHP tour 2017)

FIRST PROBLEM: HOW DO WE USEDOCTRINE?

We use them as query repositories

interface EmployeeRepository { public function findByNameAndPositionWithoutFiredPeople( string $name, string $position );}

Page 23: Code moi une RH! (PHP tour 2017)

WHAT IS A REPOSITORY?«A repository behaves like a collection of unique entities

without taking care about the storage»

Page 24: Code moi une RH! (PHP tour 2017)

WHAT DOES IT LOOK LIKE?

Page 25: Code moi une RH! (PHP tour 2017)

namespace Al\Infrastructure;

final class EmployeeRepository implements EmployeeRepositoryInterface { public function get(Uuid $identifier) { }

public function add(EmployeeInterface $employee) { }

public function remove(EmployeeInterface $employee) { }}

Page 26: Code moi une RH! (PHP tour 2017)

namespace Al\Infrastructure;

final class EmployeeRepository implements EmployeeRepositoryInterface { /** @var EntityManagerInterface */ private $entityManager;

public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; }}

Page 27: Code moi une RH! (PHP tour 2017)

namespace Al\Infrastructure;

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); }}

Page 28: Code moi une RH! (PHP tour 2017)

IS IT MANDATORY TO USE SETTER? NO!Doctrine uses the re�ection to map data

Doctrine does not instantiate objects (Ocramius/Instantiator)

Page 29: Code moi une RH! (PHP tour 2017)

SECOND PROBLEM: FORM COMPONENTPropertyAccessor is used to map data, it needs public properties or setter.

Page 30: Code moi une RH! (PHP tour 2017)

COMMAND TO THE RESCUE!« A Command is an object that represents all the information

needed to call a method.»

Page 31: Code moi une RH! (PHP tour 2017)

LET’S CREATE A COMMAND

Page 32: Code moi une RH! (PHP tour 2017)

namespace Al\Application;

final class HireEmployeeCommand { /** @var string */ public $name = '';

/** @var string */ public $position = ''; }

Page 33: Code moi une RH! (PHP tour 2017)

LET’S UPDATE OUR CONTROLLER

Page 34: Code moi une RH! (PHP tour 2017)

namespace Al\Presenter\Controller;

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() ]); }}

Page 35: Code moi une RH! (PHP tour 2017)
Page 36: Code moi une RH! (PHP tour 2017)

NOW, ESTELLE WANTS TO IMPORTEMPLOYEES!

Page 37: Code moi une RH! (PHP tour 2017)

COMMAND BUS TO THE RESCUE«A Command Bus accepts a Command object and delegates it

to a Command Handler.»

Page 38: Code moi une RH! (PHP tour 2017)

LET’S UPDATE OUR CONTROLLERHere, we are going to use simple-bus/message-bus

Page 39: Code moi une RH! (PHP tour 2017)

namespace Al\Presenter\Controller;

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() ]); }}

Page 40: Code moi une RH! (PHP tour 2017)

LET’S CREATE A COMMAND HANDLER

Page 41: Code moi une RH! (PHP tour 2017)

namespace Al\Application\Handler;

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); }}

Page 42: Code moi une RH! (PHP tour 2017)
Page 43: Code moi une RH! (PHP tour 2017)

ESTELLE WON'T USE PHPMYADMIN TO READDATA!

Page 44: Code moi une RH! (PHP tour 2017)

DO OUR MODELS NEED GETTER? NOTNECESSARILY !

Page 45: Code moi une RH! (PHP tour 2017)

DTO (DATA TRANSFER OBJECT) TO THERESCUE!

«A Data Transfer Object is an object that is used toencapsulate data, and to send it from one subsystem of an

application to another.»

Page 46: Code moi une RH! (PHP tour 2017)

DOCTRINE (>=2.4): OPERATOR NEW

Page 47: Code moi une RH! (PHP tour 2017)

namespace Al\Application;

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 }}

Page 48: Code moi une RH! (PHP tour 2017)

namespace Al\Infrastructure;

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 Al\Application\EmployeeDTO(e.id, e.name, e.position)

return $queryBuilder->getQuery()->getResult(); }}

Page 49: Code moi une RH! (PHP tour 2017)

WE CAN SEARCH AND DISPLAY EMPLOYEEDATA!

Page 50: Code moi une RH! (PHP tour 2017)
Page 51: Code moi une RH! (PHP tour 2017)

MISREADING OF DOCTRINE / DDD / CQRSDomain Driven Design

Command Query Responsibility Segregation

Page 52: Code moi une RH! (PHP tour 2017)

WHAT IS THE BEST SOLUTION?It depends on what you want!

Page 53: Code moi une RH! (PHP tour 2017)

THAT'S THE END!https://joind.in/talk/6c974

Page 54: Code moi une RH! (PHP tour 2017)

THANK YOU! QUESTIONS?

aRn0D _aRn0Dhttps://joind.in/talk/6c974