var express = require('express')
var app = express()
app.get('/', function (req, res) {
res.send('Hello World!')
})
app.listen(3000)
Dependency Injection
Value Objects
Dependency Injection
Value Objects
PSR-7
Dependency Injection
Value Objects
PSR-7
Middleware
Zend Expressive
cd /var/www/html/summercamp/phpmiddleware
git checkout 01-expressive-skeleton
Tips & Hints:
docs/readme.html
using Postman
postman --disable-gpu
cd /var/www/html/summercamp/
composer create-project zendframework/zend-expressive-skeleton phpmiddleware
Let's open PHPStorm at:
/var/www/html/summercamp/phpmiddleware
├── bin
├── config
├── data
├── docs
├── public
│ └── index.php
├── src
├── test
└── vendor
Apache's configuration
/etc/apache2/sites-available/phpmiddleware.conf
Document root
/var/www/html/summercamp/phpmiddleware/public
chdir(dirname(__DIR__));
require 'vendor/autoload.php';
call_user_func(function () {
/** @var \Interop\Container\ContainerInterface $container */
$container = require 'config/container.php';
/** @var \Zend\Expressive\Application $app */
$app = $container->get(\Zend\Expressive\Application::class);
require 'config/pipeline.php';
require 'config/routes.php';
$app->run();
});
$aggregator = new ConfigAggregator([
App\ConfigProvider::class,
new PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
new PhpFileProvider('config/development.config.php')
]);
return $aggregator->getMergedConfig();
$app->pipe(ErrorHandler::class);
$app->pipeRoutingMiddleware();
$app->pipeDispatchMiddleware();
$app->pipe(NotFoundHandler::class);
$app->get('/', App\Action\HomePageAction::class, 'home');
$app->get('/api/ping', App\Action\PingAction::class, 'api.ping');
$app->get('/', App\Action\HomePageAction::class, 'home');
$app->get('/api/ping', App\Action\PingAction::class, 'api.ping');
$app->get('/', App\Action\HomePageAction::class, 'home');
$app->get('/api/ping', App\Action\PingAction::class, 'api.ping');
namespace App\Action;
class PingAction implements ServerMiddlewareInterface
{
public function process(
ServerRequestInterface $request,
DelegateInterface $delegate
) {
return new JsonResponse(['ack' => time()]);
}
}
namespace Psr\Http\Message;
interface MessageInterface
{
public function getProtocolVersion();
public function withProtocolVersion($version);
public function getHeaders();
public function hasHeader($name);
public function getHeader($name);
public function getHeaderLine($name);
public function withHeader($name, $value);
public function withAddedHeader($name, $value);
public function withoutHeader($name);
public function getBody();
public function withBody(StreamInterface $body);
}
namespace Psr\Http\Message;
interface RequestInterface extends MessageInterface
{
public function getRequestTarget();
public function withRequestTarget($requestTarget);
public function getMethod();
public function withMethod($method);
public function getUri();
public function withUri(UriInterface $uri, $preserveHost = false);
}
namespace Psr\Http\Message;
interface ServerRequestInterface extends RequestInterface
{
public function getServerParams();
public function getCookieParams();
public function withCookieParams(array $cookies);
public function getQueryParams();
public function withQueryParams(array $query);
public function getUploadedFiles();
public function withUploadedFiles(array $uploadedFiles);
public function getParsedBody();
public function withParsedBody($data);
public function getAttributes();
public function getAttribute($name, $default = null);
public function withAttribute($name, $value);
public function withoutAttribute($name);
}
namespace Psr\Http\Message;
interface ResponseInterface extends MessageInterface
{
public function getStatusCode();
public function withStatus($code, $reasonPhrase = '');
public function getReasonPhrase();
}
$response->setStatusCode(418);
$response = $response->withStatusCode(418);
git checkout 03-welcome
get to branch 03-welcome
Create an hello route (/hello)
Display output {"hello":"NAME"}
NAME is either the value of the "name" query string parameter, or the string "random phper" if no name parameter was provided
docs/readme.html
config/routes.php
$app->get('/', \App\Action\IndexAction::class, 'index');
$app->get('/hello', \App\Action\HelloAction::class, 'hello');
src/App/Action/HelloAction.php
class HelloAction implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
DelegateInterface $delegate
): ResponseInterface
{
$query = $request->getQueryParams();
$name = $query['name'] ?? 'random phper';
return new JsonResponse([
'hello' => $name
]);
}
}
git checkout 04-hello-name
git checkout 06-chocolates-route
$app->get('/', \App\Action\IndexAction::class, 'index');
$app->get('/hello', \App\Action\HelloAction::class, 'hello');
$app->get('/chocolates', \App\Action\ChocolatesAction::class, 'chocolates');
final class ChocolatesAction implements MiddlewareInterface
{
/**
* @var ChocolatesServiceInterface
*/
private $chocolates;
public function __construct(ChocolatesServiceInterface $chocolates)
{
$this->chocolates = $chocolates;
}
public function process(
ServerRequestInterface $request,
DelegateInterface $delegate
): ResponseInterface
{
return new JsonResponse($this->chocolates->getAll());
}
}
$aggregator = new ConfigAggregator([
App\ConfigProvider::class,
new PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
new PhpFileProvider('config/development.config.php')
]);
return $aggregator->getMergedConfig();
public function getDependencies()
{
return [
'factories' => [
// ACTIONS
ChocolatesAction::class => ChocolatesActionFactory::class,
// SERVICES
ChocolatesServiceInterface::class => ChocolatesServiceFactory::class,
// REPOSITORIES
Chocolates::class => SqlChocolatesFactory::class,
],
];
}
final class ChocolatesActionFactory
{
public function __invoke(ContainerInterface $container): ChocolatesAction
{
return new ChocolatesAction(
$container->get(ChocolatesServiceInterface::class)
);
}
}
interface ContainerInterface
{
/**
* @param string $id Identifier of the entry to look for.
*
* @throws NotFoundExceptionInterface No entry was found for the identifier.
* @throws ContainerExceptionInterface Error while retrieving the entry.
*
* @return mixed Entry.
*/
public function get($id);
/**
* @param string $id Identifier of the entry to look for.
*
* @return bool
*/
public function has($id);
}
get to branch 06-chocolates-route
Create a chocolate-details (/chocolate/ID) route where ID is a proper ID of a chocolate wrapper in our domain
Interact with src/App/Domain/Service/ChocolatesService.php
Create a users (/users) route
docs/readme.html
final class ChocolateDetailsAction implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
DelegateInterface $delegate
): ResponseInterface
{
$chocolateId = ChocolateId::fromString($request->getAttribute('id'));
$chocolate = $this->chocolatesService->getChocolate($chocolateId);
return new JsonResponse($chocolate);
}
}
git checkout 07-chocolate-details-route
git checkout 08-users
class ...Action implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
DelegateInterface $delegate
): ResponseInterface
{
...
}
}
interface MiddlewareInterface
{
/**
* @param ServerRequestInterface $request
* @param DelegateInterface $delegate
*
* @return ResponseInterface
*/
public function process(
ServerRequestInterface $request,
DelegateInterface $delegate
);
}
git checkout 10-access-log
$app->pipe(ErrorHandler::class);
$app->pipe(new \Middlewares\ClientIp());
$app->pipe(\Middlewares\AccessLog::class);
$app->pipeRoutingMiddleware();
$app->pipeDispatchMiddleware();
$app->pipe(NotFoundHandler::class);
return [
'dependencies' => [
'factories' => [
\Middlewares\AccessLog::class =>
\App\Container\Middleware\AccessLogFactory::class,
],
],
];
final class AccessLogFactory
{
public function __invoke(ContainerInterface $container)
{
$logger = new Logger('access');
$filePath = $container->get('config')['access_log']['path'];
$logger->pushHandler(new StreamHandler($filePath));
$accessLog = new AccessLog($logger);
$format = '%v %h %u %t "%r" %>s %b "%{Referer}i" -> %U';
$accessLog->format($format);
$accessLog->ipAttribute('client-ip');
return $accessLog;
}
}
Get to branch 10-access-log
Create a caching middleware
For all http GET requests, make sure that a cached result is returned, when available
Bonus: have cache expire after a certain time period; have cache file path configuration within config files
docs/readme.html
git checkout 11-response-cache
git checkout 15-delete-chocolate
$app->post('/submit', \App\Action\SubmitChocolateAction::class, 'submit');
$app->post(
'/approve/{id}',
\App\Action\ApproveChocolateAction::class,
'approve'
);
$app->post(
'/delete/{id}',
\App\Action\DeleteChocolateAction::class,
'delete'
);
App\Action\SubmitChocolateAction.php
App\Action\ApproveChocolateAction.php
App\Action\DeleteChocolateAction.php
git checkout 16.1-basic-http-authentication
$app->post(
'/submit',
[
\Middlewares\HttpAuthentication::class,
\App\Action\SubmitChocolateAction::class,
],
'submit'
);
$app->post(
'/approve/{id}',
[
\Middlewares\HttpAuthentication::class,
\App\Action\ApproveChocolateAction::class,
],
'approve'
);
[...]
final class BasicHttpAuthenticationFactory
{
public function __invoke(ContainerInterface $container): HttpAuthentication
{
/** @var UsersServiceInterface $users */
$users = $container->get(UsersServiceInterface::class);
$credentials = array_reduce(
$users->getAll(),
function (array $carry, User $user) {
$carry[$user->username()] = $user->password();
return $carry;
},
[]
);
return (new BasicAuthentication($credentials))
->attribute(HttpAuthentication::class);
}
}
git checkout 17.3-jwt-authentication
$app->post(
'/submit',
[
\Middlewares\HttpAuthentication::class,
\App\Action\SubmitChocolateAction::class,
],
'submit'
);
$app->post(
'/approve/{id}',
[
\Middlewares\HttpAuthentication::class,
\App\Action\ApproveChocolateAction::class,
],
'approve'
);
$app->post('/token', \App\Action\TokenAction::class, 'token');
Get to branch 17.1-jwt-authentication
Implement JWT Authentication
Get to branch 16.1-basic-http-authentication
Allow for authenticated users only to submit new wrappers
Allow for administrators only to approve/delete wrappers
git checkout 17.3-jwt-authentication
git checkout 18-authorization
return [
'dependencies' => [
'factories' => [
\Middlewares\HttpAuthentication::class =>
\App\Container\Middleware\JwtAuthenticationFactory::class,
]
]
];
$app->post(
'/submit',
[
\Middlewares\HttpAuthentication::class,
\App\Action\SubmitChocolateAction::class,
],
'submit'
);
$app->post(
'/approve/{id}',
[
\Middlewares\HttpAuthentication::class,
\App\Middleware\Authorization::class,
\App\Action\ApproveChocolateAction::class,
],
'approve'
);
public function process(
ServerRequestInterface $request,
DelegateInterface $delegate
): ResponseInterface
{
$username = $request->getAttribute(JwtAuthentication::class)->data->username;
$user = $this->usersService->getByUsername($username);
if (! $user->isAdministrator()) {
return new EmptyResponse(403);
}
return $delegate->process($request);
}
Get to branch 19-phpunit or 20-behat
let's write a test for the Authorization Middleware of the last exercise
docs/readme.html
git checkout 19.1-phpunit
git checkout 20.1-behat
$app->add(new AccessLog($logger));
$app->add(new ClientIp());
$app->add(new BasicAuthentication($users));
$dispatcher = new Dispatcher([
new BasicAuthentication($users),
new ClientIp(),
new AccessLog($logger)
]);
Leave your feedback at https://joind.in/talk/668cc
PSR-7 By Example by Matthew Weier O'Phinney
On HTTP, Middleware, and PSR-7 by Matthew Weier O'Phinney
Zend Expressive documentation
Proposed Middleware Interface PSR-15
Why Care About PHP Middleware? by Phil Sturgeon
All about middleware by Anthony Ferrara
Telegraph: A Lambda-Style PSR-7 Middleware Dispatcher by Paul Jones
Middleman's Github code repository
Beyond PSR-7: The magical middleware tour
This workshop's Github code repository
Feng Shui at pixabay.com
SOA Middleware by David McCaldin
Board by ericfleming8
Chocolate at pixabay.com
Traveling at pixabay.com
Architecture at pixabay.com
Orioles Fan by Keith Allison
Programmer at pixabay.com
Sleepy Dog at pixabay.com
Small Board at pixabay.com
Lake Bump at pixabay.com
Networking Equipment at pixabay.com
Chefs at pixabay.com
Parts on Table at pixabay.com
PSR-7 Diagram by Matthew Weier O'Phinney
Diamond by EWAR
Chocolate Wrappers by A. Olin
Cutting onion by Lali Masriera
Middleware Flow by Matthew Weier O'Phinney
Weird Bicicle by Thomas Guest
Dinosaur at pixabay.com
Deer at Self Service from pixabay.com
JWT Flow by Mikey Stecky-Efantis
Car Crash at pixabay.com
Cow with a Prize at pixabay.com
Closing Curtains at pixabay.com