Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

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.

  • Identifiez-vous pour voir les commentaires

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

×