PSR-7
and
Middleware

 


Wed 11 Jan 2017
PHP Frameworks and CMSes
              
What is PSR?

Credits: https://xkcd.com/927
A short list...
  • PSR-0: Autoloading Standard
  • PSR-1: Basic Coding Standard
  • PSR-2: Coding Style Guide
  • PSR-4: Autoloading Standard (update to PSR-0)
  • PSR-7: HTTP Message Interface
PSR-7: HTTP Message Interface

<?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,
        ]);
    }
}
What do frameworks abstract?

  • Form post
  • Access $_SERVER variables
  • Extract request uri for routing purposes
  • Handle file uploads
  • Input and output streams
  • Returning of response

Credits: http://code.tutsplus.com/tutorials/http-the-protocol-every-web-developer-must-know-part-1--net-31177

Credits: http://wiki.hashphp.org/HttpPrimer
Every HTTP request message has a specific form:
POST /path HTTP/1.1
Host: example.com

foo=bar&baz=bat
HTTP response messages have a similar structure:
HTTP/1.1 200 OK
Content-Type: text/plain

This is the response body
MessageInterface
$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');
RequestInterface
$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);
ResponseInterface
$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);
ServerRequestInterface
$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
StreamInterface
interface StreamInterface
{
    public function __toString(); // important
    public function close();
    public function detach();
    public function getSize();
    public function tell();
    public function eof();
    public function isSeekable();
    public function seek($offset, $whence = SEEK_SET);
    public function rewind();
    public function isWritable();
    public function write($string);
    public function isReadable();
    public function read($length);
    public function getContents();
    public function getMetadata($key = null);
}
File Uploads with $_FILES
[
    'files' => [
        0 => [
            'name' => 'file0.txt',
            'type' => 'text/plain',
            /* etc. */
        ],
        1 => [
            'name' => 'file1.html',
            'type' => 'text/html',
            /* etc. */
        ],
    ],
];
File Uploads with $_FILES
[ // This is what $_FILES in PHP gives us :P
    'files' => [
        'name' => [
            0 => 'file0.txt',
            1 => 'file1.html',
        ],
        'type' => [
            0 => 'text/plain',
            1 => 'text/html',
        ],
        /* etc. */
    ],
];
UploadedFileInterface
$uploadedFiles = $request->getUploadedFiles(); // array of UploadedFileInterface

foreach ($uploadedFiles as $file) {
    echo $file->getClientFilename() . ', ' . $file->getClientMediaType();
    $size = $file->getSize();
    $contents = $file->getStream(); // StreamInterface
    $file->moveTo($newFilePath);
}

// Printout:
//   file0.txt, text/plain
//   file1.txt, text/html
What's in an URI?
                    authority               path
        ┌───────────────┴───────────────┐┌───┴────┐
 http://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
 └─┬┘   └───────┬───────┘ └────┬────┘ └┬┘           └─────────┬─────────┘ └──┬──┘
scheme  user information     host     port                  query         fragment
Credits: https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Examples
Trying to compose a URI via $_SERVER
array( // not all variables shown here
  'HTTP_HOST' => 'localhost',
  'HTTP_ACCEPT' => 'text/html,application/xhtml+xml',
  'SERVER_NAME' => 'localhost',
  'SERVER_ADDR' => '127.0.0.1',
  'SERVER_PORT' => '80',
  'REMOTE_ADDR' => '127.0.0.1',
  'REQUEST_SCHEME' => 'http',
  'REMOTE_PORT' => '53746',
  'SERVER_PROTOCOL' => 'HTTP/1.1',
  'REQUEST_METHOD' => 'GET',
  'QUERY_STRING' => 'key1=value1',
  'REQUEST_URI' => '/test.php?key1=value1',
  'SCRIPT_NAME' => '/test.php',
  'PHP_SELF' => '/test.php',
  'REQUEST_TIME' => 1470751673,
)
UriInterface
$uri = $request->getUri();
$scheme   = $uri->getScheme();
$userInfo = $uri->getUserInfo();
$host     = $uri->getHost();
$port     = $uri->getPort();
$path     = $uri->getPath();
$query    = $uri->getQuery();
$fragment = $uri->getFragment();
$newUri   = $uri->withHost('example.com');
Middleware
What is Middleware?

Credits: http://2015.phpday.it/talk/pushing-boundaries-zend-framework-3-and-the-future/
In code...
<?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);
    }
}

Credits: http://2015.phpday.it/talk/pushing-boundaries-zend-framework-3-and-the-future/
Zend Expressive

PSR-7 Middleware in Minutes
Try it today!