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 (PHPNW2016)

1,015 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: Technology
  • Be the first to comment

Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

  1. 1. @asgrim Kicking off with Zend Expressive + Doctrine ORM James Titcumb PHPNW16
  2. 2. James Titcumb www.jamestitcumb.com www.roave.com www.phphants.co.uk www.phpsouthcoast.co.uk Who is this guy?
  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 PSR-7 HTTP Message Interfaces
  6. 6. @asgrim HTTP Request POST /phpnw16/foo HTTP/1.1 Host: conference.phpnw.org.uk foo=bar&baz=bat
  7. 7. @asgrim HTTP Request POST /phpnw16/foo HTTP/1.1 Host: conference.phpnw.org.uk foo=bar&baz=bat
  8. 8. @asgrim HTTP Request POST /phpnw16/foo HTTP/1.1 Host: conference.phpnw.org.uk foo=bar&baz=bat
  9. 9. @asgrim HTTP Request POST /phpnw16/foo HTTP/1.1 Host: conference.phpnw.org.uk foo=bar&baz=bat
  10. 10. @asgrim HTTP Response HTTP/1.1 200 OK Content-Type: text/plain This is the response body
  11. 11. @asgrim HTTP Response HTTP/1.1 200 OK Content-Type: text/plain This is the response body
  12. 12. @asgrim HTTP Response HTTP/1.1 200 OK Content-Type: text/plain This is the response body
  13. 13. @asgrim HTTP Response HTTP/1.1 200 OK Content-Type: text/plain This is the response body
  14. 14. @asgrim Zend Diactoros PSR-7 implementation
  15. 15. @asgrim Zend Stratigility Creating & dispatching middleware pipelines
  16. 16. @asgrim So what is “middleware”?
  17. 17. @asgrim Middleware example public function __invoke( Request $request, Response $response, callable $next = null ) { // ... some code before ... $response = $next($request, $response); // ... some code after ... return $response; }
  18. 18. @asgrim Middleware example public function __invoke( Request $request, Response $response, callable $next = null ) { // ... some code before ... $response = $next($request, $response); // ... some code after ... return $response; }
  19. 19. @asgrim Middleware example public function __invoke( Request $request, Response $response, callable $next = null ) { // ... some code before ... $response = $next($request, $response); // ... some code after ... return $response; }
  20. 20. @asgrim Zend Expressive One Ring to bring them all and in the darkness bind them.
  21. 21. @asgrim Routing
  22. 22. @asgrim container-interop
  23. 23. @asgrim Optionally, templating
  24. 24. @asgrim Piping and Routing
  25. 25. @asgrim Pipe all the things! $pipe = new ZendStratigilityMiddlewarePipe(); $pipe->pipe($sessionMiddleware); $pipe->pipe('/foo', $fooMiddleware); $pipe->pipe($whateverMiddleware); $pipe->pipe($dogeMiddleware);
  26. 26. @asgrim Zend Framework 2/3 What of them?
  27. 27. @asgrim Middleware vs MVC
  28. 28. @asgrim Getting started with Zend Expressive
  29. 29. @asgrim https://github.com/asgrim/book-library
  30. 30. @asgrim Expressive Skeleton
  31. 31. @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
  32. 32. @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
  33. 33. @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
  34. 34. @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
  35. 35. @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
  36. 36. @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
  37. 37. @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" }
  38. 38. @asgrim Create the endpoints
  39. 39. @asgrim Book entity final class Book { /** * @var string */ private $id; /** * @var bool */ private $inStock = true; public function __construct() { $this->id = (string)Uuid::uuid4(); }
  40. 40. @asgrim Book entity final class Book { /** * @return void * @throws AppEntityExceptionBookNotAvailable */ public function checkOut() { if (!$this->inStock) { throw ExceptionBookNotAvailable::fromBook($this); } $this->inStock = false; }
  41. 41. @asgrim Book entity final class Book { /** * @return void * @throws AppEntityExceptionBookAlreadyStocked */ public function checkIn() { if ($this->inStock) { throw ExceptionBookAlreadyStocked::fromBook($this); } $this->inStock = true; }
  42. 42. @asgrim FindBookByUuidInterface interface FindBookByUuidInterface { /** * @param UuidInterface $slug * @return Book * @throws ExceptionBookNotFound */ public function __invoke(UuidInterface $slug): Book; }
  43. 43. @asgrim CheckOutAction public function __invoke( ServerRequestInterface $request, ResponseInterface $response, callable $next = null ) : 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()), ]); }
  44. 44. @asgrim Adding some ORM
  45. 45. @asgrim DoctrineORMModule
  46. 46. @asgrim DoctrineORMModule + Expressive?
  47. 47. @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);
  48. 48. @asgrim ConfigProvider namespace ZendForm; class ConfigProvider { public function __invoke() { return [ 'dependencies' => $this->getDependencyConfig(), 'view_helpers' => $this->getViewHelperConfig(), ]; }
  49. 49. @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, ],
  50. 50. @asgrim config/autoload/zend-form.global.php <?php use ZendFormConfigProvider; return (new ConfigProvider())->__invoke();
  51. 51. @asgrim But wait!
  52. 52. @asgrim container-interop-doctrine saves the day!!!
  53. 53. @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 $
  54. 54. @asgrim config/autoload/doctrine.global.php <?php declare(strict_types = 1); use DoctrineORMEntityManagerInterface; use ContainerInteropDoctrineEntityManagerFactory; return [ 'dependencies' => [ 'factories' => [ EntityManagerInterface::class => EntityManagerFactory::class, ], ], ];
  55. 55. @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
  56. 56. @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
  57. 57. @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
  58. 58. @asgrim /** * @ORMEntity * @ORMTable(name="book") */ final class Book { /** * @ORMId * @ORMColumn(name="id", type="guid") * @ORMGeneratedValue(strategy="NONE") * @var string */ private $id; src/App/Entity/Book.php
  59. 59. @asgrim /** * @ORMEntity * @ORMTable(name="book") */ final class Book { /** * @ORMId * @ORMColumn(name="id", type="guid") * @ORMGeneratedValue(strategy="NONE") * @var string */ private $id; src/App/Entity/Book.php
  60. 60. @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
  61. 61. @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
  62. 62. @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! $
  63. 63. @asgrim Insert some data INSERT INTO book (id, name, in_stock) VALUES ( '1c06bec9-adae-47c2-b411-73b1db850e6f', 'The Great Escape', true );
  64. 64. @asgrim /book/1c06bec9-adae-47c2-b411-.../check-out {"info":"You have checked out The Great Escape"}
  65. 65. @asgrim /book/1c06bec9-adae-47c2-b411-.../check-in {"info":"You have checked in The Great Escape"}
  66. 66. @asgrim Doing more with middleware
  67. 67. @asgrim Authentication
  68. 68. @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
  69. 69. @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
  70. 70. @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
  71. 71. @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
  72. 72. @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
  73. 73. @asgrim PSR7Session
  74. 74. @asgrim public function __invoke(ContainerInterface $container, $_, array $_ = null) { $symmetricKey = 'super-secure-key-you-should-not-store-this-key-in-git'; $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
  75. 75. @asgrim 'routing' => [ 'middleware' => [ ApplicationFactory::ROUTING_MIDDLEWARE, HelperUrlHelperMiddleware::class, PSR7SessionHttpSessionMiddleware::class, AppMiddlewareAuthenticationMiddleware::class, ApplicationFactory::DISPATCH_MIDDLEWARE, ], 'priority' => 1, ], Add middleware to pipe
  76. 76. @asgrim $session = $request->getAttribute( SessionMiddleware::SESSION_ATTRIBUTE ); $session->set( 'counter', $session->get('counter', 0) + 1 ); Session is stored in Request
  77. 77. @asgrim To summarise... ● PSR-7 is important ● Diactoros is just a PSR-7 implementation ● Stratigility is a middleware pipeline ● Expressive is a glue for container, router (and templating) ● DoctrineModule can be used (with some fiddling) ● container-interop-doctrine makes Doctrine work easily ● Middleware gives you good controls
  78. 78. Any questions? :) https://joind.in/talk/ff04f James Titcumb

×