Building Websites with Zend Expressive 3 Rob Allen, Nineteen Feet February 2018 ~ @akrabat
A presentation at PHP UK Conference 2018 in February 2018 in London, UK by Rob Allen
 
                Building Websites with Zend Expressive 3 Rob Allen, Nineteen Feet February 2018 ~ @akrabat
 
                A microframework with full stack components Rob Allen ~ @akrabat
 
                µ Framework core • Router • Container • Template renderer • Error handler • Configuration Rob Allen ~ @akrabat
 
                Ecosystem • Filtering and validation • API rendering: HAL & Problem-API • Database abstraction • Session handling • Logging • Mail • Pagination • Caching Rob Allen ~ @akrabat
 
                Agnostic Router: FastRoute, Aura.Router or Zend Router DI Container: Aura.Di, Auryn, Pimple, Symfony DI Container or Zend-ServiceManager Template: Plates, Twig or Zend View Rob Allen ~ @akrabat
 
                Middleware pipeline Rob Allen ~ @akrabat
 
                PSR-15 MiddlewareInterface namespace Psr\Http\Server ; use Psr\Http\Message\ResponseInterface ; use Psr\Http\Message\ServerRequestInterface ; interface MiddlewareInterface { public function process ( ServerRequestInterface $request , RequestHandlerInterface $handler ) : ResponseInterface ; } Rob Allen ~ @akrabat
 
                PSR-15 MiddlewareInterface namespace Psr\Http\Server ; use Psr\Http\Message\ResponseInterface ; use Psr\Http\Message\ServerRequestInterface ; interface MiddlewareInterface { public function process ( ServerRequestInterface $request , RequestHandlerInterface $handler ) : ResponseInterface ; } Rob Allen ~ @akrabat
 
                PSR-15 MiddlewareInterface namespace Psr\Http\Server ; use Psr\Http\Message\ResponseInterface ; use Psr\Http\Message\ServerRequestInterface ; interface MiddlewareInterface { public function process ( ServerRequestInterface $request , RequestHandlerInterface $handler ) : ResponseInterface ; } Rob Allen ~ @akrabat
 
                PSR-15 MiddlewareInterface namespace Psr\Http\Server ; use Psr\Http\Message\ResponseInterface ; use Psr\Http\Message\ServerRequestInterface ; interface MiddlewareInterface { public function process ( ServerRequestInterface $request , RequestHandlerInterface $handler ) : ResponseInterface ; } Rob Allen ~ @akrabat
 
                write ( " < !-- Time: $taken --
" ); return $response ; } } Rob Allen ~ @akrabat
 
                write ( " < !-- Time: $taken --
" ); return $response ; } } Rob Allen ~ @akrabat
 
                write ( " < !-- Time: $taken --
" ); return $response ; } } Rob Allen ~ @akrabat
 
                write ( " < !-- Time: $taken --
" ); return $response ; } } Rob Allen ~ @akrabat
 
                write ( " < !-- Time: $taken --
" ); return $response ; } } Rob Allen ~ @akrabat
 
                Getting started Rob Allen ~ @akrabat
 
                Skeleton $ composer create-project zendframework/zend-expressive-skeleton new-app Rob Allen ~ @akrabat
 
                Skeleton $ composer create-project zendframework/zend-expressive-skeleton new-app Rob Allen ~ @akrabat
 
                Skeleton Rob Allen ~ @akrabat
 
                Directory structure . ├── bin/ ├── data/ ├── config/ ├── public/ │ ├── autoload/ │ ├── css/ │ │ ├── dependencies.global.php │ ├── js/ │ │ ├── development.local.php │ └── index.php │ │ ├── development.local.php.dist ├── src/ │ │ ├── local.php.dist │ └── App/ │ │ ├── router.global.php ├── test/ │ │ ├── templates.global.php │ └── AppTest/ │ │ └── zend-expressive.global.php ├── vendor/ │ ├── config.php ├── composer.json │ ├── container.php ├── composer.lock │ ├── development.config.php ├── phpcs.xml.dist │ ├── development.config.php.dist └── phpunit.xml.dist │ ├── pipeline.php │ └── routes.php Rob Allen ~ @akrabat
 
                src/App directory • Each module lives in its own namespace • Contains all code for application • ConfigProvider class for initialisation • Configuration • DI registration Rob Allen ~ @akrabat
 
                src/App directory structure └── src/ └── App/ ├── src/ │ ├── Handler/ │ │ ├── HomePageFactory.php │ │ ├── HomePageHandler.php │ │ └── PingHandler.php │ └── ConfigProvider.php ├── templates/ │ ├── app/ │ │ └── home-page.html.twig │ ├── error/ │ │ ├── 404.html.twig │ │ └── error.html.twig │ └── layout/ │ └── default.html.twig └── test/ └── AppTest/ └── Handler/ Rob Allen ~ @akrabat
 
                A handler (AKA: an action) namespace App\Handler ; use ... ; class HomePageHandler implements RequestHandlerInterface { public function handle ( ServerRequestInterface $request ) : ResponseInterface { return new HtmlResponse ( ' < p
Hello World < /p
' ); } } Rob Allen ~ @akrabat
 
                Let's write a web page! Rob Allen ~ @akrabat
 
                Bitcoin conversion Create a page that displays the current value of 1 Bitcoin in £ , $ & € Rob Allen ~ @akrabat
 
                get ( '/bitcoin' , App\Handler\BitcoinPageHandler :: class , 'bitcoin' ); Rob Allen ~ @akrabat
 
                get ( '/bitcoin' , App\Handler\BitcoinPageHandler :: class , 'bitcoin' ); Rob Allen ~ @akrabat
 
                HTTP Method $app-
get() $app-
post() $app-
put() $app-
patch() $app-
delete() Multiple methods: $app-
any() $app-
route(…, …, ['GET', 'POST'], …); Rob Allen ~ @akrabat
 
                get ( '/bitcoin' , App\Handler\BitcoinPageHandler :: class , 'bitcoin' ); Rob Allen ~ @akrabat
 
                get ( '/hello' , … ); Rob Allen ~ @akrabat
 
                get ( '/hello' , … ); • Placeholders are wrapped in {
get ( '/hello/{name}' , … ); Rob Allen ~ @akrabat
 
                get ( '/hello' , … ); • Placeholders are wrapped in {
get ( '/hello/{name}' , … ); • Optional segments are wrapped with [
get ( '/news[/{year}[/{month}]]' , … ); Rob Allen ~ @akrabat
 
                get ( '/hello' , … ); • Placeholders are wrapped in {
get ( '/hello/{name}' , … ); • Optional segments are wrapped with [
get ( '/news/{year:\d{4}}}' , … ); // exactly 4 digits Rob Allen ~ @akrabat
 
                get ( '/bitcoin' , App\Handler\BitcoinPageHandler :: class , 'bitcoin' ); Rob Allen ~ @akrabat
 
                'Rob' ]); or in the template: {{ path ( 'user.profile' , { 'name' : 'Rob' }) }} Rob Allen ~ @akrabat
 
                get ( '/bitcoin' , App\Handler\BitcoinPageHandler :: class , 'bitcoin' ); Rob Allen ~ @akrabat
 
                Handlers
•
Receive a PSR-7
Request
•
Manage business logic operations
•
Must return a PSR-7
Response
•
Implemented as PSR-15
RequestHandler
•
Create using CLI tool:
$ composer expressive handler:create 
  App\Handler\BitcoinPageHandler
Rob Allen ~ @akrabat
 
                render ( 'app::bitcoin-page' , $data ) ); } Rob Allen ~ @akrabat
 
                render ( 'app::bitcoin-page' , $data ) ); } Rob Allen ~ @akrabat
 
                render ( 'app::bitcoin-page' , $data ) ); } Rob Allen ~ @akrabat
 
                render ( 'app::bitcoin-page' , $data ) ); } Rob Allen ~ @akrabat
 
                $btcService ; } // … Rob Allen ~ @akrabat
 
                Expressive configuration A mushed-up associative array created from: • Invoked ConfigProvider classes from libs and modules • Config files in config/autoload/ Common top level keys: • dependencies • templates • twig • filters • validators • db • cache Rob Allen ~ @akrabat
 
                getTemplates (), ]; } Rob Allen ~ @akrabat
 
                getTemplates (), ]; } Rob Allen ~ @akrabat
 
                Handler\BitcoinPageFactory :: class , ], ]; } Rob Allen ~ @akrabat
 
                Handler\BitcoinPageFactory :: class , ], ]; } Rob Allen ~ @akrabat
 
                Handler\BitcoinPageFactory :: class , ], ]; } Rob Allen ~ @akrabat
 
                get ( BitcoinService :: class ) ); } } Rob Allen ~ @akrabat
 
                get ( BitcoinService :: class ) ); } } Rob Allen ~ @akrabat
 
                get ( BitcoinService :: class ) ); } } Rob Allen ~ @akrabat
 
                Templates Expressive's view layer Rob Allen ~ @akrabat
 
                render ( 'app::bitcoin-page' , $data ); • Templates are namespaced: namespace::template • Extension is resolved by the adapter: app::bitcoin-page =
app/bitcoin-page.html.twig Rob Allen ~ @akrabat
 
                Twig templates "Twig is a modern template engine for PHP" • Manual: https://twig.symfony.com • Script extension: .twig • Variables: {{ }} • Control statements: {% %} • Comments: {# #} Rob Allen ~ @akrabat
 
                Action template {% extends '@layout/default.html.twig' %} {% block title %} Bitcoin converter {% endblock %} {% block content %} < h1
Bitcoin converter < / h1
< p
One BTC: < / p
{% for price in prices %} {{ price.symbol }} {{ price.rate_float | number_format ( 2 , '.' , ',' ) }} < br
{% endfor %} {% endblock %} Rob Allen ~ @akrabat
 
                Print variables {% extends '@layout/default.html.twig' %} {% block title %} Bitcoin converter {% endblock %} {% block content %} < h1
Bitcoin converter < / h1
< p
One BTC: < / p
{% for price in prices %} {{ price.symbol }} {{ price.rate_float | number_format ( 2 , '.' , ',' ) }} < br
{% endfor %} {% endblock %} Rob Allen ~ @akrabat
 
                Control statements {% extends '@layout/default.html.twig' %} {% block title %} Bitcoin converter {% endblock %} {% block content %} < h1
Bitcoin converter < / h1
< p
One BTC: < / p
{% for price in prices %} {{ price.symbol }} {{ price.rate_float | number_format ( 2 , '.' , ',' ) }} < br
{% endfor %} {% endblock %} Rob Allen ~ @akrabat
 
                Template inheritance {% extends '@layout/default.html.twig' %} {% block title %} Bitcoin converter {% endblock %} {% block content %} < h1
Bitcoin converter < / h1
< p
One BTC: < / p
{% for price in prices %} {{ price.symbol }} {{ price.rate_float | number_format ( 2 , '.' , ',' ) }} < br
{% endfor %} {% endblock %} Rob Allen ~ @akrabat
 
                Template inheritance • For cohesive look and feel • includes default CSS, JS & structural HTML • Build a base skeleton • Define blocks for children to override • Each child chooses which template to inherit from Rob Allen ~ @akrabat
 
                Base skeleton src/App/templates/layout/default.html.twig: < !DOCTYPE html
< html
< head
"style.css" /
< title
{% block title %}{% endblock %} - Akrabat < / title
{% endblock %} < / head
< body
{% block content %}{% endblock %} < footer
{% block footer %} & copy; 2017 {% endblock %} < / footer
< / body
< / html
Rob Allen ~ @akrabat
 
                Base skeleton src/App/templates/layout/default.html.twig: < !DOCTYPE html
< html
< head
"style.css" /
< title
{% block title %}{% endblock %} - Akrabat < / title
{% endblock %} < / head
< body
{% block content %}{% endblock %} < footer
{% block footer %} & copy; 2017 {% endblock %} < / footer
< / body
< / html
Rob Allen ~ @akrabat
 
                Base skeleton src/App/templates/layout/default.html.twig: < !DOCTYPE html
< html
< head
"style.css" /
< title
{% block title %}{% endblock %} - Akrabat < / title
{% endblock %} < / head
< body
{% block content %}{% endblock %} < footer
{% block footer %} & copy; 2017 {% endblock %} < / footer
< / body
< / html
Rob Allen ~ @akrabat
 
                Base skeleton src/App/templates/layout/default.html.twig: < !DOCTYPE html
< html
< head
"style.css" /
< title
{% block title %}{% endblock %} - Akrabat < / title
{% endblock %} < / head
< body
{% block content %}{% endblock %} < footer
{% block footer %} & copy; 2017 {% endblock %} < / footer
< / body
< / html
Rob Allen ~ @akrabat
 
                . Rob Allen ~ @akrabat
 
                Adding components Rob Allen ~ @akrabat
 
                Add a form Rob Allen ~ @akrabat
 
                "/bitcoin"
< label
& pound; < / label
" {{ amount }} "
< button
Convert < / button
< / form
< p
& pound; {{ amount }} is {{ bitcoins | number_format ( 6 ) }} BTC Rob Allen ~ @akrabat
 
                "/bitcoin"
< label
& pound; < / label
" {{ amount }} "
< button
Convert < / button
< / form
< p
& pound; {{ amount }} is {{ bitcoins | number_format ( 6 ) }} BTC Rob Allen ~ @akrabat
 
                "/bitcoin"
< label
& pound; < / label
" {{ amount }} "
< button
Convert < / button
< / form
< p
& pound; {{ amount }} is {{ bitcoins | number_format ( 6 ) }} BTC Rob Allen ~ @akrabat
 
                "/bitcoin"
< label
& pound; < / label
" {{ amount }} "
< button
Convert < / button
< / form
< p
& pound; {{ amount }} is {{ bitcoins | number_format ( 6 ) }} BTC Rob Allen ~ @akrabat
 
                "/bitcoin"
< label
& pound; < / label
" {{ amount }} "
< button
Convert < / button
< / form
< p
& pound; {{ amount }} is {{ bitcoins | number_format ( 6 ) }} BTC < / p
Rob Allen ~ @akrabat
 
                Zend-InputFilter Rob Allen ~ @akrabat
 
                Integration via ConfigProvider Rob Allen ~ @akrabat
 
                0 ], ], ] ], ]); Rob Allen ~ @akrabat
 
                0 ], ], ] ], ]); Rob Allen ~ @akrabat
 
                0 ], ], ] ], ]); Rob Allen ~ @akrabat
 
                0 ], ], ] ], ]); Rob Allen ~ @akrabat
 
                0 ], ], ] ], ]); Rob Allen ~ @akrabat
 
                Validating request data 1. Retrieve data from Request object 2. Pass to InputFilter 3. Call isValid() 4. Retrieve sanitized, valid data using getValues() 5. Use getMessages() to find out what failed Rob Allen ~ @akrabat
 
                getMessages (); } Rob Allen ~ @akrabat
 
                getMessages (); } Rob Allen ~ @akrabat
 
                getMessages (); } Rob Allen ~ @akrabat
 
                getMessages (); } Rob Allen ~ @akrabat
 
                getMessages (); } Rob Allen ~ @akrabat
 
                Summary Rob Allen ~ @akrabat
 
                Resources • https://docs.zendframework.com/zend-expressive/ • https://github.com/zendframework/ • https://akrabat.com/category/zend-expressive/ • https://framework.zend.com/blog • Zend Expressive Essentials by Matt Setter Rob Allen ~ @akrabat
 
                Thank you! https://joind.in/talk/52ee1 Rob Allen ~ @akrabat
