Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)

2,846 views

Published on

You've heard of Zend's new framework, Expressive, and you've heard it's the new hotness. In this talk, I will introduce the concepts of Expressive, how to bootstrap a simple application with the framework using best practices, and finally how to integrate a third party tool like Doctrine ORM.

Published in: Software

Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)

  1. 1. @asgrim Kicking off with Zend Expressive and Doctrine ORM James Titcumb PHP UK 2017
  2. 2. Who is this guy? James Titcumb www.jamestitcumb.com www.roave.com www.phphants.co.uk www.phpsouthcoast.co.uk @asgrim
  3. 3. @asgrim What is Zend Expressive?
  4. 4. @asgrim Layers of an Expressive application Expressive Stratigility Diactoros PSR-7 Interface DIRouter Template Your Application
  5. 5. @asgrim Layers of an Expressive application Expressive Stratigility Diactoros PSR-7 Interface DIRouter Template Your Application
  6. 6. @asgrim PSR-7 HTTP Message Interfaces
  7. 7. @asgrim HTTP Request POST /phpuk17/foo HTTP/1.1 Host: www.phpconference.co.uk foo=bar&baz=bat
  8. 8. @asgrim HTTP Response HTTP/1.1 200 OK Content-Type: text/plain This is the response body
  9. 9. @asgrim Layers of an Expressive application Expressive Stratigility Diactoros PSR-7 Interface DIRouter Template Your Application
  10. 10. @asgrim Zend Diactoros PSR-7 implementation
  11. 11. @asgrim Node http.Server using Diactoros $server = ZendDiactorosServer::createServer( function ($request, $response, $done) { return $response->getBody()->write('hello world'); }, $_SERVER, $_GET, $_POST, $_COOKIE, $_FILES ); $server->listen();
  12. 12. @asgrim Layers of an Expressive application Expressive Stratigility Diactoros PSR-7 Interface DIRouter Template Your Application
  13. 13. @asgrim Zend Stratigility Creating & dispatching middleware pipelines
  14. 14. @asgrim So what is “middleware”?
  15. 15. @asgrim Middleware in the middle function(Request $request, DelegateInterface $delegate) : Response { // ... some code before ... $response = $delegate->process($request); // ... some code after ... return $response; }
  16. 16. @asgrim http-interop/http-middleware
  17. 17. @asgrim Modifying the response function(Request $request, DelegateInterface $delegate) : Response { $response = $delegate->process($request); return $response->withHeader( 'X-Clacks-Overhead', 'GNU Terry Pratchett' ); }
  18. 18. @asgrim Pipe all the things! $pipe = new ZendStratigilityMiddlewarePipe(); $pipe->pipe($logUncaughtErrorsMiddleware); $pipe->pipe($sessionInitialisingMiddleware); $pipe->pipe($authenticationMiddleware); $pipe->pipe('/', $indexActionMiddleware); $pipe->pipe(new NotFoundHandler(...));
  19. 19. @asgrim LogErrorsCaughtMiddleware function(Request $request, DelegateInterface $delegate) : Response { try { return $delegate->process($request); } catch (Throwable $throwable) { // Log the error, handle it with error page etc. return new JsonResponse( [ 'message' => $throwable->getMessage(), ], 500 ); } }
  20. 20. @asgrim SessionInitialisingMiddleware function(Request $request, DelegateInterface $delegate) : Response { session_start(); return $delegate->process($request); }
  21. 21. @asgrim function(Request $request, DelegateInterface $delegate) : Response { if (!$this->doSomeOAuthCheck($request)) { return new JsonResponse( [ 'message' => 'Invalid OAuth token', ], 403 ); } return $delegate->process($request); } AuthenticationMiddleware
  22. 22. @asgrim IndexActionMiddleware function(Request $request, DelegateInterface $delegate) : Response { // ... some code ... return new JsonResponse($someData, 200); }
  23. 23. @asgrim Layers of an Expressive application Expressive Stratigility Diactoros PSR-7 Interface DIRouter Template Your Application
  24. 24. @asgrim Zend Expressive One Ring to bring them all and in the darkness bind them.
  25. 25. @asgrim Routing
  26. 26. @asgrim container-interop
  27. 27. @asgrim Optionally, templating
  28. 28. @asgrim Piping and Routing
  29. 29. @asgrim Zend Framework 2/3 What of them?
  30. 30. @asgrim Middleware vs MVC
  31. 31. @asgrim Using ZF components in Expressive
  32. 32. @asgrim Module.php class Module { public function getConfig() { return include __DIR__ . '/../config/module.config.php'; } }
  33. 33. @asgrim ConfigProvider
  34. 34. @asgrim ConfigProvider namespace ZendForm; class ConfigProvider { public function __invoke() { return [ 'dependencies' => $this->getDependencyConfig(), 'view_helpers' => $this->getViewHelperConfig(), ]; } }
  35. 35. @asgrim ConfigProvider#getDependencyConfig() public function getDependencyConfig() { return [ 'abstract_factories' => [ FormAbstractServiceFactory::class, ], 'aliases' => [ 'ZendFormAnnotationFormAnnotationBuilder' => 'FormAnnotationBuilder', AnnotationAnnotationBuilder::class => 'FormAnnotationBuilder', FormElementManager::class => 'FormElementManager', ], 'factories' => [ 'FormAnnotationBuilder' => AnnotationAnnotationBuilderFactory::class, 'FormElementManager' => FormElementManagerFactory::class, ],
  36. 36. @asgrim ZendForm’s Module.php class Module { public function getConfig() { $provider = new ConfigProvider(); return [ 'service_manager' => $provider->getDependencyConfig(), 'view_helpers' => $provider->getViewHelperConfig(), ]; } }
  37. 37. @asgrim config/autoload/zend-form.global.php <?php return (new ZendFormConfigProvider())->__invoke();
  38. 38. @asgrim Layers of an Expressive application Expressive Stratigility Diactoros PSR-7 Interface DIRouter Template Your Application
  39. 39. @asgrim Getting started with Zend Expressive
  40. 40. @asgrim https://github.com/asgrim/book-library
  41. 41. @asgrim Expressive Skeleton
  42. 42. @asgrim Expressive installer - start $ composer create-project zendframework/zend-expressive-skeleton book-library Installing zendframework/zend-expressive-skeleton (1.0.3) - Installing zendframework/zend-expressive-skeleton (1.0.3) Downloading: 100% Created project in book-library > ExpressiveInstallerOptionalPackages::install Setup data and cache dir Setting up optional packages
  43. 43. @asgrim Expressive installer - minimal? Minimal skeleton? (no default middleware, templates, or assets; configuration only) [y] Yes (minimal) [n] No (full; recommended) Make your selection (No): n
  44. 44. @asgrim Expressive installer - router? Which router do you want to use? [1] Aura.Router [2] FastRoute [3] Zend Router Make your selection or type a composer package name and version (FastRoute): 2
  45. 45. @asgrim Expressive installer - container? Which container do you want to use for dependency injection? [1] Aura.Di [2] Pimple [3] Zend ServiceManager Make your selection or type a composer package name and version (Zend ServiceManager): 3
  46. 46. @asgrim Expressive installer - template? Which template engine do you want to use? [1] Plates [2] Twig [3] Zend View installs Zend ServiceManager [n] None of the above Make your selection or type a composer package name and version (n): n
  47. 47. @asgrim Expressive installer - whoops? Which error handler do you want to use during development? [1] Whoops [n] None of the above Make your selection or type a composer package name and version (Whoops): n
  48. 48. @asgrim Expressive installer - run it! $ composer serve > php -S 0.0.0.0:8080 -t public/ public/index.php [Thu Sep 1 20:29:33 2016] 127.0.0.1:48670 [200]: /favicon.ico { "welcome": "Congratulations! You have installed the zend-expressive skeleton application.", "docsUrl": "zend-expressive.readthedocs.org" }
  49. 49. @asgrim Create the endpoints
  50. 50. @asgrim Book entity class Book { /** * @var string */ private $id; /** * @var bool */ private $inStock = true; public function __construct() { $this->id = (string)Uuid::uuid4(); }
  51. 51. @asgrim Book entity class Book { /** * @return void * @throws AppEntityExceptionBookNotAvailable */ public function checkOut() { if (!$this->inStock) { throw ExceptionBookNotAvailable::fromBook($this); } $this->inStock = false; }
  52. 52. @asgrim Book entity class Book { /** * @return void * @throws AppEntityExceptionBookAlreadyStocked */ public function checkIn() { if ($this->inStock) { throw ExceptionBookAlreadyStocked::fromBook($this); } $this->inStock = true; }
  53. 53. @asgrim FindBookByUuidInterface interface FindBookByUuidInterface { /** * @param UuidInterface $slug * @return Book * @throws ExceptionBookNotFound */ public function __invoke(UuidInterface $slug): Book; }
  54. 54. @asgrim CheckOutAction public function process(ServerRequestInterface $request, DelegateInterface $delegate): JsonResponse { try { $book = $this->findBookByUuid->__invoke(Uuid::fromString($request->getAttribute('id'))); } catch (BookNotFound $bookNotFound) { return new JsonResponse(['info' => $bookNotFound->getMessage()], 404); } try { $book->checkOut(); } catch (BookNotAvailable $bookNotAvailable) { return new JsonResponse(['info' => $bookNotAvailable->getMessage()], 423); } return new JsonResponse([ 'info' => sprintf('You have checked out %s', $book->getId()), ]); }
  55. 55. @asgrim Adding some ORM
  56. 56. @asgrim Your application Doctrine quick overview DB DBAL ORM (EntityManager) Entities Finders, Services, ...
  57. 57. @asgrim DoctrineORMModule
  58. 58. @asgrim DoctrineORMModule + Expressive?
  59. 59. @asgrim config/autoload/doctrine-modules.global.php $vendorPath = __DIR__ . '/../../vendor'; $doctrineModuleConfig = require_once $vendorPath . '/doctrine/doctrine-module/config/module.config.php'; $doctrineModuleConfig['dependencies'] = $doctrineModuleConfig['service_manager']; unset($doctrineModuleConfig['service_manager']); $ormModuleConfig = require_once $vendorPath . '/doctrine/doctrine-orm-module/config/module.config.php'; $ormModuleConfig['dependencies'] = $ormModuleConfig['service_manager']; unset($ormModuleConfig['service_manager']); return ArrayUtils::merge($doctrineModuleConfig, $ormModuleConfig);
  60. 60. @asgrim But wait!
  61. 61. @asgrim container-interop-doctrine saves the day!!!
  62. 62. @asgrim Installation $ composer require dasprid/container-interop-doctrine Using version ^0.2.2 for dasprid/container-interop-doctrine ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) - Installing dasprid/container-interop-doctrine (0.2.2) Loading from cache Writing lock file Generating autoload files $
  63. 63. @asgrim config/autoload/doctrine.global.php <?php declare(strict_types = 1); use DoctrineORMEntityManagerInterface; use ContainerInteropDoctrineEntityManagerFactory; return [ 'dependencies' => [ 'factories' => [ EntityManagerInterface::class => EntityManagerFactory::class, ], ], ];
  64. 64. @asgrim 'doctrine' => [ 'connection' => [ 'orm_default' => [ 'driver_class' => PDOPgSqlDriver::class, 'params' => [ 'url' => 'postgres://user:pass@localhost/book_library', ], ], ], 'driver' => [ 'orm_default' => [ 'class' => MappingDriverChain::class, 'drivers' => [ // ... and so on ... config/autoload/doctrine.global.php
  65. 65. @asgrim 'doctrine' => [ 'connection' => [ 'orm_default' => [ 'driver_class' => PDOPgSqlDriver::class, 'params' => [ 'url' => 'postgres://user:pass@localhost/book_library', ], ], ], 'driver' => [ 'orm_default' => [ 'class' => MappingDriverChain::class, 'drivers' => [ // ... and so on ... config/autoload/doctrine.global.php
  66. 66. @asgrim <?php declare(strict_types = 1); use DoctrineORMEntityManagerInterface; use DoctrineORMToolsConsoleHelperEntityManagerHelper; use SymfonyComponentConsoleHelperHelperSet; $container = require __DIR__ . '/container.php'; return new HelperSet([ 'em' => new EntityManagerHelper( $container->get(EntityManagerInterface::class) ), ]); config/cli-config.php
  67. 67. @asgrim Annotating the Entities
  68. 68. @asgrim /** * @ORMEntity * @ORMTable(name="book") */ class Book { /** * @ORMId * @ORMColumn(name="id", type="guid") * @ORMGeneratedValue(strategy="NONE") * @var string */ private $id; src/App/Entity/Book.php
  69. 69. @asgrim /** * @ORMEntity * @ORMTable(name="book") */ class Book { /** * @ORMId * @ORMColumn(name="id", type="guid") * @ORMGeneratedValue(strategy="NONE") * @var string */ private $id; src/App/Entity/Book.php
  70. 70. @asgrim public function __invoke(UuidInterface $id): Book { /** @var Book|null $book */ $book = $this->repository->find((string)$id); if (null === $book) { throw ExceptionBookNotFound::fromUuid($id); } return $book; } src/App/Service/Book/DoctrineFindBookByUuid.php
  71. 71. @asgrim try { $this->entityManager->transactional(function () use ($book) { $book->checkOut(); }); } catch (BookNotAvailable $bookNotAvailable) { return new JsonResponse(['info' => $bookNotAvailable->getMessage()], 423); } src/App/Action/CheckOutAction.php
  72. 72. @asgrim Generate the schema $ vendor/bin/doctrine orm:schema-tool:create ATTENTION: This operation should not be executed in a production environment. Creating database schema... Database schema created successfully! $
  73. 73. @asgrim Insert some data INSERT INTO book (id, name, in_stock) VALUES ( '1c06bec9-adae-47c2-b411-73b1db850e6f', 'The Great Escape', true );
  74. 74. @asgrim /book/1c06bec9-adae-47c2-b411-.../check-out {"info":"You have checked out The Great Escape"}
  75. 75. @asgrim /book/1c06bec9-adae-47c2-b411-.../check-in {"info":"You have checked in The Great Escape"}
  76. 76. @asgrim Automatic Flushing Middleware
  77. 77. @asgrim FlushingMiddleware public function __invoke(Request $request, Response $response, callable $out = null) : Response { if (null === $out) { throw new InvalidArgumentException('Middleware in ' . __CLASS__ . ' must be piped'); } $response = $out($request, $response); if ($this->entityManager->isOpen()) { $this->entityManager->flush(); } return $response; }
  78. 78. @asgrim FlushingMiddleware public function __invoke(Request $request, Response $response, callable $out = null) : Response { if (null === $out) { throw new InvalidArgumentException('Middleware in ' . __CLASS__ . ' must be piped'); } $response = $out($request, $response); if ($this->entityManager->isOpen()) { $this->entityManager->flush(); } return $response; }
  79. 79. @asgrim FlushingMiddleware public function __invoke(Request $request, Response $response, callable $out = null) : Response { if (null === $out) { throw new InvalidArgumentException('Middleware in ' . __CLASS__ . ' must be piped'); } $response = $out($request, $response); if ($this->entityManager->isOpen()) { $this->entityManager->flush(); } return $response; }
  80. 80. @asgrim FlushingMiddleware public function __invoke(Request $request, Response $response, callable $out = null) : Response { if (null === $out) { throw new InvalidArgumentException('Middleware in ' . __CLASS__ . ' must be piped'); } $response = $out($request, $response); if ($this->entityManager->isOpen()) { $this->entityManager->flush(); } return $response; }
  81. 81. @asgrim FlushingMiddleware public function __invoke(Request $request, Response $response, callable $out = null) : Response { if (null === $out) { throw new InvalidArgumentException('Middleware in ' . __CLASS__ . ' must be piped'); } $response = $out($request, $response); if ($this->entityManager->isOpen()) { $this->entityManager->flush(); } return $response; }
  82. 82. @asgrim Doing more with middleware
  83. 83. @asgrim Authentication
  84. 84. @asgrim public function __invoke( Request $request, Response $response, callable $next = null ) : Response { $queryParams = $request->getQueryParams(); if (!array_key_exists('authenticated', $queryParams)||$queryParams['authenticated'] !== '1') { return new JsonResponse( ['error' => 'You are not authenticated.'], 403 ); } return $next($request, $response); } Create the middleware
  85. 85. @asgrim public function __invoke( Request $request, Response $response, callable $next = null ) : Response { $queryParams = $request->getQueryParams(); if (!array_key_exists('authenticated', $queryParams)||$queryParams['authenticated'] !== '1') { return new JsonResponse( ['error' => 'You are not authenticated.'], 403 ); } return $next($request, $response); } Create the middleware
  86. 86. @asgrim public function __invoke( Request $request, Response $response, callable $next = null ) : Response { $queryParams = $request->getQueryParams(); if (!array_key_exists('authenticated', $queryParams)||$queryParams['authenticated'] !== '1') { return new JsonResponse( ['error' => 'You are not authenticated.'], 403 ); } return $next($request, $response); } Create the middleware
  87. 87. @asgrim public function __invoke( Request $request, Response $response, callable $next = null ) : Response { $queryParams = $request->getQueryParams(); if (!array_key_exists('authenticated', $queryParams)||$queryParams['authenticated'] !== '1') { return new JsonResponse( ['error' => 'You are not authenticated.'], 403 ); } return $next($request, $response); } Create the middleware
  88. 88. @asgrim Helios composer require dasprid/helios
  89. 89. @asgrim PSR7-Session composer require psr7-sessions/storageless
  90. 90. @asgrim public function __invoke(ContainerInterface $container, $_, array $_ = null) { $symmetricKey = 'super-secure-key-you-should-not-store-this-key-in-git-store-it-in-configuration-instead-please'; $expirationTime = 1200; // 20 minutes return new SessionMiddleware( new SignerHmacSha256(), $symmetricKey, $symmetricKey, SetCookie::create(SessionMiddleware::DEFAULT_COOKIE) ->withSecure(false) // false on purpose, unless you have https locally ->withHttpOnly(true) ->withPath('/'), new Parser(), $expirationTime, new SystemCurrentTime() ); } Factory the middleware
  91. 91. @asgrim 'routing' => [ 'middleware' => [ ApplicationFactory::ROUTING_MIDDLEWARE, HelperUrlHelperMiddleware::class, PSR7SessionHttpSessionMiddleware::class, AppMiddlewareAuthenticationMiddleware::class, ApplicationFactory::DISPATCH_MIDDLEWARE, ], 'priority' => 1, ], Add middleware to pipe
  92. 92. @asgrim $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE); $session->set('counter', $session->get('counter', 0) + 1); Session is stored in Request
  93. 93. @asgrim To summarise... ● PSR-7 is the foundation for this! ● Diactoros is just a PSR-7 implementation ● Stratigility is a middleware pipeline: the main bit ● Expressive is a glue for everything ● DoctrineModule can be used (with some fiddling) ● container-interop-doctrine makes Doctrine work easier ● Middleware all the things!
  94. 94. Any questions? https://joind.in/talk/98515 James Titcumb @asgrim

×