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