Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)
-
Upload
james-titcumb -
Category
Technology
-
view
318 -
download
3
Transcript of Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)
@asgrim
Kicking off withZend Expressive
+Doctrine ORM
James TitcumbPHPNW16
James Titcumbwww.jamestitcumb.comwww.roave.comwww.phphants.co.ukwww.phpsouthcoast.co.uk
Who is this guy?
@asgrim
What is Zend Expressive?
@asgrim
Layers of an Expressive application
Expressive
Stratigility
Diactoros
PSR-7 Interface
DIRouter Template
Your Application
@asgrim
PSR-7HTTP Message Interfaces
@asgrim
HTTP Request
POST /phpnw16/foo HTTP/1.1
Host: conference.phpnw.org.uk
foo=bar&baz=bat
@asgrim
HTTP Request
POST /phpnw16/foo HTTP/1.1
Host: conference.phpnw.org.uk
foo=bar&baz=bat
@asgrim
HTTP Request
POST /phpnw16/foo HTTP/1.1
Host: conference.phpnw.org.uk
foo=bar&baz=bat
@asgrim
HTTP Request
POST /phpnw16/foo HTTP/1.1
Host: conference.phpnw.org.uk
foo=bar&baz=bat
@asgrim
HTTP Response
HTTP/1.1 200 OK
Content-Type: text/plain
This is the response body
@asgrim
HTTP Response
HTTP/1.1 200 OK
Content-Type: text/plain
This is the response body
@asgrim
HTTP Response
HTTP/1.1 200 OK
Content-Type: text/plain
This is the response body
@asgrim
HTTP Response
HTTP/1.1 200 OK
Content-Type: text/plain
This is the response body
@asgrim
Zend DiactorosPSR-7 implementation
@asgrim
Zend StratigilityCreating & dispatching middleware pipelines
@asgrim
So what is “middleware”?
@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;
}
@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;
}
@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;
}
@asgrim
Zend ExpressiveOne Ring to bring them all and in the darkness bind them.
@asgrim
Routing
@asgrim
container-interop
@asgrim
Optionally, templating
@asgrim
Piping and Routing
@asgrim
Pipe all the things!
$pipe = new \Zend\Stratigility\MiddlewarePipe();
$pipe->pipe($sessionMiddleware);
$pipe->pipe('/foo', $fooMiddleware);
$pipe->pipe($whateverMiddleware);
$pipe->pipe($dogeMiddleware);
@asgrim
Zend Framework 2/3What of them?
@asgrim
Middleware vs MVC
@asgrim
Getting startedwith Zend Expressive
@asgrim
https://github.com/asgrim/book-library
@asgrim
Expressive Skeleton
@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
@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
@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
@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
@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
@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
@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"}
@asgrim
Create the endpoints
@asgrim
Book entity
final class Book
{
/**
* @var string
*/
private $id;
/**
* @var bool
*/
private $inStock = true;
public function __construct()
{
$this->id = (string)Uuid::uuid4();
}
@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;
}
@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;
}
@asgrim
FindBookByUuidInterface
interface FindBookByUuidInterface
{
/**
* @param UuidInterface $slug
* @return Book
* @throws Exception\BookNotFound
*/
public function __invoke(UuidInterface $slug): Book;
}
@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()), ]);}
@asgrim
Adding some ORM
@asgrim
DoctrineORMModule
@asgrim
DoctrineORMModule + Expressive?
@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);
@asgrim
ConfigProvider
namespace Zend\Form;
class ConfigProvider
{
public function __invoke()
{
return [
'dependencies' => $this->getDependencyConfig(),
'view_helpers' => $this->getViewHelperConfig(),
];
}
@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,
],
@asgrim
config/autoload/zend-form.global.php
<?php
use Zend\Form\ConfigProvider;
return (new ConfigProvider())->__invoke();
@asgrim
But wait!
@asgrim
container-interop-doctrinesaves the day!!!
@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
$
@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,
],
],
];
@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
@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
@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
@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
@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
@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
@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
@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!
$
@asgrim
Insert some data
INSERT INTO book (id, name, in_stock) VALUES (
'1c06bec9-adae-47c2-b411-73b1db850e6f',
'The Great Escape',
true
);
@asgrim
/book/1c06bec9-adae-47c2-b411-.../check-out
{"info":"You have checked out The Great Escape"}
@asgrim
/book/1c06bec9-adae-47c2-b411-.../check-in
{"info":"You have checked in The Great Escape"}
@asgrim
Doing more with middleware
@asgrim
Authentication
@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
@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
@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
@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
@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
@asgrim
PSR7Session
@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
@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
@asgrim
$session = $request->getAttribute(
SessionMiddleware::SESSION_ATTRIBUTE
);
$session->set(
'counter',
$session->get('counter', 0) + 1
);
Session is stored in Request
@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
Any questions? :)
https://joind.in/talk/ff04fJames Titcumb