Episode I
PHP-FIG
Turmoil has engulfed the PHP Republic with chaotic factions fighting against each other.
An alliance was formed by devs from different frameworks in 2009.
Their name - PHP Framework Interoperability Group (PHP-FIG).
Their weapon - PHP Standards Recommendations (PSR).
<?php // test.php?city=SG
if (isset($_POST['submit'])) {
echo 'You typed: ' . $_POST['myText'];
}
?>
City: <?php echo ($_GET['city'] ?? 'None'); ?><br />
<form method="POST">
Text: <input type="text" name="myText"><br />
<input type="submit" />
</form>
<?php // localhost/city/SG
class TestController extends AbstractActionController
{
public function indexAction()
{
$request = $this->getRequest();
$usertext = $request->isPost() ? $request->getPost()['myText'] : '';
return new ViewModel([
'city' => $this->params('city', 'None'),
'form' => new TestForm(),
'usertext' => $usertext,
]);
}
}
POST /path HTTP/1.1
Host: example.com
foo=bar&baz=bat
HTTP/1.1 200 OK
Content-Type: text/plain
This is the response body
$headers = $message->getHeaders(); // [<headerName> => <array>]
$headerValues = $message->getHeader('Cookie'); // array, may be more than 1
$headerLine = $message->getHeaderLine('Accept'); // string
$body = $message->getBody(); // StreamInterface
$newMessage = $message->withHeader('Content-Type', 'application/json');
$newerMessage = $newMessage->withBody($body->getContents() . 'appended content');
$method = $request->getMethod(); // GET, POST, etc.
$uri = $request->getUri(); // UriInterface
$body = new Stream();
$body->write('{"id":1}');
$newRequest = $request
->withMethod('GET')
->withUri('http://example.com/api/v1/users')
->withHeader('Accept', 'application/json')
->withBody($body);
$status = $response->getStatusCode(); // 200, 404, 500
$reason = $response->getReasonPhrase(); // OK, Not Found, Server Error
$data = json_decode((string) $response->getBody(), true);
$data['timestamp'] = gmdate('c');
$newResponse = $response->withBody(json_encode($data));
$newerResponse = $newResponse->withStatus(200);
$queryParams = $request->getQueryParams();
$parsedBody = $request->getParsedBody(); // deserialized body params
$uploadedFiles = $request->getUploadedFiles(); // array of UploadedFileInterface
// Get `id` passed from route, eg. localhost/user/100.
// Application must parse route and inject into request attributes
$id = $request->getAttribute('id'); // 100
<?php
function middleware(
ServerRequestInterface $request,
ResponseInterface $response,
callable $next
);
<?php // Before: Framework-specific methods, parent classes, naming convention
class TestController extends AbstractActionController
{
public function indexAction()
{
$request = $this->getRequest();
$usertext = $request->isPost() ? $request->getPost()['myText'] : '';
return new ViewModel([ // tied to framework
'usertext' => $usertext,
]);
}
}
<?php // After: Framework-agnostic Middleware
class TestAction
{
private $renderer;
public function __construct(TemplateRendererInterface $renderer)
{
$this->renderer = $renderer; // dependency injection
}
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
callable $next
) {
$usertext = $request->getParsedBody['myText'] ?? '';
$response->getBody()->write( // renderer not tied to framework
$this->renderer->render('app::test', ['usertext' => $usertext])
);
return $next($request, $response);
}
}
<?php
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
callable $next
) {
// do something
return $next($request, $response);
}
<?php
interface RequestHandlerInterface
{
public function handle(ServerRequestInterface $request): ResponseInterface;
}
<?php
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
callable $next
) {
// do something
return $next($request, $response);
}
<?php
interface MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface;
}
fn(request, response, next): response
Single Passfn(request, next): response
fn(request, response, next): response // Problems with Double Pass
fn(request, next): response
class QueueRequestHandler implements RequestHandlerInterface {
public function __construct(RequestHandlerInterface $fallbackHandler) {
$this->fallbackHandler = $fallbackHandler;
}
public function add(MiddlewareInterface $middleware) {
$this->middleware[] = $middleware;
}
public function handle(ServerRequestInterface $request): ResponseInterface {
// Last middleware in the queue has called on the request handler.
if (0 === count($this->middleware)) {
return $this->fallbackHandler->handle($request);
}
$middleware = array_shift($this->middleware);
return $middleware->process($request, $this);
}
}
// An application bootstrap may look like this:
// Fallback handler:
$fallbackHandler = new NotFoundHandler();
// Create request handler instance:
$app = new QueueRequestHandler($fallbackHandler);
// Add one or more middleware:
$app->add(new AuthorizationMiddleware());
$app->add(new RoutingMiddleware());
// execute it:
$response = $app->handle(ServerRequestFactory::fromGlobals());
https://www.php-fig.org/psr/psr-15/meta/#queue-based-request-handler
class AuthorizationMiddleware implements MiddlewareInterface {
public function __construct(AuthorizationMap $authorizationMap) {
$this->authorizationMap = $authorizationMap;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
if (! $authorizationMap->needsAuthorization($request)) {
return $handler->handle($request);
}
if (! $authorizationMap->isAuthorized($request)) {
return $authorizationMap->prepareUnauthorizedResponse();
}
$response = $handler->handle($request);
return $authorizationMap->signResponse($response, $request);
}
}
class RoutingMiddleware implements MiddlewareInterface
{
public function __construct(Router $router)
{
$this->router = $router;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$result = $this->router->match($request);
if ($result->isSuccess()) {
return $result->getHandler()->handle($request);
}
return $handler->handle($request);
}
}