1. Dependencies and Directories

// We'll need a few dependencies
composer require zendframework/zend-expressive \
    zendframework/zend-expressive-fastroute \
    zendframework/zend-servicemanager \
    && mkdir -p public \
    && touch public/index.php

2. The Bootstrap File

use Zend\Expressive\AppFactory;

chdir(dirname(__DIR__));
require 'vendor/autoload.php';

$app = AppFactory::create();
$app->get('/', function ($request, $response, $next) {
    $response->getBody()->write('Hello, world!');
    return $response;
});

// Setup routing and dispatching middleware
$app->pipeRoutingMiddleware();
$app->pipeDispatchMiddleware();

$app->run();

First - Refactor The Handler

// src/App/HelloWorldAction.php
namespace App;

class HelloWorldAction
{
    public function __invoke($request, $response, $next)
    {
        $response->getBody()->write('Hello, world!');
        return $response;
    }
}

Next - Refactor index.php

use Zend\ServiceManager\ServiceManager;

$container = new ServiceManager();

$container->setInvokableClass(
  \App\HelloWorldAction::class
);

$app = AppFactory::create($container);
$app->get('/', \App\HelloWorldAction::class);

1. Add further dependencies

composer require zendframework/zend-config \
                 zendframework/zend-stdlib;

2. Add Configuration Files

// Create further configuration files
mkdir -p config/autoload;

touch config/autoload/dependencies.global.php \
    config/autoload/routes.global.php \
    config/container.php \
    config/config.php;

3. Configure Dependencies

// dependencies.global.php
use Zend\ServiceManager\Factory\InvokableFactory;
use \Zend\Expressive\Container\ApplicationFactory;
use \Zend\Expressive\Application;
use \App\HelloWorldAction;

return [
    'dependencies' => [
        'factories' => [
            Application::class => ApplicationFactory::class,
            HelloWorldAction::class => InvokableFactory::class
        ],
    ]
];

4. Configure Routes

// routes.global.php
return [
    'routes' => [
        [
            'path' => '/',
            'middleware' => \App\HelloWorldAction::class,
            'allowed_methods' => [ 'GET' ],
        ],
    ],
];

5. Simplify Config Retrieval

// config.php
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\Glob;

$config = [];

foreach (Glob::glob(
    'config/autoload/{{,*.}global,{,*.}local}.php',
    Glob::GLOB_BRACE) as $file
) {
    $config = ArrayUtils::merge($config, include $file);
}

return new ArrayObject($config, ArrayObject::ARRAY_AS_PROPS);

6. Configure the DI Container

// container.php
use Zend\ServiceManager\Config;
use Zend\ServiceManager\ServiceManager;

// Load configuration
$config = require __DIR__.'/config.php';

// Build container
$container = new ServiceManager();
(new Config($config['dependencies']))
    ->configureServiceManager($container);

// Inject config
$container->setService('config', $config);

return $container;

Then Refactor index.php - again

// Retrieve the application from the container
$container = include 'config/container.php';
$app = $container->get(\Zend\Expressive\Application::class);

What Kind of Application?

Minimal skeleton?
  (no default middleware, templates, or assets; configuration only)
  [y] Yes (minimal)
  [n] No (full; recommended)
  Make your selection (No):

Which 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):

Which DI 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):

Which Template Engine?

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):

An Error Handler?

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):

1. Update Dependencies

use Zend\Db\Adapter\{AdapterServiceFactory, Adapter};

return [
    'dependencies' => [
        'factories' => [
            Adapter::class => AdapterServiceFactory::class,
        ],
    ],
];

2. Add Its Configuration

// config/autoload/database.global.php
return [
    'db' => [
        'driver' => 'Pdo',
        'dsn' => 'mysql:host=mysql;port=3306;dbname=project',
        'user' => 'project',
        'password' => 'project'
    ]
];

The Middleware Class

public function __invoke(ServerRequestInterface $request,
    ResponseInterface $response,
    callable $next
) {
    $session = $request->getAttribute(
        SessionMiddleware::SESSION_ATTRIBUTE
    );
    if ($session->get('id') === null) {
        $route = sprintf(
            '/login?redirect_to=%s',
            $this->getCurrentRequest($request)
        )
        return new RedirectResponse($route, 302);
    }
    return $next($request, $response);
}

The Middleware Class

public function __invoke(ServerRequestInterface $request,
    ResponseInterface $response,
    callable $next = null)
{
    $session = $request->getAttribute(
      SessionMiddleware::SESSION_ATTRIBUTE
    );

    $validates = $this->doValidation($request, $session);
    $user = $this->form->getData();

The Middleware Class

    if ($request->getMethod() === 'POST' && $validates) {
        // May throw an exception...
        $userId = $this->userAuthenticationService
                    ->authenticateUser(
                        $user->getUsername(),
                        $user->getPassword()
                    );
        $session->set('id', $userId);
        return new RedirectResponse(
            $this->getRedirectUri($request),
            RFC7231::FOUND
        );
    }

The Middleware Class

    return new HtmlResponse(
      $this->template->render(self::PAGE_TEMPLATE, [
        'form' => $this->form,
      ])
    );
}

Initialising It

public function __invoke(ContainerInterface $container) {
    $router   = $container->get(RouterInterface::class);
    $template = $container->get(TemplateRendererInterface::class);
    $userRepository = $container->get(
        UserAuthenticationInterface::class
    );
    $userEntity = new LoginUser();

    return new LoginPageAction(
        $router,
        $template,
        $userRepository,
        $userEntity,
    );
}

Registering Dependencies

return [
  'dependencies' => [
    'factories' => [
      AuthenticationMiddleware::class => AuthenticationMiddlewareFactory::class,
      LoginPageAction::class => LoginPageFactory::class,
    ],
  ]
]

Enabled For One Route

  [
      'name' => 'home',
      'path' => '/',
      'middleware' =>
        [
            \App\Middleware\AuthenticationMiddleware::class,
            App\Action\HomePageAction::class,
        ],
      'allowed_methods' => ['GET'],
  ],
  [
      'name' => 'login',
      'path' => '/login',
      'middleware' => App\Action\LoginPageAction::class,
      'allowed_methods' => ['GET', 'POST'],
  ],

Enabled For All Routes

// middleware-pipeline.global.php
'routing' => [
    'middleware' => [
        ApplicationFactory::ROUTING_MIDDLEWARE,
        \App\Middleware\AuthenticationMiddleware::class,
        ApplicationFactory::DISPATCH_MIDDLEWARE,
    ],
    'priority' => 1,
],