Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Divide and Conquer

Divide and Conquer

Andreas Hucks

January 30, 2017
Tweet

More Decks by Andreas Hucks

Other Decks in Programming

Transcript

  1. Divide And Conquer
    PHPBenelux 2017
    Andreas Hucks

    View full-size slide

  2. @meandmymonkey
    Andreas Hucks
    CTO @

    View full-size slide

  3. What this talk is not about:
    • DDD
    • Generic Package Design
    (but these topics will play a role)

    View full-size slide

  4. A Generic Framework App

    View full-size slide

  5. /**

    * @Route(path="/", name="list")

    */

    public function indexAction()

    {

    $robots = $this

    ->getDoctrine()

    ->getRepository('AppBundle:Robot')

    ->findAll()

    ;


    return $this->render(

    '@App/robot/index.html.twig',

    [

    'robots' => $robots

    ]

    );

    }

    View full-size slide

  6. /**

    * @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');

    }

    View full-size slide

  7. /**

    * @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;

    View full-size slide

  8. public function validatePropulsion(
    ExecutionContextInterface $context,
    $payload
    ) {

    if (!in_array($this->getPropulsion(), self::$propusions)) {
    $context->buildViolation('Unknown propulsion type!')

    ->atPath('propulsion')

    ->addViolation();

    }

    }

    View full-size slide

  9. 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

    {

    // …

    View full-size slide

  10. Dependencies of your Code
    • to Validator
    • to Doctrine
    • to Security Layer

    View full-size slide

  11. Direct Access to Entities
    • from View
    • in Forms

    View full-size slide

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

    View full-size slide

  13. Why Separate?

    View full-size slide

  14. 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…)

    View full-size slide

  15. Extracting the Domain

    View full-size slide

  16. 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

    View full-size slide

  17. public function __construct($value)

    {

    $propulsion = (string) $value;


    if (!in_array($propulsion, array_keys(self::$propusions)))
    {

    throw new Exception('Invalid key');

    }


    $this->propulsion = $propulsion;

    }

    View full-size slide

  18. Domain\Entity\Data\Propulsion:

    type: embeddable

    fields:

    propulsion:

    type: string

    length: 255

    nullable: false

    View full-size slide

  19. class Id

    {

    private $key;


    public function __construct($key)

    {

    $this->key = (string) $key;

    }


    public function getKey()

    {

    return $this->key;

    }

    View full-size slide

  20. dbal:

    types:

    uuid:

    class: Domain\Doctrine\Type\Uuid

    commented: true

    View full-size slide


  21. 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';

    }

    }

    View full-size slide

  22. Deptrac
    • github.com/sensiolabs-de/deptrac
    by @timglabisch and contributors

    View full-size slide

  23. 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

    View full-size slide

  24. Building Bridges

    View full-size slide

  25. namespace Domain\Repository;


    use Domain\Entity\Data\Id;

    use Domain\Entity\Robot;
    interface RobotRepository

    {

    public function get(Id $id);


    public function add(Robot $robot);
    // […]

    }

    View full-size slide

  26. 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);

    }

    }

    View full-size slide

  27. Decoupling Security

    View full-size slide

  28. 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

    {

    // …

    View full-size slide


  29. interface UserProviderInterface

    {

    public function loadUserByUsername($username);


    public function refreshUser(UserInterface $user);


    public function supportsClass($class);

    }


    View full-size slide

  30. 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;

    }

    View full-size slide

  31. 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);

    }

    View full-size slide

  32. Validation, Forms,
    ViewModels

    View full-size slide

  33. class RobotUpdate

    {

    public $name;

    public $coolness;

    public $propulsion;

    }

    View full-size slide

  34. Domain\RobotUpdate:

    properties:

    name:

    - NotBlank: ~



    View full-size slide

  35. class RobotService

    {

    public function updateRobot(RobotUpdate $date, Robot $robot)

    {

    // ...

    }

    }

    View full-size slide

  36. public function configureOptions(OptionsResolver $resolver)

    {

    $resolver->setDefaults(array(

    'data_class' => 'Domain\RobotUpdate'

    ));

    }

    View full-size slide

  37. Be pragmatic

    View full-size slide

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

    View full-size slide