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.

Make your application expressive

Learn the concepts of PSR-7 middleware with Zend Expressive and how your application could be developed from scratch adapting those concepts with a new mindset. You'll see the different approaches, advantages and disadvantages, and the contrast of this paradigm and other more conventional paradigms.

  • Login to see the comments

  • Be the first to like this

Make your application expressive

  1. 1. Make your Application Expressive By Christian Varela @gabriel0702 cvarela@conquerorsoft.com https://joind.in/talk/b5ac8 1
  2. 2. Slides at: [link] 2
  3. 3. Christian Varela • I have a wife and 3 daughters • I am from Mexico • Master Degree in Computer Science • 13 years programming with PHP • I live in Miami • I created Conqueror Soft Inc • I play guitar and piano 3
  4. 4. 4
  5. 5. Conqueror Soft will take your business to the next Level! 5 www.conquerorsoft.com info@conquerorsoft.com facebook.com/conquerorsoft
  6. 6. What is this session about? Learn the concepts of PSR-7 middleware with Zend Expressive and how your application could be developed from scratch adapting those concepts with a new mindset. You'll see the different approaches, advantages and disadvantages, and the contrast of this paradigm and other more conventional paradigms. 6
  7. 7. Expressive Expressive it is a framework by Zend that allows you to write PSR-7 middleware applications for the web. 7
  8. 8. PSR-7 1. PHP Standard Recommendation 7 2. HTTP message interfaces 3. Describes common interfaces for representing HTTP messages as described in RFC 7230 and RFC 7231 4. Describes interfaces for representing URIs for use with HTTP messages as described in RFC 3986. 8
  9. 9. HTTP messages 1. They are messages between servers and clients in the web 1. HTTP request messages 2. HTTP response messages 9
  10. 10. HTTP Request messages 1. Request line 1. Method 2. Target 3. HTTP protocol version 2. One or more headers 3. empty line 4. Message body 10
  11. 11. HTTP Request messages POST /authors HTTP/1.1 Host: localhost:8081 Content-Type: application/json Authorization: Basic [replace_with_password] Cache-Control: no-cache Postman-Token: 1b196a8d-c32b-be43-5523-d5354e5b5cc5 { "first_name":"First_name", "last_name":"Last_name", "dob":"1900-01-01" } 11
  12. 12. HTTP Response messages 1. Status line 1. HTTP protocol version Method 2. HTTP status code 3. Reason phrase 2. One or more headers 3. empty line 4. Message body 12
  13. 13. HTTP Response messages HTTP/1.1 201 Created host:"localhost:8081" date:"Sun, 08 Oct 2017 21:07:33 +0000" connection:"close" x-powered-by:"PHP/7.1.1" location:"http://localhost:8081/authors/33" content-location:"http://localhost:8081/authors/33" content-type:"application/json" vary:"Origin" { "entity_id": "33", "first_name": "First_name", "last_name": "Last_name", "dob": "1900-01-01", "created_at": null } 13
  14. 14. PSR-7 PsrHttpMessage interfaces 1.MessageInterface 1.RequestInterface 1.ServerRequestInterface 2.ResponseInterface 14
  15. 15. PSR-7 other interfaces 1.StreamInterface 2.UploadedFileInterface 3.UriInterface 15
  16. 16. Middleware Middleware is code that exists between the request and response, and which can take the incoming request, perform actions based on it, and either complete the response or pass delegation on to the next middleware in the queue. 16
  17. 17. Middleware For example, you could have a collection of middleware like this: 1.reverse string 2.convert string to upper case 3.shuffle string 17
  18. 18. Middleware 1 $app=new Middleware(); 2 $app->pipe($errorHandler); 3 $app->pipe($reverseMiddleware); 4 $app->pipe($uppercaseMiddleware); 5 $app->pipe($shuffleMiddleware); 6 $app->pipe($defaultMiddleware); 7 $app->run(); 18
  19. 19. Middleware 19 In (request) Out (response) to delegate from delegate
  20. 20. Middleware 20 In (request) Out (response)
  21. 21. Middleware 21 In (request) Out (response)
  22. 22. Middleware 22 Error handler Reverse Uppercase Shuffle Default In Out Middleware something GSNEHOMIT Error something GSNEHOMIT Reverse something TIMOHENSG Uppercase something timohensg Shuffle something something Default
  23. 23. How expressive puts all that in practice? 23
  24. 24. Expressive 1.X middleware 1 <?php 2 function ( 3 ServerRequestInterface $request, 4 ResponseInterface $response, 5 callable $next) 6 { 7 $response = $next($request, $response); 8 return $response->withHeader('X-Test', time()); 9 } 24
  25. 25. Expressive 2 middleware 1 function ( 2 ServerRequestInterface $request, 3 DelegateInterface $delegate) 4 { 5 $response = $delegate->process($request); 6 return $response->withHeader('X-Test', time()); 7 } 25
  26. 26. Expressive 2 default middlewares 1. Route middleware: parses and identifies the middleware to handle the request 2. Dispatching middleware: call the middleware identified by route 26
  27. 27. Expressive implementations 1. psr/http-message (Common interface for HTTP messages) (based on PSR-7) 1. zend-stratigility (Middleware for PHP) 2. zend-diactoros (PSR HTTP Message implementations) 3. http-interop/http-middleware (Common interface for HTTP server-side middleware) 27
  28. 28. Zend-Stratigility 1. Routing 2. PSR-11 container (dependency injection) 3. Templating 4. Error handling 28
  29. 29. Routers supported 1. Aura.Router 2. FastRoute 3. zend-mvc Router 29
  30. 30. Containers supported 1. Zend-servicemanager 2. pimple-interop 3. aura.di 30
  31. 31. Templates supported 1. Plates 2. Twig 3. zend-view 31
  32. 32. Error handler supported 1. Whoops: whoops is an error handler framework for PHP. Out-of-the-box, it provides a pretty error interface that helps you debug your web projects, but at heart it's a simple yet powerful stacked error handling system. 32
  33. 33. Zend-Diactoros zend-diactoros is a PHP package containing implementations of the accepted PSR-7 HTTP message interfaces, as well as a "server" implementation similar to node's http.Server. All the responses will be handled with Diactoros. 33
  34. 34. Interop 0.4.1 1 <?php 2 namespace InteropHttpServerMiddleware; 3 4 use PsrHttpMessageResponseInterface; 5 use PsrHttpMessageServerRequestInterface; 6 7 interface MiddlewareInterface 8 { 9 public function process( 10 ServerRequestInterface $request, 11 DelegateInterface $delegate 12 ) : ResponseInterface; 13 } 14 15 interface DelegateInterface 16 { 17 public function process( 18 ServerRequestInterface $request 19 ) : ResponseInterface; 20 } 34
  35. 35. Creating a Web Application with expressive 35
  36. 36. Web application 1. It will consume a remote API for authors to be able to do: 1. List of authors 2. Adding authors 3. Editing authors 4. Deleting authors 36
  37. 37. https://bitbucket.org/ gabriel0702/quotesexpressive 37
  38. 38. Authors list 38
  39. 39. Creating the project composer create-project zendframework/zend-expressive-skeleton QuotesExpressive 39
  40. 40. Choose application type 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): 3 40
  41. 41. Choose Container for DI 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): 41
  42. 42. Choose 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): 42
  43. 43. Choose Template engine 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):1 43
  44. 44. Choose Error handler 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): 44
  45. 45. Create module php vendor/bin/expressive module:create Authors composer dump-autoload 45
  46. 46. Create module src/Authors ├── src │   └── ConfigProvider.php └── templates 46
  47. 47. Create Authors’ list 1. Configure templates in ConfigProvider 2. Create Authors list action class (ListAction.php) 3. Register class in dependencies in configuration. 4. Configure routes in routes.php 5. Generate factory for ListAction 6. Update the dependencies in dependencies.global.php 47
  48. 48. Create Authors’ list 7. Configure global.php file 8. composer require zendframework/zend-http 9. Configure local.php with credentials 10.composer require zendframework/zend-json 11.Modify list template 12.create AuthorRestCollection 13.php -S 0.0.0.0:8082 -t public 48
  49. 49. routes.php 1 <?php 2 ... 3 $app->get( 4 '/author', 5 AuthorsActionListAction::class, 6 'authors.list' 7 ); 8 ... 49
  50. 50. dependencies.global.php 1 <?php 2 ... 3 AuthorsActionListAction::class => AuthorsActionListActionFactory::class, 4 AuthorsModelAuthorRestCollection::class=>function($container) { 5 $httpClient=$container->get(AuthorsModelAuthorHttpClient::class); 6 return new AuthorsModelAuthorRestCollection($httpClient); 7 }, 8 AuthorsModelAuthorHttpClient::class => function($container) { 9 $config=$container->get('config'); 10 $client=new Client($config['httpclient']['base_uri']. 11 $config['httpclient']['authors']['route']); 12 $client->setHeaders($config['httpclient']['headers']); 13 $client->setAuth( 14 $config['httpclient']['basic_auth']['user'], 15 $config['httpclient']['basic_auth']['password'] 16 ); 17 return $client; 18 }, 19 ... 50
  51. 51. global.php 1 <?php 2 return [ 3 'httpclient'=>[ 4 'base_uri'=>'http://localhost:8081/', 5 'headers'=>[ 6 'Accept'=>'*/*', 7 ], 8 'authors'=>[ 9 'route'=>'authors', 10 ], 11 ], 12 ]; 51
  52. 52. ListActionFactory.php 1 <?php 2 ... 3 public function __invoke( 4 ContainerInterface $container, 5 $requestedName, 6 array $options = null) 7 { 8 return new ListAction( 9 $container->get(... TemplateRendererInterface::class), 10 $container->get(...AuthorRestCollection::class) 11 ); 12 } 13 ... 52
  53. 53. ListAction.php 1 <?php 2 ... 3 class ListAction implements MiddlewareInterface 4 { 5 ... 6 public function process( 7 ServerRequestInterface $request, 8 DelegateInterface $delegate 9 ) 10 { 11 $authors=$this->authorrestcollection->fetchAll(); 12 return new HtmlResponse( 13 $this->renderer->render( 14 'author/view::authors-list', 15 ['authors'=>$authors] 16 ) 17 ); 18 } 19 ... 20 } 53
  54. 54. AuthorRestCollection.php 1 <?php 2 class AuthorRestCollection 3 { 4 ... 5 public function fetchAll() 6 { 7 $res=$this->httpClient->send(); 8 $json=new Json(); 9 $all=$json->decode($res->getContent()); 10 return $all; 11 } 12 ... 13 } 54
  55. 55. 55
  56. 56. One class per action?!? 56
  57. 57. Handling multiples routes in a single class 57
  58. 58. Adding authors 58
  59. 59. Multiple routes in single class 1. Create Abstract page class in App 2. Create AuthorsPage class in Authors/Action 3. Create AuthorsPageFactory class 4. Create indexAction in AuthorsPage 5. Create authors-index.phtml template 6. Configure factories in dependencies.global.php 7. Update routes configuration 59
  60. 60. Multiple routes in single class 8. Test index action 9. Create form 10.Add addAction 11.Create add template 12.Add saveAuthor method to AuthorRestCollection 60
  61. 61. routes.php 1 <?php 2 ... 3 $app->route( 4 '/author[/{action:add|edit}[/{id}]]', 5 AuthorsActionAuthorsPage::class, 6 ['GET','POST'], 7 'author'); 8 ... 61
  62. 62. AbstractPage.php 1 <?php 2 ... 3 abstract class AbstractPage 4 implements MiddlewareInterface 5 { 6 public function process( 7 ServerRequestInterface $request, 8 DelegateInterface $delegate 9 ) 10 { 11 $action = $request->getAttribute('action', 'index') . 'Action'; 12 if (! method_exists($this, $action)) { 13 return new EmptyResponse(StatusCode::STATUS_NOT_FOUND); 14 } 15 return $this->$action($request, $delegate); 16 } 17 } 62
  63. 63. AuthorsPageFactory.php 1 <?php 2 ... 3 class AuthorsPageFactory implements FactoryInterface 4 { 5 public function __invoke( 6 ContainerInterface $container, 7 $requestedName, 8 array $options = null) 9 { 10 return new AuthorsPage( 11 $container->get(... TemplateRendererInterface::class), 12 $container->get(...AuthorRestCollection::class) 13 ); 14 } 15 } 63
  64. 64. AuthorsPage.php 1 <?php 2 ... 3 class AuthorsPage extends AbstractPage 4 { 5 ... 6 public function indexAction( 7 ServerRequestInterface $request, 8 DelegateInterface $delegate) 9 { 10 $authors=$this->authorrestcollection->fetchAll(); 11 return new HtmlResponse( 12 $this->renderer->render('author/view::authors-index', ['authors'=>$authors]) 13 ); 14 } 15 ... 16 } 64
  65. 65. AuthorsPage.php addAction 1 <?php 2 ... 3 public function addAction( 4 ServerRequestInterface $request, 5 DelegateInterface $delegate 6 ) 7 { 8 if($request->getMethod()!=='POST') 9 { 10 return new HtmlResponse( 11 $this->renderer->render('author/view::authors-add') 12 ); 13 } 14 ... 15 if(!$form->isValid()) 16 { 17 return new HtmlResponse( 18 $this->renderer->render('author/view::authors-add') 19 ); 20 } 21 $author->exchangeArray($form->getData()); 22 $this->authorrestcollection->saveAuthor($author); 23 return new RedirectResponse("/author"); 24 } 65
  66. 66. AuthorRestCollection.php 1 <?php 2 ... 3 public function saveAuthor(Author $author) 4 { 5 $data=$author->getData(); 6 $entity_id=(int)$data['entity_id']; 7 8 if(empty($entity_id)) 9 { 10 $this->httpClient->setMethod('POST'); 11 } 12 else 13 { 14 $this->httpClient->setMethod('PUT'); 15 } 16 $json=$this->getJson(); 17 $body=$json->encode($data); 18 19 $this->httpClient->setRawBody($body); 20 $res=$this->httpClient->send(); 21 if($res->getStatusCode()!=201) 22 { 23 throw new RuntimeException("The author could not be saved."); 24 } 25 } 66
  67. 67. 67
  68. 68. Editing authors 68
  69. 69. Editing authors 1. Add edit action 2. Add edit template 3. Create getAuthor in RestCollection 4. Change the condition to be 200 or 201 depending on the presence of entity_id 69
  70. 70. Editing authors 5. Modify factories in order to pass settings later 6. Configure editAction 7. Configure edit template 70
  71. 71. dependencies.global.php 1 <?php 2 ... 3 AuthorsModelAuthorRestCollection::class=>function($container) { 4 $httpClient=$container->get(...AuthorHttpClient::class); 5 $config=$container->get('config'); 6 return new ...AuthorRestCollection( 7 $httpClient, 8 $config['httpclient'] 9 ); 10 }, 11 AuthorsModelAuthorHttpClient::class => function() { 12 return new Client(); 13 }, 14 AuthorsActionAuthorsPage::class => AuthorsActionAuthorsPageFactory::class, 15 ... 71
  72. 72. AuthorsPage.php editAction 1 <?php 2 public function editAction( 3 ServerRequestInterface $request, 4 DelegateInterface $delegate 5 ) 6 { 7 ... 8 if($entity_id===0) 9 { 10 return $this->redirect()->toRoute('author',['action'=>'add']); 11 } 12 $author=$this->authorrestcollection->getAuthor($entity_id); 13 ... 14 if($request->getMethod()!=='POST') 15 { 16 return new HtmlResponse( 17 $this->renderer->render('author/view::authors-edit', $viewData) 18 ); 19 } 20 ... 21 $this->authorrestcollection->saveAuthor($author); 22 return new RedirectResponse("/author"); 23 } 72
  73. 73. AuthorRestCollection saveAuthor 1 public function saveAuthor(Author $author) 2 { 3 ... 4 if(empty($entity_id) and $res->getStatusCode()!=201) 5 { 6 throw new RuntimeException("The author could not be saved."); 7 } 8 if(!empty($entity_id) and $res->getStatusCode()!=200) 9 { 10 throw new RuntimeException("The author could not be saved."); 11 } 12 } 73
  74. 74. AuthorRestCollection getAuthor 1 public function getAuthor($id) 2 { 3 $id=(int)$id; 4 $this->httpClient->setMethod('GET'); 5 $this->httpClient->setUri($this->httpClientUri."/$id"); 6 $res=$this->httpClient->send(); 7 if($res->getStatusCode()!=200) 8 { 9 throw new RuntimeException(sprintf("There is no author with ID %d", $id)); 10 } 11 $json=$this->getJson(); 12 $all=$json->decode($res->getContent()); 13 $author=new Author(); 14 $author->exchangeArray(get_object_vars($all)); 15 return $author; 16 } 74
  75. 75. 75
  76. 76. Deleting authors 76
  77. 77. Deleting authors 1. Create delete action 2. Create template for delete 3. Create deleteAuthor in AuthorRestCollection 4. Modify routes to accept delete action 77
  78. 78. routes.php 1 <?php 2 $app->route( 3 '/author[/{action:add|edit|delete}[/{id}]]', 4 AuthorsActionAuthorsPage::class,['GET','POST'], 5 'author' 6 ); 78
  79. 79. AuthorsPage.php deleteAction 1 <?php 2 public function deleteAction( 3 ServerRequestInterface $request, 4 DelegateInterface $delegate 5 ) 6 { 7 ... 8 if($entity_id===0) 9 { 10 return $this->redirect()->toRoute('author',['action'=>'add']); 11 } 12 if($request->getMethod()==='POST') 13 { 14 ... 15 if($del==='Yes') 16 { 17 $id=(int) $post['entity_id']; 18 $this->authorrestcollection->deleteAuthor($id); 19 } 20 return new RedirectResponse("/author"); 21 } 22 $author=$this->authorrestcollection->getAuthor($entity_id); 23 return new HtmlResponse( 24 $this->renderer->render("author/view::authors-delete", ['entity_id'=>$entity_id, 'author'=>$author]) 25 ); 79
  80. 80. AuthorRestCollection deleteAuthor 1 <?php 2 public function deleteAuthor($id) 3 { 4 $id=(int)$id; 5 $this->httpClient->setMethod('DELETE'); 6 $this->httpClient->setUri($this- >httpClientUri."/$id"); 7 $res=$this->httpClient->send(); 8 if($res->getStatusCode()!=204) 9 { 10 throw new RuntimeException(sprintf("The author with ID %d was not deleted", $id)); 11 } 12 } 80
  81. 81. 81
  82. 82. Middleware advantages 1. Easy to learn 2. Easy to implement 3. You don’t loose modularity if you are used to MVC 4. Flexibility, you can use other libraries for routing, templating, DI, etc. 5. It adapts to small and big projects. 6. Easy to implement Zend-* libraries if you are used to them 82
  83. 83. Middleware disadvantages 1. It’s a relatively “new” conceptual implementation in PHP, which is subject to more changes or adjustments and you’ll need to be ready if you want to catch up with updates 2. since it’s relatively “new” also, there are no too many people with experience on this. 83
  84. 84. Questions? 84
  85. 85. Thank you 85 https://joind.in/talk/b5ac8

×