Slide 1

Slide 1 text

∙ ∙ in Symfony ∙

Slide 2

Slide 2 text

architectural patterns

Slide 3

Slide 3 text

needs they do

Slide 4

Slide 4 text

it depends™

Slide 5

Slide 5 text

wait

Slide 6

Slide 6 text

don't

Slide 7

Slide 7 text

!==

Slide 8

Slide 8 text

belongs to

Slide 9

Slide 9 text

tools responsibility well fault

Slide 10

Slide 10 text

not the end

Slide 11

Slide 11 text

library

Slide 12

Slide 12 text

don't *

Slide 13

Slide 13 text

Symfony ?

Slide 14

Slide 14 text

good boy framework!

Slide 15

Slide 15 text

clear responsibility boundaries

Slide 16

Slide 16 text

supports

Slide 17

Slide 17 text

like a framework

Slide 18

Slide 18 text

forget included in PHP SPL

Slide 19

Slide 19 text

information flow the architecture

Slide 20

Slide 20 text

composer create-project symfony/skeleton .

Slide 21

Slide 21 text

├── bin │ └── console ├── composer.json ├── composer.lock ├── config │ ├── bootstrap.php │ ├── bundles.php │ ├── packages │ │ ├── cache.yaml │ │ ├── dev │ │ │ └── routing.yaml │ │ ├── framework.yaml │ │ ├── routing.yaml │ │ └── test │ │ ├── framework.yaml │ │ └── routing.yaml │ ├── routes.yaml │ └── services.yaml ├── public │ └── index.php ├── src │ ├── Controller │ └── Kernel.php ├── symfony.lock ├── var └── vendor

Slide 22

Slide 22 text

├── bin │ └── console ├── composer.json ├── composer.lock ├── config │ ├── bootstrap.php │ ├── bundles.php │ ├── packages │ │ ├── cache.yaml │ │ ├── dev │ │ │ └── routing.yaml │ │ ├── framework.yaml │ │ ├── routing.yaml │ │ └── test │ │ ├── framework.yaml │ │ └── routing.yaml │ ├── routes.yaml │ └── services.yaml ├── public │ └── index.php ├── src │ ├── Controller │ └── Kernel.php ├── symfony.lock ├── var └── vendor

Slide 23

Slide 23 text

└── src ├── Controller └── Kernel.php

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

└── src ├── Command │ └── GenerateCommand.php ├── Controller │ ├── UserController.php │ └── CarController.php ├── Entity │ ├── User.php │ └── Car.php ├── Form │ ├── UserType.php │ └── CarType.php ├── Manager │ ├── UserManager.php │ └── CarManager.php ├── Repository │ ├── UserRepository.php │ └── CarRepository.php ├── Security │ └── Voter │ ├── UserVoter.php │ └── CarVoter.php ├── Service │ ├── UserService.php │ └── CarService.php └── Kernel.php

Slide 26

Slide 26 text

└── src ├── Command │ └── GenerateCommand.php ├── Controller │ ├── UserController.php │ └── CarController.php ├── Entity │ ├── User.php │ └── Car.php ├── Form │ ├── UserType.php │ └── CarType.php ├── Manager │ ├── UserManager.php │ └── CarManager.php ├── Repository │ ├── UserRepository.php │ └── CarRepository.php ├── Security │ └── Voter │ ├── UserVoter.php │ └── CarVoter.php ├── Service │ ├── UserService.php │ └── CarService.php └── Kernel.php

Slide 27

Slide 27 text

not guilty

Slide 28

Slide 28 text

extremely simplified architectural approaches

Slide 29

Slide 29 text

ports adapters

Slide 30

Slide 30 text

CQRS

Slide 31

Slide 31 text

Domain Driven Design

Slide 32

Slide 32 text

Event Sourcing

Slide 33

Slide 33 text

src └── Shared └── Infrastructure └── SymfonyKernel.php

Slide 34

Slide 34 text

src ├── Account │ ├── Application │ ├── Domain │ └── Infrastructure └── Shared ├── Application ├── Domain └── Infrastructure └── SymfonyKernel.php

Slide 35

Slide 35 text

src ├── Account │ ├── Application │ ├── Domain │ └── Infrastructure └── Shared ├── Application │ └── CommandInterface.php ├── Domain └── Infrastructure ├── SynchronousCommandBus.php ├── CommandBusInterface.php └── SymfonyKernel.php

Slide 36

Slide 36 text

interface CommandBusInterface { public function handle(CommandInterface $command): void; } final class SynchronousCommandBus implements CommandBusInterface { private $handlers; public function map(string $command, callable $handler): void { $this->handlers[$command] = $handler; } public function handle(CommandInterface $command): void { $fqcn = \get_class($command); $handlerNotFound = false === isset($this->handlers[$fqcn]); HandlerNotFoundException::throwWhen($handlerNotFound, $fqcn); call_user_func($this->handlers[$fqcn], $command); } }

Slide 37

Slide 37 text

src ├── Account │ ├── Application │ ├── Domain │ │ ├── Account.php │ │ └── AccountRepositoryInterface.php │ └── Infrastructure └── Shared ├── Application │ └── CommandInterface.php ├── Domain └── Infrastructure ├── SynchronousCommandBus.php ├── CommandBusInterface.php └── SymfonyKernel.php

Slide 38

Slide 38 text

interface AccountRepositoryInterface { public function findById(int $id): Account; } final class Account { /** @var int */ private $id; /** @var string */ private $email; public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } }

Slide 39

Slide 39 text

src ├── Account │ ├── Application │ │ ├── AccountQueryInterface.php │ │ ├── SetupAccountCommand.php │ │ └── SetupAccountHandler.php │ ├── Domain │ │ ├── Account.php │ │ └── AccountRepositoryInterface.php │ └── Infrastructure └── Shared ├── Application │ └── CommandInterface.php ├── Domain └── Infrastructure ├── SynchronousCommandBus.php ├── CommandBusInterface.php └── SymfonyKernel.php

Slide 40

Slide 40 text

final class SetupAccountCommand implements CommandInterface { /** @var int */ private $id; /** @var string */ private $email; public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } public function getId(): int { return $this->id; } public function getEmail(): string { return $this->email; } }

Slide 41

Slide 41 text

final class SetupAccountHandler { /** @var AccountRepositoryInterface */ private $accounts; public function __construct(AccountRepositoryInterface $accounts) { $this->accounts = $accounts; } public function __invoke(SetupAccountCommand $cmd) { $account = new Account($cmd->getId(), $cmd->getEmail()); $this->accounts->add($account); } }

Slide 42

Slide 42 text

interface AccountQueryInterface { public function findByEmail(EmailValue $email): AccountView; } final class AccountView { /** @var int */ public $id; /** @var string */ public $email; public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } }

Slide 43

Slide 43 text

src ├── Account │ ├── Application │ │ ├── AccountQueryInterface.php │ │ ├── SetupAccountCommand.php │ │ └── SetupAccountHandler.php │ ├── Domain │ │ ├── Account.php │ │ └── AccountRepositoryInterface.php │ └── Infrastructure │ ├── AccountController.php │ ├── DoctrineDbalAccountQuery.php │ └── DoctrineOrmAccountRepository.php └── Shared ├── Application │ └── CommandInterface.php ├── Domain └── Infrastructure ├── SynchronousCommandBus.php ├── CommandBusInterface.php └── SymfonyKernel.php

Slide 44

Slide 44 text

final class DoctrineOrmAccountRepository implements AccountRepositoryInterface { /** @var EntityManagerInterface */ private $em; public function __construct(EntityManagerInterface $em) { $this->em = $em; } public function findById(int $id): Account { return $this->em->find(Account::class, $id); } }

Slide 45

Slide 45 text

final class DoctrineDbalAccountQuery implements AccountQueryInterface { /** @var Connection */ private $connection; public function __construct(Connection $connection) { $this->connection = $connection; } public function findByEmail(EmailValue $email): AccountView { $sql = 'SELECT id, email FROM user WHERE email = :email'; $data = $this->connection->exec($sql, ['email' => $email]); return new AccountView($data['id'], $data['email']); } }

Slide 46

Slide 46 text

final class AccountController { // ... public function __construct(CommandBusInterface $bus, RouterInterface $router) { // ... } public function setupAction(Request $request): Response { $id = generateId(); $payload = $request->request->all(); $command = new SetupAccountCommand($id, $payload['email']); $this->bus->handle($command); return new Response(null, Response::HTTP_CREATED, [ 'Location' => $this->router->generate('account.view', [$id]), ]); } }

Slide 47

Slide 47 text

Symfony?

Slide 48

Slide 48 text

account.setup: methods: [POST] path: /accounts controller: account.controller::setupAction

Slide 49

Slide 49 text

parameters: services: _defaults: autowire: true autoconfigure: true App\: resource: '../src/*' exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}' App\Controller\: resource: '../src/Controller' tags: ['controller.service_arguments']

Slide 50

Slide 50 text

services: _defaults: autowire: false autoconfigure: false public: false

Slide 51

Slide 51 text

account.query: class: Thunder\App\Account\Infrastructure\DoctrineDbalAccountQuery arguments: ['@doctrine.dbal.connection'] account.repository: class: Thunder\App\Account\Infrastructure\DoctrineOrmAccountRepository arguments: ['@doctrine.orm.entity_manager'] account.controller: class: Thunder\App\Account\Infrastructure\AccountController arguments: ['@command_bus', '@routing'] public: true

Slide 52

Slide 52 text

command_bus: class: Thunder\App\Shared\Infrastructure\SynchronousCommandBus arguments: [] calls: - ['map', ['Thunder\App\Account\Application\SetupAccountCommand','@account.setup.handler']] account.setup.handler: class: Thunder\App\Account\Application\AddAccountHandler arguments: ['@account.repository']

Slide 53

Slide 53 text

E S

Slide 54

Slide 54 text

src ├── Account │ ├── Application │ │ ├── AccountQueryInterface.php │ │ ├── SetupAccountCommand.php │ │ └── SetupAccountHandler.php │ ├── Domain │ │ ├── Account.php │ │ ├── AccountWasSetupEvent.php │ │ └── AccountRepositoryInterface.php │ └── Infrastructure │ ├── AccountController.php │ ├── DoctrineDbalAccountQuery.php │ └── DoctrineOrmAccountRepository.php └── Shared ├── Application │ └── CommandInterface.php ├── Domain └── Infrastructure ├── SynchronousCommandBus.php ├── CommandBusInterface.php └── SymfonyKernel.php

Slide 55

Slide 55 text

final class AccountWasSetupEvent { /** @var int */ private $id; /** @var string */ private $email; public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } public function getId(): int { return $this->id; } public function getEmail(): string { return $this->email; } }

Slide 56

Slide 56 text

simple, eh?

Slide 57

Slide 57 text

simple !== easy

Slide 58

Slide 58 text

Any questions? ask

Slide 59

Slide 59 text

Resources https://murze.be/hexagonal-architecture https://fideloper.com/hexagonal-architecture https://stackoverflow.com/q/32216408/443341 https://www.slideshare.net/profpv/hexagonal-architecture-in-php https://matthiasnoback.nl/2017/08/layers-ports-and-adapters-part-2-layers https://enterprisecraftsmanship.com/2019/01/31/cqrs-commands-part-domain-model https://blog.octo.com/en/hexagonal-architecture-three-principles-and-an-implementation-example https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/ https://apiumhub.com/tech-blog-barcelona/applying-hexagonal-architecture-symfony-project Images https://unsplash.com/photos/cjNaoIqbWCI (dog) https://unsplash.com/photos/6U-sSfBV-gM (city tower) https://unsplash.com/photos/aSCx7M1E4Vo (foggy forest) https://unsplash.com/photos/h0Vxgz5tyXA (wooden floor)

Slide 60

Slide 60 text

5 30 2 8 knowledge eternal glory Symfopardy! see you today at 17:00!

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

GENTLEMAN software with manners @tmmx

Slide 64

Slide 64 text

Thanks!