Hi!

I'm Marco!

Ocramius

The Domain

The year is 2004.

A new client approaches us with a software project to be built.

Our Services

The best you can find, according to 2004 standards!

  • Top-grade Weaponry
  • Exclusively to Coyotes
  • Express Delivery
  • No Refunds

Requirements

  • E-Commerce Platform
  • Coyotes can purchase

Plain PHP

We did it anyway!

WARNING

The following content may contain elements that are not suitable for some audiences.

Accordingly, viewer discretion is advised


<?php

include 'functions.php.inc';

// register_globals polyfill
foreach ($_REQUEST as $var => $value) {
    $$var = $value;
}

if ($type == 'purchase' && $userType == 'coyote') {
    $products = mysql_query(
        'SELECT * FROM product WHERE id = ' . $productId
    );

    if ($product = mysql_fetch_assoc($products)) {
        if ($purchase && strlen($creditCard) == 16) {
            $success = true;
            mysql_query(
                'INSERT INTO purchase (product_id, credit_card, user)
                VALUES (' . $productId . ', "' . $creditCard . '", "' . $user . '")'
            );
            mail(
                $userEmail,
                'Purchase completed',
                'Thank you for your purchase!'
            );
        }
    }
}

include 'header.php';

if (isset($success)) {
    echo "<h3>Thank you for your purchase, $user!</h3>";
}

include 'product-details.php';
include 'footer.php';

IT COULD WORK

Security Issues

Let's get some
frameworks
involved

At least these people know what they're doing...

(sometimes)

ZF 1.x

Migrate
all the things!

Things are separated!

MVC

(-ish)

Controller

(code is made up)

(I CBA to look up old ZF1 code)


<?php

class PurchaseController extends Zend_Controller_Action
{
    public function purchaseAction()
    {

if (! $this->isCoyote()) {
    return $this->notAllowed();
}

$form = new PurchaseForm();

if (! $form->isValid($this->getPost())) {
    return $this->render(['form' => $form]);
}


$data = $form->getData();

$product = $this->loadProduct($data['product']);
$product->purchase(); // magic

$this->mail($this->user(), 'Purchase completed');

return $this->render(['success' => true]);

PROGRESS!

No more basic
Security Issues

  • Rendering in views, assuming escaping, fixes XSS
  • Proper, unavoidable form validation
  • Form validation with CSRF protection
  • SQL abstraction removed SQL injections
  • Mailer removed SMTP header injection

Testability

(in theory)

Anybody tested a ZF1 controller?

Coupling
with the
Framework

Everything extends from it

Everything consumes its types

Everything talks to a singleton kernel

All plugins and helpers have a 0-argument-constructor

Zend_Loader used as a lazy-loading mechanism

ZF2

Rewrite

It's gonna be a rewrite

You saw it coming!

... ok, we all didn't.

We didn't know any better!

The Controller

... after many iterations of spit and polish ...


<?php

namespace App;

use Zend\Mvc\Controller\AbstractActionController;
use App\Helper\IsCoyote;
use App\Helper\NotAllowed;
use App\Helper\PurchaseForm;
use App\Helper\GetProduct;
use App\Helper\Notifications;
use App\Helper\CurrentUser;

class PurchaseController extends AbstractActionController
{
    // private properties

Namespaces! Imports! We know what we use! Hooray!


public function __construct(
    IsCoyote $isCoyote,
    NotAllowed $notAllowed,
    PurchaseForm $form,
    GetProduct $getProduct,
    CurrentUser $currentUser,
    Notifications $notifications
) {
    $this->isCoyote = $isCoyote;
    $this->notAllowed = $notAllowed;
    $this->form = $form;
    $this->getProduct = $getProduct;
    $this->currentUser = $currentUser;
    $this->notifications = $notifications;
}

Constructor injection!

NO HORRIBLE MAGIC SINGLETON LOOPS!


public function purchaseAction()
{
    $request = $this->getRequest(); // magic mvc stuff :-(

    if (! $this->isCoyote->isCoyote($request)) {
        return $this->notAllowed->notAllowed($request);
    }

    $this->form->setData($request->getPost());

    if (! $this->form->isValid($this->getPost())) {
        return ['form' => $this->form];
    }

Removed all magic __call-based helpers

Still rely on Zend\Mvc lifecycle magic

Still rely on Zend\Stdlib\RequestInterface


$data = $this->form->getData();

$product = $this->getProduct->get($data['product']);
$product->purchase(); // magic

$this->notifications->notify(
    $this->currentUser->get($request),
    'Purchase completed'
);

return ['success' => true];
}

Still some magic in the domain - let's skip that for now

Reduced framework coupling

Our classes don't extend from the framework anymore.

We have a clean DI graph

We still have the benefits of lazy loading

Less Magic = Less Code Rot

We still type-hint against framework code

Specifically, for the HTTP request

We are mostly doing
HTTP
stuff

(that's what controllers were meant for)

If we abstract into
listeners
we add
framework magic

... and framework coupling

Middleware

Solid specification

Based on PSR-7 and PSR-15

Still developed by the friendly framework folks that brought you ZF1 and ZF2

PSR-7

Immutable representation of HTTP messages and server-side HTTP messages.


namespace Psr\Http\Message;

interface MessageInterface ...
interface RequestInterface ...
interface ServerRequestInterface ...
interface ResponseInterface ...
interface UploadedFileInterface ...
interface UriInterface ...
interface StreamInterface ...

PSR-15


namespace Psr\Http\ServerMiddleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

interface MiddlewareInterface {
    public function process(
        ServerRequestInterface $request,
        DelegateInterface $delegate
    ) : ResponseInterface;
}

namespace Psr\Http\ServerMiddleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

interface DelegateInterface {
    public function process(
        ServerRequestInterface $request
    ) : ResponseInterface;
}

No more
framework coupling!

These standards will outlive the current frameworks

Simple abstractions, just interfaces

Bring your own implementation

What can you do with it?

http://www.php-middleworld.com/

Orthogonal concerns

Solved with function composition, not with magic!

An example middleware pipeline


final class NotFound implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, DelegateInterface $delegate) : ResponseInterface
    {
        return new HtmlResponse('Not Found', 404);
    }
}

final class InitializeSession implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, DelegateInterface $delegate) : ResponseInterface
    {
        return $delegate->process(
            $request->withAttribute(
                'session',
                $this->loadSession($request)
            )
        );
    }

    private function loadSession(ServerRequestInterface $request) : array
    {
        // ...
    }
}

final class IsCoyote implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, DelegateInterface $delegate) : ResponseInterface
    {
        if (! $this->isCoyote($request)) {
            return $this->notAllowed->process($request, $delegate);
        }

        return $delegate->process($request);
    }

    private function isCoyote(ServerRequestInterface $request) : bool
    {
        // ...
    }
}

final class NotAllowed implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, DelegateInterface $delegate) : ResponseInterface
    {
        return new HtmlResponse('You are not allowed here', 403);
    }
}

final class ValidatePurchase implements MiddlewareInterface
{
  // ...
  public function process(ServerRequestInterface $request, DelegateInterface $delegate) : ResponseInterface
  {
    $validationResult = $this->form->validate($request);
    $request = $request->withAttribute(
      'validationResult',
      $validationResult
    );

    if (! $validationResult->isValid()) {
      return $this->validationError->process($request, $delegate);
    }

    return $delegate->process($request, $delegate);
  }
}

final class FormValidationError implements MiddlewareInterface
{
    private $renderer;

    public function __construct(Renderer $renderer)
    {
        $this->renderer = $renderer;
    }

    public function process(ServerRequestInterface $request, DelegateInterface $delegate) : ResponseInterface
    {
        return new HtmlResponse(
            $this->renderer->render(
                'validation-error',
                ['result' => $request->getAttribute('validationResult')]
            ),
            422
        );
    }
}

final class PurchaseProduct implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, DelegateInterface $delegate) : ResponseInterface
    {
        $this->getProduct->get(
            $request->getAttribute('validationResult')->get('product')
        );

        $product->purchase(); // still magic - not for this talk

        $this->notifications->notify(
            $request->getAttribute('session')['user'],
            'Purchase completed'
        );

        return new HtmlResponse($this->renderer->render(
            'product-purchased',
            ['product' => $product]
        ));
    }
}

Simplification through decomposition

Each middleware is ludicrously simple

Each middleware handles orthogonal concerns

Easy to test

Easy to re-arrange

Easy to delete and replace

WARNING

ServerRequestInterface#withAttribute() and ServerRequestInterface#getAttribute() are hacks, and aren't type-safe. Use with care!

Thanks!

What if we combine
CQRS with Middleware?

Middleware does only HTTP.

Command Bus does all the Business Logic.