Divide and Conquer

Divide and Conquer

E9612cd342dbddff6640b99db21deee7?s=128

Andreas Hucks

January 30, 2017
Tweet

Transcript

  1. Divide And Conquer PHPBenelux 2017 Andreas Hucks

  2. @meandmymonkey Andreas Hucks CTO @

  3. What this talk is not about: • DDD • Generic

    Package Design (but these topics will play a role)
  4. A Generic Framework App

  5. None
  6. None
  7. None
  8. /**
 * @Route(path="/", name="list")
 */
 public function indexAction()
 {
 $robots

    = $this
 ->getDoctrine()
 ->getRepository('AppBundle:Robot')
 ->findAll()
 ;
 
 return $this->render(
 '@App/robot/index.html.twig',
 [
 'robots' => $robots
 ]
 );
 }
  9. /**
 * @Route(path="/{id}", name="edit")
 */
 public function editAction(Request $request, Robot

    $robot)
 {
 $form = $this->createForm(RobotType::class, $robot);
 $form->handleRequest($request);
 
 if ($form->isSubmitted() && $form->isValid()) {
 $this->getDoctrine()->getManager()->flush();
 
 return $this->redirectToRoute('list');
 }
  10. /**
 * @ORM\Entity(repositoryClass="…")
 * @ORM\Table(name="robots")
 */
 class Robot
 {
 public

    static $propusionTypes = [
 'legs' => 'Can walk',
 'wheels' => 'Drives',
 'tracks' => 'Tracked Vehicle',
 'jetpack' => 'Hovers',
 'weird' => 'Why would you build that'
 ];
 
 /**
 *@ORM\Column(type="integer")
 * @ORM\Id()
 * @ORM\GeneratedValue()
 */
 private $id;
  11. public function validatePropulsion( ExecutionContextInterface $context, $payload ) {
 if (!in_array($this->getPropulsion(),

    self::$propusions)) { $context->buildViolation('Unknown propulsion type!')
 ->atPath('propulsion')
 ->addViolation();
 }
 }
  12. namespace AppBundle\Entity;
 
 use Doctrine\ORM\Mapping as ORM;
 use Symfony\Component\Security\Core\User\UserInterface;
 use

    Symfony\Component\Validator\Constraints as Assert;
 
 /**
 * @ORM\Entity()
 * @ORM\Table(name="mechanics")
 */
 class Mechanic implements UserInterface
 {
 // …
  13. The Problem

  14. None
  15. Dependencies of your Code • to Validator • to Doctrine

    • to Security Layer
  16. Direct Access to Entities • from View • in Forms

  17. Other Concerns • Mixing responsibilities • Controller uses Doctrine directly

  18. Why Separate?

  19. Clean Packages • Separate your business code from your framework

    • … or in fact, any other 3rd party libs • Separate your own layers/packages (Domain, Application, Infrastructure…)
  20. Extracting the Domain

  21. None
  22. None
  23. None
  24. Domain\Entity\Robot:
 type: entity
 table: robots
 repositoryClass: Domain\Repository\RobotRepository
 id:
 id:
 type:

    uuid
 length: 255
 fields:
 name:
 type: string
 length: 255
 nullable: false
 […]
 embedded:
 propulsion:
 class: Domain\Entity\Data\Propulsion
 columnPrefix: false
  25. public function __construct($value)
 {
 $propulsion = (string) $value;
 
 if

    (!in_array($propulsion, array_keys(self::$propusions))) {
 throw new Exception('Invalid key');
 }
 
 $this->propulsion = $propulsion;
 }
  26. Domain\Entity\Data\Propulsion:
 type: embeddable
 fields:
 propulsion:
 type: string
 length: 255
 nullable:

    false
  27. None
  28. class Id
 {
 private $key;
 
 public function __construct($key)
 {


    $this->key = (string) $key;
 }
 
 public function getKey()
 {
 return $this->key;
 }
  29. dbal:
 types:
 uuid:
 class: Domain\Doctrine\Type\Uuid
 commented: true

  30. 
 class Uuid extends Type
 {
 public function convertToDatabaseValue($value, AbstractPlatform

    $platform)
 {
 if (empty($value)) {
 return null;
 }
 
 if ($value instanceof Id) {
 return $value->getKey();
 }
 
 return null;
 }
 
 
 
 public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $pl {
 return 'VARCHAR(255) COMMENT \'(DC2Type:uuid)\'';
 }
 
 public function getName()
 {
 return 'uuid';
 }
 }
  31. None
  32. Deptrac • github.com/sensiolabs-de/deptrac by @timglabisch and contributors

  33. None
  34. None
  35. layers:
 - name: Application
 collectors:
 - type: className
 regex: ^AppBundle\\.*


    - name: Domain
 collectors:
 - type: className
 regex: ^Domain\\.*
 - name: Framework
 collectors:
 - type: className
 regex: ^Symfony\\|^Doctrine\\.*
 ruleset:
 Application:
 - Domain
 - Framework
 Application:
 - Domain
  36. None
  37. None
  38. Building Bridges

  39. None
  40. None
  41. namespace Domain\Repository;
 
 use Domain\Entity\Data\Id;
 use Domain\Entity\Robot; interface RobotRepository
 {


    public function get(Id $id);
 
 public function add(Robot $robot); // […]
 }
  42. namespace Bridge\Doctrine\Repository;
 
 use Doctrine\ORM\EntityRepository;
 use Domain\Entity\Data\Id;
 use Domain\Entity\Robot;
 use

    Domain\Repository\RobotRepository as RepositoryInterface; class RobotRepository extends EntityRepository implements Reposit {
 public function get(Id $id)
 {
 return $this->findOneBy(['id' => $id]);
 }
 
 public function add(Robot $robot)
 {
 $this->_em->persist($robot);
 $this->_em->flush($robot);
 }
 }
  43. None
  44. None
  45. Decoupling Security

  46. namespace AppBundle\Entity;
 
 use Doctrine\ORM\Mapping as ORM;
 use Symfony\Component\Security\Core\User\UserInterface;
 use

    Symfony\Component\Validator\Constraints as Assert;
 
 /**
 * @ORM\Entity()
 * @ORM\Table(name="mechanics")
 */
 class Mechanic implements UserInterface
 {
 // …
  47. 
 interface UserProviderInterface
 {
 public function loadUserByUsername($username);
 
 public function

    refreshUser(UserInterface $user);
 
 public function supportsClass($class);
 }

  48. None
  49. class MechanicAccount implements UserInterface
 {
 private $mechanicId;
 private $username;
 private

    $password;
 
 public static function fromMechanic(Mechanic $mechanic)
 {
 $account = new static();
 
 $account->mechanicId = $mechanic->getId();
 $account->username = $mechanic->getEmail();
 $account->password = $mechanic->getPassword();
 
 return $account;
 }
  50. public function loadUserByUsername($username)
 {
 $mechanic = $this->repository
 ->findOneBy(['email' => new

    Email($username)]);
 
 if (null === $mechanic) {
 throw new UsernameNotFoundException( 'User does not exist.' );
 }
 
 return MechanicAccount::fromMechanic($mechanic);
 }
  51. None
  52. Validation, Forms, ViewModels

  53. class RobotUpdate
 {
 public $name;
 public $coolness;
 public $propulsion;
 }

  54. Domain\RobotUpdate:
 properties:
 name:
 - NotBlank: ~
 
 


  55. class RobotService
 {
 public function updateRobot(RobotUpdate $date, Robot $robot)
 {


    // ...
 }
 }
  56. public function configureOptions(OptionsResolver $resolver)
 {
 $resolver->setDefaults(array(
 'data_class' => 'Domain\RobotUpdate'
 ));


    }
  57. Be pragmatic

  58. Thank you! legacy.joind.in/20202