Top Banner
@asgrim Kicking off with Zend Expressive + Doctrine ORM James Titcumb PHPNW16
78

Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

Jan 26, 2017

Download

Technology

James Titcumb
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Kicking off withZend Expressive

+Doctrine ORM

James TitcumbPHPNW16

Page 3: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

What is Zend Expressive?

Page 4: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Layers of an Expressive application

Expressive

Stratigility

Diactoros

PSR-7 Interface

DIRouter Template

Your Application

Page 5: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

PSR-7HTTP Message Interfaces

Page 6: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

HTTP Request

POST /phpnw16/foo HTTP/1.1

Host: conference.phpnw.org.uk

foo=bar&baz=bat

Page 7: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

HTTP Request

POST /phpnw16/foo HTTP/1.1

Host: conference.phpnw.org.uk

foo=bar&baz=bat

Page 8: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

HTTP Request

POST /phpnw16/foo HTTP/1.1

Host: conference.phpnw.org.uk

foo=bar&baz=bat

Page 9: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

HTTP Request

POST /phpnw16/foo HTTP/1.1

Host: conference.phpnw.org.uk

foo=bar&baz=bat

Page 10: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

HTTP Response

HTTP/1.1 200 OK

Content-Type: text/plain

This is the response body

Page 11: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

HTTP Response

HTTP/1.1 200 OK

Content-Type: text/plain

This is the response body

Page 12: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

HTTP Response

HTTP/1.1 200 OK

Content-Type: text/plain

This is the response body

Page 13: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

HTTP Response

HTTP/1.1 200 OK

Content-Type: text/plain

This is the response body

Page 14: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Zend DiactorosPSR-7 implementation

Page 15: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Zend StratigilityCreating & dispatching middleware pipelines

Page 16: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

So what is “middleware”?

Page 17: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Middleware example

public function __invoke(

Request $request,

Response $response,

callable $next = null

) {

// ... some code before ...

$response = $next($request, $response);

// ... some code after ...

return $response;

}

Page 18: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Middleware example

public function __invoke(

Request $request,

Response $response,

callable $next = null

) {

// ... some code before ...

$response = $next($request, $response);

// ... some code after ...

return $response;

}

Page 19: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Middleware example

public function __invoke(

Request $request,

Response $response,

callable $next = null

) {

// ... some code before ...

$response = $next($request, $response);

// ... some code after ...

return $response;

}

Page 20: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Zend ExpressiveOne Ring to bring them all and in the darkness bind them.

Page 21: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Routing

Page 22: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

container-interop

Page 23: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Optionally, templating

Page 24: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Piping and Routing

Page 25: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Pipe all the things!

$pipe = new \Zend\Stratigility\MiddlewarePipe();

$pipe->pipe($sessionMiddleware);

$pipe->pipe('/foo', $fooMiddleware);

$pipe->pipe($whateverMiddleware);

$pipe->pipe($dogeMiddleware);

Page 26: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Zend Framework 2/3What of them?

Page 27: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Middleware vs MVC

Page 28: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Getting startedwith Zend Expressive

Page 29: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

https://github.com/asgrim/book-library

Page 30: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Expressive Skeleton

Page 31: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Expressive installer - start

$ composer create-project zendframework/zend-expressive-skeleton

book-library

Installing zendframework/zend-expressive-skeleton (1.0.3)

- Installing zendframework/zend-expressive-skeleton (1.0.3)

Downloading: 100%

Created project in book-library

> ExpressiveInstaller\OptionalPackages::install

Setup data and cache dir

Setting up optional packages

Page 32: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Expressive installer - minimal?

Minimal skeleton? (no default middleware, templates, or assets;

configuration only)

[y] Yes (minimal)

[n] No (full; recommended)

Make your selection (No): n

Page 33: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@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): 2

Page 34: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Expressive installer - container?

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): 3

Page 35: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@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): n

Page 36: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@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

Page 37: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@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": "zend-expressive.readthedocs.org"}

Page 38: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Create the endpoints

Page 39: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Book entity

final class Book

{

/**

* @var string

*/

private $id;

/**

* @var bool

*/

private $inStock = true;

public function __construct()

{

$this->id = (string)Uuid::uuid4();

}

Page 40: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Book entity

final class Book

{

/**

* @return void

* @throws \App\Entity\Exception\BookNotAvailable

*/

public function checkOut()

{

if (!$this->inStock) {

throw Exception\BookNotAvailable::fromBook($this);

}

$this->inStock = false;

}

Page 41: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Book entity

final class Book

{

/**

* @return void

* @throws \App\Entity\Exception\BookAlreadyStocked

*/

public function checkIn()

{

if ($this->inStock) {

throw Exception\BookAlreadyStocked::fromBook($this);

}

$this->inStock = true;

}

Page 42: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

FindBookByUuidInterface

interface FindBookByUuidInterface

{

/**

* @param UuidInterface $slug

* @return Book

* @throws Exception\BookNotFound

*/

public function __invoke(UuidInterface $slug): Book;

}

Page 43: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

CheckOutAction

public function __invoke( ServerRequestInterface $request, ResponseInterface $response, callable $next = null) : 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()), ]);}

Page 44: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Adding some ORM

Page 45: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

DoctrineORMModule

Page 46: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

DoctrineORMModule + Expressive?

Page 47: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

config/autoload/doctrine-modules.global.php

$vendorPath = __DIR__ . '/../../vendor';

$doctrineModuleConfig = require_once

$vendorPath . '/doctrine/doctrine-module/config/module.config.php';

$doctrineModuleConfig['dependencies'] = $doctrineModuleConfig['service_manager'];

unset($doctrineModuleConfig['service_manager']);

$ormModuleConfig = require_once

$vendorPath . '/doctrine/doctrine-orm-module/config/module.config.php';

$ormModuleConfig['dependencies'] = $ormModuleConfig['service_manager'];

unset($ormModuleConfig['service_manager']);

return ArrayUtils::merge($doctrineModuleConfig, $ormModuleConfig);

Page 48: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

ConfigProvider

namespace Zend\Form;

class ConfigProvider

{

public function __invoke()

{

return [

'dependencies' => $this->getDependencyConfig(),

'view_helpers' => $this->getViewHelperConfig(),

];

}

Page 49: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

ConfigProvider#getDependencyConfig()

public function getDependencyConfig()

{

return [

'abstract_factories' => [

FormAbstractServiceFactory::class,

],

'aliases' => [

'Zend\Form\Annotation\FormAnnotationBuilder' => 'FormAnnotationBuilder',

Annotation\AnnotationBuilder::class => 'FormAnnotationBuilder',

FormElementManager::class => 'FormElementManager',

],

'factories' => [

'FormAnnotationBuilder' => Annotation\AnnotationBuilderFactory::class,

'FormElementManager' => FormElementManagerFactory::class,

],

Page 50: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

config/autoload/zend-form.global.php

<?php

use Zend\Form\ConfigProvider;

return (new ConfigProvider())->__invoke();

Page 51: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

But wait!

Page 52: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

container-interop-doctrinesaves the day!!!

Page 53: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Installation

$ composer require dasprid/container-interop-doctrine

Using version ^0.2.2 for dasprid/container-interop-doctrine

./composer.json has been updated

Loading composer repositories with package information

Updating dependencies (including require-dev)

- Installing dasprid/container-interop-doctrine (0.2.2)

Loading from cache

Writing lock file

Generating autoload files

$

Page 54: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

config/autoload/doctrine.global.php

<?php

declare(strict_types = 1);

use Doctrine\ORM\EntityManagerInterface;

use ContainerInteropDoctrine\EntityManagerFactory;

return [

'dependencies' => [

'factories' => [

EntityManagerInterface::class => EntityManagerFactory::class,

],

],

];

Page 55: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

'doctrine' => [

'connection' => [

'orm_default' => [

'driver_class' => PDOPgSql\Driver::class,

'params' => [

'url' => 'postgres://user:pass@localhost/book_library',

],

],

],

'driver' => [

'orm_default' => [

'class' => MappingDriverChain::class,

'drivers' => [

// ... and so on ...

config/autoload/doctrine.global.php

Page 56: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

'doctrine' => [

'connection' => [

'orm_default' => [

'driver_class' => PDOPgSql\Driver::class,

'params' => [

'url' => 'postgres://user:pass@localhost/book_library',

],

],

],

'driver' => [

'orm_default' => [

'class' => MappingDriverChain::class,

'drivers' => [

// ... and so on ...

config/autoload/doctrine.global.php

Page 57: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

<?php

declare(strict_types = 1);

use Doctrine\ORM\EntityManagerInterface;

use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;

use Symfony\Component\Console\Helper\HelperSet;

$container = require __DIR__ . '/container.php';

return new HelperSet([

'em' => new EntityManagerHelper(

$container->get(EntityManagerInterface::class)

),

]);

config/cli-config.php

Page 58: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

/**

* @ORM\Entity

* @ORM\Table(name="book")

*/

final class Book

{

/**

* @ORM\Id

* @ORM\Column(name="id", type="guid")

* @ORM\GeneratedValue(strategy="NONE")

* @var string

*/

private $id;

src/App/Entity/Book.php

Page 59: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

/**

* @ORM\Entity

* @ORM\Table(name="book")

*/

final class Book

{

/**

* @ORM\Id

* @ORM\Column(name="id", type="guid")

* @ORM\GeneratedValue(strategy="NONE")

* @var string

*/

private $id;

src/App/Entity/Book.php

Page 60: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

public function __invoke(UuidInterface $id): Book

{

/** @var Book|null $book */

$book = $this->repository->find((string)$id);

if (null === $book) {

throw Exception\BookNotFound::fromUuid($id);

}

return $book;

}

src/App/Service/Book/DoctrineFindBookByUuid.php

Page 61: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

try {

$this->entityManager->transactional(function () use ($book) {

$book->checkOut();

});

} catch (BookNotAvailable $bookNotAvailable) {

return new JsonResponse(['info' => $bookNotAvailable->getMessage()], 423);

}

src/App/Action/CheckOutAction.php

Page 62: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@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!

$

Page 63: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Insert some data

INSERT INTO book (id, name, in_stock) VALUES (

'1c06bec9-adae-47c2-b411-73b1db850e6f',

'The Great Escape',

true

);

Page 64: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

/book/1c06bec9-adae-47c2-b411-.../check-out

{"info":"You have checked out The Great Escape"}

Page 65: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

/book/1c06bec9-adae-47c2-b411-.../check-in

{"info":"You have checked in The Great Escape"}

Page 66: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Doing more with middleware

Page 67: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

Authentication

Page 68: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

public function __invoke(

Request $request, Response $response, callable $next = null

) : Response {

$queryParams = $request->getQueryParams();

if (!array_key_exists('authenticated', $queryParams)

|| $queryParams['authenticated'] !== '1') {

return new JsonResponse(

['error' => 'You are not authenticated.'],

403

);

}

return $next($request, $response);

}

Create the middleware

Page 69: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

public function __invoke(

Request $request, Response $response, callable $next = null

) : Response {

$queryParams = $request->getQueryParams();

if (!array_key_exists('authenticated', $queryParams)

|| $queryParams['authenticated'] !== '1') {

return new JsonResponse(

['error' => 'You are not authenticated.'],

403

);

}

return $next($request, $response);

}

Create the middleware

Page 70: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

public function __invoke(

Request $request, Response $response, callable $next = null

) : Response {

$queryParams = $request->getQueryParams();

if (!array_key_exists('authenticated', $queryParams)

|| $queryParams['authenticated'] !== '1') {

return new JsonResponse(

['error' => 'You are not authenticated.'],

403

);

}

return $next($request, $response);

}

Create the middleware

Page 71: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

public function __invoke(

Request $request, Response $response, callable $next = null

) : Response {

$queryParams = $request->getQueryParams();

if (!array_key_exists('authenticated', $queryParams)

|| $queryParams['authenticated'] !== '1') {

return new JsonResponse(

['error' => 'You are not authenticated.'],

403

);

}

return $next($request, $response);

}

Create the middleware

Page 72: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

public function __invoke(

Request $request, Response $response, callable $next = null

) : Response {

$queryParams = $request->getQueryParams();

if (!array_key_exists('authenticated', $queryParams)

|| $queryParams['authenticated'] !== '1') {

return new JsonResponse(

['error' => 'You are not authenticated.'],

403

);

}

return $next($request, $response);

}

Create the middleware

Page 73: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

PSR7Session

Page 74: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

public function __invoke(ContainerInterface $container, $_, array $_ = null)

{

$symmetricKey = 'super-secure-key-you-should-not-store-this-key-in-git';

$expirationTime = 1200; // 20 minutes

return new SessionMiddleware(

new Signer\Hmac\Sha256(),

$symmetricKey,

$symmetricKey,

SetCookie::create(SessionMiddleware::DEFAULT_COOKIE)

->withSecure(false) // false on purpose, unless you have https locally

->withHttpOnly(true)

->withPath('/'),

new Parser(),

$expirationTime,

new SystemCurrentTime()

);

}

Factory the middleware

Page 75: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

'routing' => [

'middleware' => [

ApplicationFactory::ROUTING_MIDDLEWARE,

Helper\UrlHelperMiddleware::class,

PSR7Session\Http\SessionMiddleware::class,

App\Middleware\AuthenticationMiddleware::class,

ApplicationFactory::DISPATCH_MIDDLEWARE,

],

'priority' => 1,

],

Add middleware to pipe

Page 76: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

$session = $request->getAttribute(

SessionMiddleware::SESSION_ATTRIBUTE

);

$session->set(

'counter',

$session->get('counter', 0) + 1

);

Session is stored in Request

Page 77: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

@asgrim

To summarise...

● PSR-7 is important● Diactoros is just a PSR-7 implementation● Stratigility is a middleware pipeline● Expressive is a glue for container, router (and templating)● DoctrineModule can be used (with some fiddling)● container-interop-doctrine makes Doctrine work easily● Middleware gives you good controls

Page 78: Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)

Any questions? :)

https://joind.in/talk/ff04fJames Titcumb