O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

Kicking off with Zend Expressive and Doctrine ORM (PHP MiNDS March 2018)

181 visualizações

Publicada em

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.

Publicada em: Tecnologia
  • Login to see the comments

Kicking off with Zend Expressive and Doctrine ORM (PHP MiNDS March 2018)

  1. 1. @asgrim Kicking off with Zend Expressive and Doctrine ORM James Titcumb php[MiNDS] March 2018 Follow along (optional): https://github.com/asgrim/book-library
  2. 2. $ whoami James Titcumb www.jamestitcumb.com www.roave.com @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 /talk HTTP/1.1 Host: phpminds.org 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, RequestHandler $handler) : Response { // ... some code before ... $response = $handler->handle($request); // ... some code after ... return $response; }
  16. 16. @asgrim Some people call it an onion SessionInitialisingMiddleware AuthenticationMiddleware ThingyMiddleware DashboardHandler
  17. 17. @asgrim Some people call it an onion SessionInitialisingMiddleware AuthenticationMiddleware ThingyMiddleware DashboardHandler
  18. 18. @asgrim Some people call it an onion SessionInitialisingMiddleware AuthenticationMiddleware ThingyMiddleware DashboardHandler
  19. 19. @asgrim psr/http-server-middleware
  20. 20. @asgrim Middleware using MiddlewareInterface <?php declare(strict_types=1); namespace AppMiddleware; use PsrHttpMessageResponseInterface; use PsrHttpMessageServerRequestInterface; use PsrHttpServerMiddlewareInterface; use PsrHttpServerRequestHandlerInterface; final class MyMiddleware implements MiddlewareInterface { public function process( ServerRequestInterface $request, RequestHandlerInterface $requestHandler ) : ResponseInterface { return $requestHandler>handle($request); } }
  21. 21. @asgrim Double-pass middleware public function __invoke( Request $request, Response $response, callable $next ): Response { return $next($request, $response); } !!! DEPRECATED !!!
  22. 22. @asgrim Modifying the response function(Request $request, RequestHandler $handler) : Response { $response = $handler->handle($request); return $response->withHeader( 'X-Clacks-Overhead', 'GNU Terry Pratchett' ); }
  23. 23. @asgrim Pipe all the things! $pipe = new ZendStratigilityMiddlewarePipe(); $pipe->pipe($logUncaughtErrorsMiddleware); $pipe->pipe($sessionInitialisingMiddleware); $pipe->pipe($authenticationMiddleware); $pipe->pipe('/', $indexHandlerMiddleware); $pipe->pipe(new NotFoundHandler(...));
  24. 24. @asgrim LogErrorsCaughtMiddleware function(Request $request, RequestHandler $handler) : Response { try { return $handler->handle($request); } catch (Throwable $throwable) { // Log the error, handle it with error page etc. return new JsonResponse( [ 'message' => $throwable->getMessage(), ], 500 ); } }
  25. 25. @asgrim SessionInitialisingMiddleware function(Request $request, RequestHandler $handler) : Response { session_start(); return $handler->handle($request); }
  26. 26. @asgrim function(Request $request, RequestHandler $handler) : Response { if (!$this->doSomeOAuthCheck($request)) { return new JsonResponse( [ 'message' => 'Invalid OAuth token', ], 403 ); } return $handler->handle($request); } AuthenticationMiddleware
  27. 27. @asgrim IndexHandlerMiddleware function(Request $request, RequestHandler $handler) : Response { // ... some code ... return new JsonResponse($someData, 200); }
  28. 28. @asgrim Layers of an Expressive application Expressive Stratigility Diactoros PSR-7 Interface DIRouter Template Your Application
  29. 29. @asgrim Zend Expressive One Ring to bring them all and in the darkness bind them.
  30. 30. @asgrim Routing
  31. 31. @asgrim container-interop
  32. 32. @asgrim Optionally, templating
  33. 33. @asgrim Piping and Routing
  34. 34. @asgrim Zend Framework 2/3 What of them?
  35. 35. @asgrim Middleware vs MVC
  36. 36. @asgrim Using ZF components in Expressive
  37. 37. @asgrim ZF’s Module.php class Module { public function getConfig() { return include __DIR__ . '/../config/module.config.php'; } }
  38. 38. @asgrim ConfigProvider
  39. 39. @asgrim ConfigProvider namespace ZendForm; class ConfigProvider { public function __invoke() { return [ 'dependencies' => $this->getDependencyConfig(), 'view_helpers' => $this->getViewHelperConfig(), ]; } }
  40. 40. @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, ],
  41. 41. @asgrim ZendForm’s Module.php (for Zend Framework) class Module { public function getConfig() { $provider = new ConfigProvider(); return [ 'service_manager' => $provider->getDependencyConfig(), 'view_helpers' => $provider->getViewHelperConfig(), ]; } }
  42. 42. @asgrim config/config.php <?php $aggregator = new ConfigAggregator([ ZendFormConfigProvider::class, // .. other config ... ], $cacheConfig['config_cache_path']); return $aggregator->getMergedConfig();
  43. 43. @asgrim ConfigAggregator
  44. 44. @asgrim config/config.php $aggregator = new ConfigAggregator([ ZendExpressiveRouterFastRouteRouterConfigProvider::class, ZendHttpHandlerRunnerConfigProvider::class, new ArrayProvider($cacheConfig), ZendExpressiveHelperConfigProvider::class, ZendExpressiveConfigProvider::class, ZendExpressiveRouterConfigProvider::class, AppConfigProvider::class, new PhpFileProvider(realpath(__DIR__) . '/autoload/{{,*.}global,{,*.}local}.php'), new PhpFileProvider(realpath(__DIR__) . '/development.config.php'), ], $cacheConfig['config_cache_path']);
  45. 45. @asgrim Layers of an Expressive application Expressive Stratigility Diactoros PSR-7 Interface DIRouter Template Your Application
  46. 46. @asgrim Getting started with Zend Expressive
  47. 47. @asgrim https://github.com/asgrim/book-library
  48. 48. @asgrim Expressive Skeleton
  49. 49. @asgrim Expressive installer - start composer create-project zendframework/zend-expressive-skeleton:3.0.0-rc1 book-library Installing zendframework/zend-expressive-skeleton (3.0.0rc1) - Installing zendframework/zend-expressive-skeleton (3.0.0rc1): Downloading (100%) Created project in test > ExpressiveInstallerOptionalPackages::install Setting up optional packages Setup data and cache dir Removing installer development dependencie
  50. 50. @asgrim Expressive installer - structure What type of installation would you like? [1] Minimal (no default middleware, templates, or assets; configuration only) [2] Flat (flat source code structure; default selection) [3] Modular (modular source code structure; recommended) Make your selection (2): 2 - Copying src/App/ConfigProvider.php
  51. 51. @asgrim Modular structure zend-config-aggregator
  52. 52. @asgrim Modular structure zend-config-aggregator
  53. 53. @asgrim Flat or Modular?
  54. 54. @asgrim Default modular structure src/ MyModule/ src/ ConfigProvider.php Handler/ Entity/ Middleware/ templates/ test/
  55. 55. @asgrim Expressive installer - container? Which container do you want to use for dependency injection? [1] Aura.Di [2] Pimple [3] zend-servicemanager [4] Auryn [5] Symfony DI Container Make your selection or type a composer package name and version (zend-servicemanager): - Adding package zendframework/zend-servicemanager (^3.3) - Copying config/container.php
  56. 56. @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): - Adding package zendframework/zend-expressive-fastroute (^3.0.0alpha1) - Whitelist package zendframework/zend-expressive-fastroute - Copying config/routes.php
  57. 57. @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):
  58. 58. @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
  59. 59. @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": "https://docs.zendframework.com/zend-expressive/" }
  60. 60. @asgrim Create the endpoints
  61. 61. @asgrim Book entity class Book { /** * @var string */ private $id; /** * @var bool */ private $inStock = true; public function __construct() { $this->id = (string)Uuid::uuid4(); }
  62. 62. @asgrim Book entity class Book { /** * @return void * @throws AppEntityExceptionBookNotAvailable */ public function checkOut() { if (!$this->inStock) { throw ExceptionBookNotAvailable::fromBook($this); } $this->inStock = false; }
  63. 63. @asgrim Book entity class Book { /** * @return void * @throws AppEntityExceptionBookAlreadyStocked */ public function checkIn() { if ($this->inStock) { throw ExceptionBookAlreadyStocked::fromBook($this); } $this->inStock = true; }
  64. 64. @asgrim FindBookByUuidInterface interface FindBookByUuidInterface { /** * @param UuidInterface $slug * @return Book * @throws ExceptionBookNotFound */ public function __invoke(UuidInterface $slug): Book; }
  65. 65. @asgrim CheckOutHandler public function process(ServerRequestInterface $request, RequestHandler $handler): 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()), ]); }
  66. 66. @asgrim return function ( Application $app, MiddlewareFactory $factory, ContainerInterface $container ): void { $app->pipe(ZendStratigilityMiddlewareErrorHandler::class); $app->pipe(ZendExpressiveRouterMiddlewarePathBasedRoutingMiddleware::class); // Routing $app->pipe(ZendExpressiveRouterMiddlewareDispatchMiddleware::class); // Dispatch $app->pipe(ZendExpressiveHandlerNotFoundHandler::class); }; Define application pipeline - pipeline.php
  67. 67. @asgrim return function ( Application $app, MiddlewareFactory $factory, ContainerInterface $container ): void { $app->get( '/book/{id}/check-out', AppHandlerCheckOutHandler::class, 'check-out' ); $app->get( '/book/{id}/check-in', AppHandlerCheckInHandler::class, 'check-in' ); }; Define routes - routes.php
  68. 68. @asgrim Adding some ORM
  69. 69. @asgrim Your application Doctrine quick overview DB DBAL ORM (EntityManager) Entities Finders, Services, ...
  70. 70. @asgrim container-interop-doctrine
  71. 71. @asgrim Installation $ composer require dasprid/container-interop-doctrine Using version ^1.1 for dasprid/container-interop-doctrine ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 9 installs, 0 updates, 0 removals - Installing doctrine/lexer (v1.0.1): Loading from cache - Installing doctrine/inflector (v1.3.0): Loading from cache - Installing doctrine/collections (v1.5.0): Loading from cache - Installing doctrine/cache (v1.7.1): Loading from cache - Installing doctrine/annotations (v1.6.0): Loading from cache - Installing doctrine/common (v2.8.1): Loading from cache - Installing doctrine/dbal (v2.6.3): Loading from cache - Installing doctrine/orm (v2.6.1): Loading from cache - Installing dasprid/container-interop-doctrine (1.1.0): Loading from cache Writing lock file Generating autoload files
  72. 72. @asgrim src/App/ConfigProvider.php use DoctrineORMEntityManagerInterface; use ContainerInteropDoctrineEntityManagerFactory; final class ConfigProvider { // ... public function getDependencies() : array { return [ 'factories' => [ EntityManagerInterface::class => EntityManagerFactory::class, // ... other services ], ];
  73. 73. @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
  74. 74. @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
  75. 75. @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
  76. 76. @asgrim Annotating the Entities
  77. 77. @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
  78. 78. @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
  79. 79. @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
  80. 80. @asgrim try { $this->entityManager->transactional(function () use ($book) { $book->checkOut(); }); } catch (BookNotAvailable $bookNotAvailable) { return new JsonResponse(['info' => $bookNotAvailable->getMessage()], 423); } src/App/Handler/CheckOutHandler.php
  81. 81. @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! $
  82. 82. @asgrim Insert some data INSERT INTO book (id, name, in_stock) VALUES ( '1c06bec9-adae-47c2-b411-73b1db850e6f', 'The Great Escape', true );
  83. 83. @asgrim /book/1c06bec9-adae-47c2-b411-.../check-out {"info":"You have checked out The Great Escape"}
  84. 84. @asgrim /book/1c06bec9-adae-47c2-b411-.../check-in {"info":"You have checked in The Great Escape"}
  85. 85. @asgrim Automatic Flushing Middleware
  86. 86. @asgrim FlushingMiddleware public function process(Request $request, RequestHandler $handler) { $response = $handler->handle($request); if ($this->entityManager->isOpen()) { $this->entityManager->flush(); } return $response; }
  87. 87. @asgrim FlushingMiddleware public function process(Request $request, RequestHandler $handler) { $response = $handler->handle($request); if ($this->entityManager->isOpen()) { $this->entityManager->flush(); } return $response; }
  88. 88. @asgrim FlushingMiddleware public function process(Request $request, RequestHandler $handler) { $response = $handler->handle($request); if ($this->entityManager->isOpen()) { $this->entityManager->flush(); } return $response; }
  89. 89. @asgrim Add to pipeline return function ( Application $app, MiddlewareFactory $factory, ContainerInterface $container ): void { $app->pipe(ZendStratigilityMiddlewareErrorHandler::class); $app->pipe(ZendExpressiveRouterMiddlewarePathBasedRoutingMiddleware::class); // Routing $app->pipe(AppMiddlewareFlushingMiddleware::class); $app->pipe(ZendExpressiveRouterMiddlewareDispatchMiddleware::class); // Dispatch $app->pipe(ZendExpressiveHandlerNotFoundHandler::class); };
  90. 90. @asgrim Doing more with middleware
  91. 91. @asgrim Authentication
  92. 92. @asgrim public function process( ServerRequestInterface $request, RequestHandler $handler ) : Response { $queryParams = $request->getQueryParams(); // DO NOT DO THIS IN REAL LIFE // It's really not secure ;) if (!array_key_exists('authenticated', $queryParams) || $queryParams['authenticated'] !== '1') { return new JsonResponse(['error' => 'You are not authenticated.'], 403); } return $handler->handle($request); } Create the middleware
  93. 93. @asgrim public function process( ServerRequestInterface $request, RequestHandler $handler ) : Response { $queryParams = $request->getQueryParams(); // DO NOT DO THIS IN REAL LIFE // It's really not secure ;) if (!array_key_exists('authenticated', $queryParams) || $queryParams['authenticated'] !== '1') { return new JsonResponse(['error' => 'You are not authenticated.'], 403); } return $handler->handle($request); } Create the middleware
  94. 94. @asgrim public function process( ServerRequestInterface $request, RequestHandler $handler ) : Response { $queryParams = $request->getQueryParams(); // DO NOT DO THIS IN REAL LIFE // It's really not secure ;) if (!array_key_exists('authenticated', $queryParams) || $queryParams['authenticated'] !== '1') { return new JsonResponse(['error' => 'You are not authenticated.'], 403); } return $handler->handle($request); } Create the middleware
  95. 95. @asgrim public function process( ServerRequestInterface $request, RequestHandler $handler ) : Response { $queryParams = $request->getQueryParams(); // DO NOT DO THIS IN REAL LIFE // It's really not secure ;) if (!array_key_exists('authenticated', $queryParams) || $queryParams['authenticated'] !== '1') { return new JsonResponse(['error' => 'You are not authenticated.'], 403); } return $handler->handle($request); } Create the middleware
  96. 96. @asgrim return function ( Application $app, MiddlewareFactory $factory, ContainerInterface $container ): void { $app->pipe(ZendStratigilityMiddlewareErrorHandler::class); $app->pipe(ZendExpressiveRouterMiddlewarePathBasedRoutingMiddleware::class); // Routing $app->pipe(AppMiddlewareFlushingMiddleware::class); $app->pipe(AppMiddlewareAuthenticationMiddleware::class); $app->pipe(ZendExpressiveRouterMiddlewareDispatchMiddleware::class); // Dispatch $app->pipe(ZendExpressiveHandlerNotFoundHandler::class); }; Add middleware to pipe
  97. 97. @asgrim Helios composer require dasprid/helios
  98. 98. @asgrim PSR7-Session composer require psr7-sessions/storageless
  99. 99. @asgrim public function __invoke(ContainerInterface $container, $_, array $_ = null) { $symmetricKey = 'do-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 SystemClock() ); } Factory the middleware
  100. 100. @asgrim return function ( Application $app, MiddlewareFactory $factory, ContainerInterface $container ): void { $app->pipe(ZendStratigilityMiddlewareErrorHandler::class); $app->pipe(ZendExpressiveRouterMiddlewarePathBasedRoutingMiddleware::class); // Routing $app->pipe(AppMiddlewareFlushingMiddleware::class); $app->pipe(PSR7SessionsStoragelessHttpSessionMiddleware::class); $app->pipe(AppMiddlewareAuthenticationMiddleware::class); $app->pipe(ZendExpressiveRouterMiddlewareDispatchMiddleware::class); // Dispatch $app->pipe(ZendExpressiveHandlerNotFoundHandler::class); }; Add middleware to pipe
  101. 101. @asgrim $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE); $session->set('counter', $session->get('counter', 0) + 1); Session is stored in Request
  102. 102. @asgrim $skills++;
  103. 103. @asgrim To summarise... ● PSR-7 & PSR-15 lay the foundations! ● Diactoros is just a PSR-7 implementation ● Stratigility is a middleware pipeline: the main bit ● Expressive is a glue for everything ● ConfigProvider is great for aggregating (merging) config ● container-interop-doctrine makes Doctrine work easier ● Middleware all the things! ● Similar concepts in other frameworks (e.g. Slim)
  104. 104. @asgrim Useful references ● https://github.com/asgrim/book-library ● https://framework.zend.com/blog/2017-12-14-expressive-3-dev.html ● https://framework.zend.com/blog/2017-03-13-expressive-2-migration.html ● https://framework.zend.com/blog/2017-03-30-expressive-config-routes.html ● https://mwop.net/blog/2016-05-16-programmatic-expressive.html ● https://docs.zendframework.com/zend-expressive/
  105. 105. Any questions? James Titcumb @asgrim

×