Make your Application
Expressive
By Christian Varela

@gabriel0702

cvarela@conquerorsoft.com

https://joind.in/talk/b5ac8
1
Slides at:
[link]
2
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
Conqueror Soft will take your business to the next Level!
5
www.conquerorsoft.com
info@conquerorsoft.com
facebook.com/conquerorsoft
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
Expressive
Expressive it is a framework by Zend that allows you to
write PSR-7 middleware applications for the web.
7
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
HTTP messages
1. They are messages between servers and clients in the web

1. HTTP request messages

2. HTTP response messages
9
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
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
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
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
PSR-7 PsrHttpMessage interfaces
1.MessageInterface

1.RequestInterface

1.ServerRequestInterface

2.ResponseInterface
14
PSR-7 other interfaces
1.StreamInterface

2.UploadedFileInterface

3.UriInterface
15
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
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
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
Middleware
19
In (request) Out (response)
to delegate from delegate
Middleware
20
In (request) Out (response)
Middleware
21
In (request) Out (response)
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
How expressive puts all that in
practice?
23
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
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
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
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
Zend-Stratigility
1. Routing

2. PSR-11 container (dependency injection)

3. Templating

4. Error handling
28
Routers supported
1. Aura.Router

2. FastRoute

3. zend-mvc Router
29
Containers supported
1. Zend-servicemanager

2. pimple-interop

3. aura.di
30
Templates supported
1. Plates

2. Twig

3. zend-view
31
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
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
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
Creating a Web Application
with expressive
35
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
https://bitbucket.org/
gabriel0702/quotesexpressive
37
Authors list
38
Creating the project
composer create-project zendframework/zend-expressive-skeleton
QuotesExpressive
39
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
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
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
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
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
Create module
php vendor/bin/expressive module:create Authors

composer dump-autoload
45
Create module
src/Authors
├── src
│   └── ConfigProvider.php
└── templates
46
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
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
routes.php
1 <?php
2 ...
3 $app->get(
4 '/author',
5 AuthorsActionListAction::class,
6 'authors.list'
7 );
8 ...
49
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
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
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
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
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
One class per action?!?
56
Handling multiples routes in a
single class
57
Adding authors
58
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
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
routes.php
1 <?php
2 ...
3 $app->route(
4 '/author[/{action:add|edit}[/{id}]]',
5 AuthorsActionAuthorsPage::class,
6 ['GET','POST'],
7 'author');
8 ...
61
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
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
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
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
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
Editing authors
68
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
Editing authors
5. Modify factories in order to pass settings later

6. Configure editAction

7. Configure edit template
70
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
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
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
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
Deleting authors
76
Deleting authors
1. Create delete action

2. Create template for delete

3. Create deleteAuthor in AuthorRestCollection

4. Modify routes to accept delete action
77
routes.php
1 <?php
2 $app->route(
3 '/author[/{action:add|edit|delete}[/{id}]]',
4 AuthorsActionAuthorsPage::class,['GET','POST'],
5 'author'
6 );
78
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
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
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
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
Questions?
84
Thank you
85
https://joind.in/talk/b5ac8

Make your application expressive

  • 1.
    Make your Application Expressive ByChristian Varela @gabriel0702 cvarela@conquerorsoft.com https://joind.in/talk/b5ac8 1
  • 2.
  • 3.
    Christian Varela • Ihave 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.
  • 5.
    Conqueror Soft willtake your business to the next Level! 5 www.conquerorsoft.com info@conquerorsoft.com facebook.com/conquerorsoft
  • 6.
    What is thissession 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.
    Expressive Expressive it isa framework by Zend that allows you to write PSR-7 middleware applications for the web. 7
  • 8.
    PSR-7 1. PHP StandardRecommendation 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.
    HTTP messages 1. Theyare messages between servers and clients in the web 1. HTTP request messages 2. HTTP response messages 9
  • 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.
    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.
    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.
    HTTP Response messages HTTP/1.1201 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.
  • 15.
  • 16.
    Middleware Middleware is codethat 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.
    Middleware For example, youcould have a collection of middleware like this: 1.reverse string 2.convert string to upper case 3.shuffle string 17
  • 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.
    Middleware 19 In (request) Out(response) to delegate from delegate
  • 20.
  • 21.
  • 22.
    Middleware 22 Error handler Reverse Uppercase Shuffle Default In OutMiddleware something GSNEHOMIT Error something GSNEHOMIT Reverse something TIMOHENSG Uppercase something timohensg Shuffle something something Default
  • 23.
    How expressive putsall that in practice? 23
  • 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.
    Expressive 2 middleware 1function ( 2 ServerRequestInterface $request, 3 DelegateInterface $delegate) 4 { 5 $response = $delegate->process($request); 6 return $response->withHeader('X-Test', time()); 7 } 25
  • 26.
    Expressive 2 defaultmiddlewares 1. Route middleware: parses and identifies the middleware to handle the request 2. Dispatching middleware: call the middleware identified by route 26
  • 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.
    Zend-Stratigility 1. Routing 2. PSR-11container (dependency injection) 3. Templating 4. Error handling 28
  • 29.
    Routers supported 1. Aura.Router 2.FastRoute 3. zend-mvc Router 29
  • 30.
  • 31.
  • 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.
    Zend-Diactoros zend-diactoros is a PHPpackage 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.
    Interop 0.4.1 1 <?php 2namespace 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.
    Creating a WebApplication with expressive 35
  • 36.
    Web application 1. Itwill 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.
  • 38.
  • 39.
    Creating the project composercreate-project zendframework/zend-expressive-skeleton QuotesExpressive 39
  • 40.
    Choose application type Whattype 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.
    Choose Container forDI 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.
    Choose Router Which routerdo 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.
    Choose Template engine Whichtemplate 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.
    Choose Error handler Whicherror 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.
    Create module php vendor/bin/expressivemodule:create Authors composer dump-autoload 45
  • 46.
    Create module src/Authors ├── src │  └── ConfigProvider.php └── templates 46
  • 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.
    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.
    routes.php 1 <?php 2 ... 3$app->get( 4 '/author', 5 AuthorsActionListAction::class, 6 'authors.list' 7 ); 8 ... 49
  • 50.
    dependencies.global.php 1 <?php 2 ... 3AuthorsActionListAction::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.
    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.
    ListActionFactory.php 1 <?php 2 ... 3public 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.
    ListAction.php 1 <?php 2 ... 3class 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.
    AuthorRestCollection.php 1 <?php 2 classAuthorRestCollection 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.
  • 56.
    One class peraction?!? 56
  • 57.
    Handling multiples routesin a single class 57
  • 58.
  • 59.
    Multiple routes insingle 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.
    Multiple routes insingle class 8. Test index action 9. Create form 10.Add addAction 11.Create add template 12.Add saveAuthor method to AuthorRestCollection 60
  • 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.
    AbstractPage.php 1 <?php 2 ... 3abstract 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.
    AuthorsPageFactory.php 1 <?php 2 ... 3class 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.
    AuthorsPage.php 1 <?php 2 ... 3class 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.
    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.
    AuthorRestCollection.php 1 <?php 2 ... 3public 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.
  • 68.
  • 69.
    Editing authors 1. Addedit 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.
    Editing authors 5. Modifyfactories in order to pass settings later 6. Configure editAction 7. Configure edit template 70
  • 71.
    dependencies.global.php 1 <?php 2 ... 3AuthorsModelAuthorRestCollection::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.
    AuthorsPage.php editAction 1 <?php 2public 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.
    AuthorRestCollection saveAuthor 1 publicfunction 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.
    AuthorRestCollection getAuthor 1 publicfunction 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.
  • 76.
  • 77.
    Deleting authors 1. Createdelete action 2. Create template for delete 3. Create deleteAuthor in AuthorRestCollection 4. Modify routes to accept delete action 77
  • 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.
    AuthorsPage.php deleteAction 1 <?php 2public 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.
    AuthorRestCollection deleteAuthor 1 <?php 2public 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.
  • 82.
    Middleware advantages 1. Easyto 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.
    Middleware disadvantages 1. It’sa 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.
  • 85.