How to Manually Create a Mezzio Application

If you want to build applications in PHP — from one-page apps to enterprise-grade applications — PHP’s Mezzio framework is a excellent framework to choose. In this tutorial, which is an excerpt from my new book, Mezzio Essentials, I’ll show you how to manually build an application in Mezzio.

Before we build the application, let’s discuss what we’re going to create and how we’re going to do it, so that the process which we follow makes sense. We’re going to build a small movie database application hand, one with no command-line tooling support or any other form of support.

As the book focuses on the essentials of building an application with Mezzio, we’re not going to build a fully-featured, world-killing, ready for IPO, application. Instead, it’s going to do as little as possible. That way, our focus stays with understanding the critical elements of functionality, instead of on all the add-on logic and functionality.

I’m not suggesting that this approach is the wrong way to build a Mezzio application, so please don’t misunderstand me. Building Mezzio applications in this way can be a beneficial approach if it suits your needs. It’s just that, up until now, I’ve found that the Skeleton Installer, which we’ll cover in the next chapter, has suited my needs better.

We’ll build the application in a series of iterations. Here are the essentials:

Let’s get started!

Iteration One - A Router, a Bootstrap File, and a Dependency Injection (DI) Container

In the root directory of your project, from the terminal, run the code below to create the project directory structure and set the current user as the directory’s owner.

# Create the base directory structure.
# Feel free to change this directory to whatever works best for you.
# If you do, remember to change the subsequent two references to it as well.
sudo mkdir -p /opt/laminas/mezzio/manual-build

# Set your user as the owner of the directory structure.
sudo chown -Rv $(whoami) /opt/laminas

cd /opt/laminas/mezzio/manual-build/
#--------------------------------------------------------------------------
# Use Composer to bring in the five required project dependencies.
#--------------------------------------------------------------------------
composer require \
  laminas/laminas-config-aggregator \
  laminas/laminas-diactoros \
  laminas/laminas-servicemanager \
  mezzio/mezzio \
  mezzio/mezzio-fastroute \
  mezzio/mezzio-helpers

#--------------------------------------------------------------------------
# Create the project's directory and file structure
#--------------------------------------------------------------------------
mkdir -p public \
   src/Movies/Middleware resources/templates \
   config/autoload \
   data/cache

touch public/index.php \
  config/{config,container,development.config,pipeline}.php \
  config/autoload/{dependencies.global,mezzio.global}.php \
  data/movies.php \
  src/Movies/BasicRenderer.php \
  src/Movies/Middleware/BasicRenderer.php

This creates the core directory structure, consisting of the bootstrap file (public/index.php), the configuration files, and the five core dependencies; those being:

Next, we need to add a PSR-4 namespace for our classes. To save time, add the following to the newly-generated composer.json file:

"autoload": {
    "psr-4": {
        "Movies\\": "src/Movies/"
    }
},

Now, let’s flesh out the configuration files that index.php uses.

config/config.php

This file takes care of gathering together the collective, configuration settings for the application. It’s simplified by the extensive use of ConfigProvider classes.

<?php

declare(strict_types=1);

use Laminas\ConfigAggregator\ConfigAggregator;
use Laminas\ConfigAggregator\PhpFileProvider;

$aggregator = new ConfigAggregator(
    [
        \Mezzio\ConfigProvider::class,
        \Mezzio\Helper\ConfigProvider::class,
        \Mezzio\Router\ConfigProvider::class,
        \Mezzio\Router\FastRouteRouter\ConfigProvider::class,
        new PhpFileProvider(realpath(__DIR__) . '/autoload/{{,*.}global,{,*.}local}.php'),
    ]
);

return $aggregator->getMergedConfig();

It starts by initializing a new Laminas\ConfigAggregator\ConfigAggregator object. If you’re not familiar with them, they condense (or aggregate) multiple configuration objects, saving you the hassle of having to code it yourself. In our configuration, we’ll make use of the configuration files in config/autoload, and the referenced ConfigProvider classes, which simplify the process of setting up our Application object.

PackageDescription

Mezzio

Provides the initial configuration for Mezzio.

Mezzio Helper

Sets up a range of helper services, including ServerUrlHelper and UrlHelper.

Mezzio Router

Provides the DI configuration for proper application routing, such as dispatching, routing, and handling requests to routes using disallowed methods.

Mezzio Router FastRouteRouter.

Provides the configuration for using FasteRoute as the application’s router.

From this file, we return the merged configuration.

config/autoload/dependencies.global.php

This file is where we wire up the dependency injection container’s services (or dependencies). Strictly speaking, at this point, we don’t, yet, need it. But, it makes sense to introduce it now, so that we are ready for it later.

<?php

declare(strict_types=1);

return [
    // Provides application-wide services.
    // We recommend using fully-qualified class names whenever possible as
    // service names.
    'dependencies' => [
        // Use 'aliases' to alias a service name to another service. The
        // key is the alias name, the value is the service to which it points.
        'aliases'    => [
            // Fully\Qualified\ClassOrInterfaceName::class => Fully\Qualified\ClassName::class,
        ],
        // Use 'invokables' for constructor-less services, or services that do
        // not require arguments to the constructor. Map a service name to the
        // class name.
        'invokables' => [
            // Fully\Qualified\InterfaceName::class => Fully\Qualified\ClassName::class,
        ],
        // Use 'factories' for services provided by callbacks/factory classes.
        'factories'  => [
            // Fully\Qualified\ClassName::class => Fully\Qualified\FactoryName::class,
        ],
    ],
];

You can see that it returns an array with the primary key being dependencies. I’ve used a generic copy from the Skeleton Installer project whose comments give an excellent explanation of what each key in the array and sub-arrays are for.

config/autoload/mezzio.global.php

This configuration file stores the Mezzio Application object’s configuration settings. I’ve set:

  • ConfigAggregator::ENABLE_CACHE to true. This enables the configuration cache.

  • debug to true. This enables debugging

  • mezzio > raise_throwables to true. This enables exception-based error handling.

<?php

declare(strict_types=1);

use Laminas\ConfigAggregator\ConfigAggregator;

return [
    // Toggle the configuration cache. Set this to boolean false,
    // or remove the directive, to disable configuration caching.
    // Toggling development mode will also disable it by default;
    // clear the configuration cache using `composer clear-config-cache`.
    ConfigAggregator::ENABLE_CACHE => true,

    // Enable debugging; typically used to provide
    // debugging information within templates.
    'debug'                        => false,

    'mezzio' => [
        // Enable exception-based error handling via standard middleware.
        'raise_throwables'      => true,
    ],
];

config/container.php

This file initializes the dependency injection container for the application, based on laminas-servicemanager. It does so by reading in the contents of config/config.php, and using that to initialize a laminas-servicemanager instance.

<?php

declare(strict_types=1);

// Load configuration
use Laminas\ServiceManager\ServiceManager;

$config = require __DIR__ . '/config.php';
$dependencies = $config['dependencies'];
$dependencies['services']['config'] = $config;

// Build container
return new ServiceManager($dependencies);

data/movies.php

Next, we need to initialize the alternative data source for our application, located in data/movies.php. It will contain a small associative array with the essential movie details, including a movie’s the title, director, release date, synopsis, stars, and genre. You can see an example below.

<?php

return [
    [
        'title' => 'Hot Fuzz',
        'director' => 'Edgar Wright',
        'release_date' => '2007/02/14',
        'synopsis' => 'Exceptional London cop Nicholas Angel is involuntarily transferred to a quaint English village and paired with a witless new partner. While on the beat, Nicholas suspects a sinister conspiracy is afoot with the residents.',
        'stars' => [
            'Martin Freeman',
            'Nick Frost',
            'Simon Pegg',
        ],
        'genre' => [
            'action',
            'comedy',
            'mystery',
        ]
    ]
];

If you’ve not seen Hot Fuzz, do yourself a favour and watch it - TODAY! It’s SO worth it!

public/index.php

Now, let’s work through public/index.php, the application’s heart.

<?php

declare(strict_types=1);

use Laminas\Diactoros\Response\HtmlResponse;
use Mezzio\Application;
use Mezzio\MiddlewareFactory;
use Movies\BasicRenderer;
use Psr\Http\Message\ServerRequestInterface;
use \Movies\Middleware\RenderMoviesMiddleware;

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

/**
 * Self-called anonymous function that creates its own scope and keep the global namespace clean.
 */
(function () {
    /** @var \Psr\Container\ContainerInterface $container */
    $container = require 'config/container.php';
    $container->setFactory('MovieData', function() {
        return include 'data/movies.php';
    });

    /** @var Application $app */
    $app = $container->get(
        Application::class
    );

    $factory = $container->get(
        MiddlewareFactory::class
    );

    (require 'config/pipeline.php')($app, $factory, $container);

    $movieData = $container->get('MovieData');

    $app->get(
        '/',
        function (ServerRequestInterface $request) use ($movieData) {
            return new HtmlResponse(
                (new BasicRenderer())(
                    $movieData
                )
            );
        }
    );

    $app->run();
})();

We start by including the two classes we’ll need, and require Composer’s autoloader. After that, we retrieve the initialized container, storing it in an aptly named variable, called $container, by requiring the contents of config/container.php.

We then declare an anonymous function, which will contain the core code for the file. There, we start by retrieving the dependency injection container from config/container.php, and storing it in a new variable, $container.

We next call $container::setFactory() to define a new service, called MovieData, which will return the contents of data/movie.php, which we saw just before.

After that, we initialize a new object, called $app, with the Application object retrieved from the container. Application objects are the core of a Mezzio application, whether they’re built manually or using the Skeleton Installer project.

They handle the core tasks that any Mezzio application needs, including building and managing the application’s routing table, and middleware pipeline, handling responses to requests made to the application. There’s quite a bit to it, which we’ll see progressively, throughout the rest of the book.

Next, we retrieve the MiddlewareFactory from the container. MiddlewareFactory marshals middleware for use in the application. It provides several methods for preparing and returning middleware for use within an application.

With the application object instantiated, we then define a GET route, quite similar to how Sinatra, Slim, or Silex would. To it, we provide the route’s path and the route’s handler, a RenderMoviesMiddleware object, which you can see below.

<?php

namespace Movies\Middleware;

use Laminas\Diactoros\Response\HtmlResponse;
use Movies\BasicRenderer;
use Psr\Http\Message\ServerRequestInterface;

class RenderMoviesMiddleware
{
    private $movieData;

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

    public function __invoke(ServerRequestInterface $request) {
        $renderer = (new BasicRenderer())(
            $this->movieData
        );
        return new HtmlResponse($renderer);
    }
}

The route’s handler is an implementation of Mezzio Middleware. It doesn’t look that sophisticated, does it? However, it’s good to know that it doesn’t need to be. It’s a PHP class, with the implementation of the __invoke magic method being where all the action happens.

The method takes a ServerRequestInterface object, which lets us access the currently called request. However, in this particular case, we’re not going to do much with it. Instead, we’re initializing a new BasicRender object and passing the movieData object to it, which was passed to the class' constructor.

That object is then passed to the constructor of a new HtmlResponse object, which creates an HTML response to this request.

<?php

namespace Movies;

class BasicRenderer
{
    const HEADER = '<tr>
<th>Title</th>
<th>Director</th>
<th>Release Date</th>
<th>Stars</th>
<th>Synopsis</th>
<th>Genre</th>
</tr>';

    const BODY_TEMPLATE = '<tr>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
</tr>';

    public function __invoke($movieData)
    {
        $tableBody = '';

        foreach ($movieData as $movie) {
            $tableBody .= sprintf(
                self::BODY_TEMPLATE,
                $movie['title'],
                $movie['director'],
                (new \DateTime($movie['release_date']))->format('d/M/Y'),
                implode(', ', $movie['stars']),
                $movie['synopsis'],
                implode(', ', $movie['genre'])
            );
        }

        return sprintf(
            '<h1>%s</h1><table style="padding:5px;">%s%s</table>',
            'PHP Movie Database',
            self::HEADER,
            $tableBody
        );
    }
}

The body of the response comes from calling BasicRenderer’s `__invoke method. You can see the code for BasicRenderer above. It’s nothing too special. All it does is to render the movie array data in a hand-generated HTML table. Don’t hate me for using tables, in this case: they’re simple, they work, and they’re uncomplicated.

config/pipeline.php

In this file, we build up the application’s middleware pipeline. This is the over-arching application pipeline where middleware that runs for every request is configured.

<?php

declare(strict_types=1);

use Mezzio\Application;
use Mezzio\Handler\NotFoundHandler;
use Mezzio\MiddlewareFactory;
use Mezzio\Router\Middleware\DispatchMiddleware;
use Mezzio\Router\Middleware\RouteMiddleware;
use Psr\Container\ContainerInterface;

/**
 * Setup middleware pipeline:
 * @param Application $app
 * @param MiddlewareFactory $factory
 * @param ContainerInterface $container
 */
return function (
    Application $app,
    MiddlewareFactory $factory,
    ContainerInterface $container
) : void {
    // Register the routing middleware in the middleware pipeline.
    // This middleware registers the Zend\Expressive\Router\RouteResult request attribute.
    $app->pipe(RouteMiddleware::class);

    // Register the dispatch middleware in the middleware pipeline
    $app->pipe(DispatchMiddleware::class);

    // At this point, if no Response is returned by any middleware, the
    // NotFoundHandler kicks in; alternately, you can provide other fallback
    // middleware to execute.
    $app->pipe(NotFoundHandler::class);
};

In our example, it’s going to be rather modest, including (at this stage) just the bare basics. You can see that it only includes two middleware classes: RouteMiddleware and DispatchMiddleware. These two (respectively) ensure that the routes that we’ll register are known to the application and that the application knows how to respond to them.

Other than that, for this simplistic example, we don’t need anything else. However, in a more sophisticated example, we’d make use of some of the other available middleware classes, including:

ClassDescription

ErrorHandler

Use this middleware class to intercept PHP errors and exceptions.

ImplicitHeadMiddleware

Use this middleware class to handle implicit HEAD requests.

ImplicitOptionsMiddleware

Use this middleware class to handle implicit OPTIONS requests.

MethodNotAllowedMiddleware

Use this middleware class to handle requests to routes with disallowed methods.

NotFoundHandler

Use this middleware class to handle requests to undefined routes.

The Route’s Handler

Let’s look at the route’s handler (the anonymous function) in a bit more depth. It takes one argument, an object which implements the ServerRequestInterface. This object represents an incoming, server-side HTTP request, which includes the following properties:

  • Protocol version

  • HTTP method

  • URI

  • Headers; and

  • Message body

It also encapsulates all data from the CGI or PHP environment. This includes such things as Server, Cookie, and variables; Query strings, and uploaded files. In addition to the request, it also has access to the container ($container), as it’s passed in via the anonymous function’s use method.

The middleware invokes Movies\BasicRenderer.php, which handles rendering the movie data using the MovieData service (retrieved from the container and passed to the anonymous function) in a tabular format. This is then passed to the constructor of a new HtmlResponse object.

Note: I’m doing it this way, as we’re not using a template engine, yet.

An HtmlResponse object, to quote the source file:

Allows creating a response by passing an HTML string to the constructor; by default, sets a status code of 200 and sets the Content-Type header to text/html.

The HtmlResponse object is returned from the anonymous function, which renders the generated HTML table output as the route’s response.

Movies\BasicRenderer.php

If you dive into Movies\BasicRenderer.php, you can see it uses two constants; one for the table header and one for the table body. The __invoke method iterates over the data, building the body rows of the table; it performs some minor formatting in the process. The body rows are combined with the header row, via sprintf, and the result is returned from the function.

Getting back to the middleware, with the route created, it then calls the Application object’s run, which launches the application.

How to Run the Application

With all that hard work out of the way, let’s run the application. We can use PHP’s built-in web server, with the following command:

# Update Composer's autoloader configuration
composer dump-autoload

# Start the internal web server on port 8080
php -S 0.0.0.0:8080 -t public/ &>/dev/null

Next, open your browser to http://localhost:8080, where you will see a page similar to the image below.

Mezzio’s Most Basic Implementation

Wouldn’t you agree it generates an amazingly exciting page? How about no?!.

Cheeky book promotion. Skip over it and keep reading, if you're not interested.
Buy Mezzio Essentials Today and learn the fundamentals that you need to begin building applications today! Mezzio Essentials teaches you the fundamentals of PHP's Mezzio framework — the fundamentals that you need — to begin building get paid for applications with the Mezzio framework right away. It’s a practical, hands-on approach, which shows you just enough of about the underlying principles and concepts before stepping you through the process of creating an application. Jump to the table of contents

Iteration Two — Make the Default Route’s Handler Reusable

Looking at the route, while it is easy to create, it is also going to get quite cumbersome, and difficult to maintain, rather quickly. Not sure why? Consider the following scenarios:

  • What if we wanted to create a more sophisticated body, which would change based on several conditions?

  • What if we wanted to pass that body around to different routes?

  • What if we wanted to create a more sophisticated response?

Let’s say one or more of these is something that we want to do, and this rather simplistic way of working is getting in the way. So let’s refactor it so that we can. Specifically, let’s make the code more flexible and more accommodating by refactoring the route handler out into a standalone class, called RenderMoviesMiddleware.php. You can see its source below.

<?php

namespace Movies\Middleware;

use Laminas\Diactoros\Response\HtmlResponse;
use Movies\BasicRenderer;
use Psr\Http\Message\ServerRequestInterface;

class RenderMoviesMiddleware
{
    private $movieData;

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

    public function __invoke(ServerRequestInterface $request) {
        $renderer = (new BasicRenderer())(
            $this->movieData
        );
        return new HtmlResponse($renderer);
    }
}

To do so, we first create the class above, which primarily does nothing more than refactor the closure into a class. The __invoke method still takes the same parameters, contains the same body, and returns the same response.

The one notable difference is that it has a class constructor, which takes in the array of movie data, and initializes a private member variable, $movieData, with it. By refactoring it out into a separate class, we’re able to create a more reusable and maintainable handler.

Now that we’ve completed this refactor, we next need to refactor index.php so that it uses the class instead of the anonymous function. There’s not much to do. We only need to instantiate an instance of \Movies\Middleware\RenderMoviesMiddleware, passing it the result of calling $container’s `get method (from before), which retrieves the MovieData service, containing the movie array data.

You can see the changes below.

$app->get('/', new RenderMoviesMiddleware($movieData));

Iteration Three - Register the Handler With the DI Container

Now that the route’s handler has become more flexible and maintainable let’s see how to use a factory to register it as a dependency with the dependency injection container. You can see the source of the new factory class, RenderMoviesMiddlewareFactory, below.

<?php

namespace Movies\Middleware;

use Interop\Container\ContainerInterface;

class RenderMoviesMiddlewareFactory
{
    /**
     * @param ContainerInterface $container
     * @return RenderMoviesMiddleware
     */
    public function __invoke(ContainerInterface $container) : RenderMoviesMiddleware
    {
        $movieData = $container->has('MovieData')
            ? $container->get('MovieData')
            : null;

        return new RenderMoviesMiddleware($movieData);
    }
}

Unlike factories in early Zend Expressive versions (which preceded Mezzio), it doesn’t implement the Laminas\ServiceManager\FactoryInterface. However, it still follows part of that earlier convention by implementing the __invoke magic method.

Following the standard naming convention in Mezzio, we’ll call it, RenderMoviesMiddlewareFactory. In that method, we instantiate and return a RenderMoviesMiddleware object, passing to its constructor the MovieData service from the container. We also do a little bit of sanity checking by first checking if the container has such a service. Admittedly, it doesn’t do much other than that and would crash if the service weren’t defined.

The next thing that we need to do in this refactor is to make a minor update to RenderMoviesMiddleware. In previous versions of Mezzio, the class could be used, as is, when returned from the dependency injection container. However, in version 3, it needs to implement the RequestHandlerInterface.

Implementations of this interface process an HTTP request and produces an HTTP response. It requires one method, handle, which takes a ServerRequestInterface argument, and must return a ResponseInterface object.

I don’t know where you stand on the matter, but (for the most part) I’m all for the clarity and simplicity that Return Type Declarations provide.

Implementing this interface doesn’t require much of a change. We need to rename __invoke to handle, and add ResponseInterface as the return type. Also, we’ll refactor the class to implement RequestHandlerInterface. You can see the revised class below.

<?php

namespace Movies\Middleware;

use Laminas\Diactoros\Response\HtmlResponse;
use Movies\BasicRenderer;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

/**
 * Class RenderMoviesMiddleware
 * @package Movies\Middleware
 */
class RenderMoviesMiddleware implements RequestHandlerInterface
{
    /**
     * @var array|\Traversable
     */
    private $movieData;

    /**
     * RenderMoviesMiddleware constructor.
     * @param array|\Traversable $movieData
     */
    public function __construct($movieData)
    {
        $this->movieData = $movieData;
    }

    /**
     * @param ServerRequestInterface $request
     * @return ResponseInterface
     */
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        $renderer = (new BasicRenderer())(
            $this->movieData
        );
        return new HtmlResponse($renderer);
    }
}

With the factory created, we then make a few changes to public/index.php. First, we register RenderMoviesMiddleware as a service with the dependency injection container by adding a subsequent call to setFactory.

The first argument is RenderMoviesMiddleware::class and the second is RenderMoviesMiddlewareFactory::class. Next, we update the default route’s handler to be RenderMoviesMiddleware::class. The rest is as before.

<?php

declare(strict_types=1);

use Mezzio\Application;
use Mezzio\MiddlewareFactory;
use Movies\Middleware\RenderMoviesMiddlewareFactory;
use \Movies\Middleware\RenderMoviesMiddleware;

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

/**
 * Self-called anonymous function that creates its own scope and keep the global namespace clean.
 */
(function () {
    /** @var \Psr\Container\ContainerInterface $container */
    $container = require 'config/container.php';
    $container->setFactory('MovieData', function() {
        return include 'data/movies.php';
    });

    /** @var Application $app */
    $app = $container->get(
        Application::class
    );

    $factory = $container->get(
        MiddlewareFactory::class
    );

    (require 'config/pipeline.php')($app, $factory, $container);

    $container->setFactory(
        RenderMoviesMiddleware::class,
        RenderMoviesMiddlewareFactory::class
    );

    $app->get('/', RenderMoviesMiddleware::class);

    $app->run();
})();

If this looks a bit confusing, what’s happening is that when the default route is requested, $app will attempt to retrieve an instance of RenderMoviesMiddleware from the dependency injection container. In doing so, it will see that RenderMoviesMiddlewareFactory provides it, can return the result of invoking it, which returns the fully instantiated RenderMoviesMiddleware object, containing the movie data.

Iteration Four — Make the App Configuration-driven

How we’ve registered RenderMoviesMiddleware with the dependency injection container is now almost as simple as it could be. However, it still requires the developer to do more work than is necessary. Let’s now do a tiny refactor so that almost no work is required to use this, or any, service that we may add in the future.

To do so, we’re going to create a ConfigProvider class. ConfigProvider classes are plain old PHP classes, with no inheritance hierarchy. As a result, there is minimal, if any, complexity to them.

All they do is return an array that contains a set of configuration options. Specifically, an option that you can put in any one of the provided configuration files, whether that be dependencies, route configurations, middleware, etc., can be used.

Under src/Movies, we’ll create a new file called ConfigProvider.php, which looks as follows:

<?php

namespace Movies;

use Movies\Middleware\RenderMoviesMiddleware;
use Movies\Middleware\RenderMoviesMiddlewareFactory;

class ConfigProvider
{
    public function __invoke() : array
    {
        return [
            'dependencies' => $this->getDependencyConfig(),
        ];
    }

    public function getDependencyConfig() : array
    {
        return [
            'factories' => [
                RenderMoviesMiddleware::class => RenderMoviesMiddlewareFactory::class
            ],
        ];
    }
}

You can see that it implements just two functions: invoke and getDependencyConfig. When invoke is called, it returns an array of configuration options for the module, containing one configuration option, dependencies, which contains a nested associative array. This array can contain the same keys that we’ve already seen in config/autoload/dependencies.global.php, such as aliases, invokables, and factories.

We’re only implementing factories, as in the previous refactor, we created RenderMoviesMiddlewareFactory to handle the instantiation of RenderMoviesMiddleware. In the factories element, similar to how we called setFactory, we provide the service name as the element’s key and the service handler as the element’s value.

With that done, the class is ready to go. To use it, we now need to add it in config/config.php, as you can see in the example below, which will call its __invoke magic method, returning its configuration. When the configuration is returned, it will be merged on top of the application’s existing configuration.

<?php

declare(strict_types=1);

use Laminas\ConfigAggregator\ConfigAggregator;
use Laminas\ConfigAggregator\PhpFileProvider;

$aggregator = new ConfigAggregator(
    [
        \Mezzio\ConfigProvider::class,
        \Mezzio\Helper\ConfigProvider::class,
        \Mezzio\Router\ConfigProvider::class,
        \Mezzio\Router\FastRouteRouter\ConfigProvider::class,

        // Enable the Movies module's ConfigProvider
        Movies\ConfigProvider::class,

        new PhpFileProvider(realpath(__DIR__) . '/autoload/{{,*.}global,{,*.}local}.php'),
    ]
);

return $aggregator->getMergedConfig();

Be careful with the services that you register in your ConfigProvider classes! They can overwrite the global configuration, as well as configuration settings from other modules. If you’re not careful, you may end up in a bit of debugging hell.

Cheeky book promotion. Skip over it and keep reading, if you're not interested.
Buy Mezzio Essentials Today and learn the fundamentals that you need to begin building applications today! Mezzio Essentials teaches you the fundamentals of PHP's Mezzio framework — the fundamentals that you need — to begin building get paid for applications with the Mezzio framework right away. It’s a practical, hands-on approach, which shows you just enough of about the underlying principles and concepts before stepping you through the process of creating an application. Jump to the table of contents

Iteration Five — Extract the Routes

The application is a lot more compact now, far more straightforward to use, and far less effort on a developer’s part. What’s more, we’re almost ready to make our module separately available on GitHub for others to use in their applications - should they ever want to.

However, there’s still a bit to go. For example, the application’s routes are defined in public/index.php. While okay, there are far more logical places to define them, than there, such as in a dedicated routes file like config/routes.php, or in the module’s ConfigProvider class. In this section, we’re going to refactor the application to see how to make use of either option.

config/routes.php

We’ll start with config/routes.php as it is likely the most obvious place to all developers on a project for storing the application’s routes. First, create the new file, such as by using the command touch config/routes.php, or using your preferred text editor or IDE to do it, if it supports that functionality. Then, add the code in the example below.

<?php

declare(strict_types=1);

use Mezzio\Application;
use Mezzio\MiddlewareFactory;
use Psr\Container\ContainerInterface;

return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
};

This class defines an anonymous function which is supplied with two critical parameters, an Application instance, and the application’s container. As the function has access to the application and container, we can then call the application’s route methods, to start defining routes on it.

As we only have one route, we’ll only make one call. The first element, as we’ve already seen, is the request path, which can be called by the user. The second element is the middleware which handles requests to the route. In this case, we’ve retrieved the middleware class directly from the container.

Note: You can specify the class' name as well, and it will be implicitly retrieved and injected.

We can supply an optional third parameter as well, the route’s name. This is handy for referencing the route, which we’ll see later, in a human-readable form.

Other Ways To Call Routes

Method-specific route calls, such as GET, may not always be appropriate, because, for example, you may need to make GET and PUT requests to a route for retrieving and updating individual records. In this case, you can use the more generic route method instead, as in the example below, and avoid defining almost the same route multiple times.

$app->route(
  '/',
  \Movies\Middleware\RenderMoviesMiddleware::class,
  [
    'GET',
    'PUT',
  ]
);

This example is the same as the get method we defined in the previous example, but by using the third parameter, it can now be accessed using both GET and PUT requests.

If you don’t supply an array, the route will be accessible with all HTTP methods. Alternatively, you can use the Mezzio\Router\Route::HTTP_METHOD_ANY constant, to be explicit.

With the routes defined in config/routes.php, we need to enable the new routing table. To do that:

  1. Add the following line, after the reference to $container→setFactory

  2. Remove the call to $app→get

  // ... preceding code

  (require 'config/pipeline.php')($app, $factory, $container);

  (require 'config/routes.php')(
      $app, $factory, $container
  );

  $app->run();

  // ... following code

Now, let’s take it one step further, and refactor the routing table definition again. This time, we’ll move the enabling of the routing table for our module to to src/Movies/ConfigProvider.php. That way, when the module’s enabled, its routes will be available as well.

Be aware that if you define routes at the module-level, they may overwrite routes defined in config/routes.php or other modules that have been activated before your module.

The first thing we need to do is to define a new method, getRouteConfig, which will store the routes. You can see it below. Here, unlike in the previous implementations, the route is defined in a more configuration-driven style.

public function getRouteConfig() : array
{
    return [
        [
            'path'            => '/',
            'middleware'      => RenderMoviesMiddleware::class,
            'allowed_methods' => ['GET'],
        ],
    ];
}

The method returns an array of arrays. Each nested array, like the route methods (other than route), can contain up to four keys. These are: path, middleware, allowed_methods, and name. These match the arguments that we previously supplied to the function calls.

Once getRouteConfig has been defined, we next need to ensure that it’s merged with the application’s configuration. We do that in the __invoke method, by adding a new key, aptly named routes, whose value is the result of calling $this→getRouteConfig. You can see an example of this in the example below.

public function __invoke() : array
{
    return [
        'dependencies' => $this->getDependencyConfig(),
        'routes' => $this->getRouteConfig(),
    ];
}

With that done, to keep the application tidy, we remove the original route definition from config/routes.php. I don’t see any harm in leaving the rest of config/routes.php. It would make for a fun exercise to create another module and define its routes there, and see which of the approaches that you prefer.

We now have one last change to make. We need to add Mezzio\Container\ApplicationConfigInjectionDelegator.php to the Movie module’s dependencies. ApplicationConfigInjectionDelegator allows the Application object to read the routes from the Movie module’s ConfigProvider class.

To enable it, as you can see in the code sample below, we’ve added a delegators element to config/autoload/dependencies.global.php, which will return an array. In that array, we’ve added Mezzio\Application::class as a key, which references an array with one element, ApplicationConfigInjectionDelegator.

public function getDependencyConfig() : array
{
    return [
        'factories' => [
            RenderMoviesMiddleware::class => RenderMoviesMiddlewareFactory::class
        ],
        'delegators' => [
            Mezzio\Application::class => [
                Mezzio\Container\ApplicationConfigInjectionDelegator::class,
            ],
        ],
    ];
}

What Are Delegators?

If you’ve not heard of the term "delegator" before, it comes from the Delegation software design pattern. To quote Wikipedia:

The delegation pattern is an object-oriented design pattern that allows object composition to achieve the same code reuse as inheritance. In delegation, an object handles a request by delegating to a second object (the delegate). The delegate is a helper object, but with the original context.

With language-level support for delegation, this is done implicitly by having self in the delegate refer to the original (sending) object, not the delegate (receiving object). In the delegate pattern, this is instead accomplished by explicitly passing the original object to the delegate, as an argument to a method.

Note that "delegation" is often used loosely to refer to the distinct concept of forwarding, where the sending object simply uses the corresponding member on the receiving object, evaluated in the context of the receiving object, not the original object.

The Delegation pattern is implemented in the laminas-servicemanager, which we’re making use of in our application, by Delegators, which implement the Delegator Factory Interface.

Mezzio supports the concept of delegator factories, which allow decoration of services created by your dependency injection container, across all dependency injection containers supported by Mezzio

A delegator factory is pretty much a wrapper around a real factory. In essence, it allows us to either replace the actual service with a "delegate" or interact with an object produced by a factory before the Zend\ServiceManager returns it.

To put my spin on it, they allow you to add functionality dynamically to existing services. I hope one, or all, of these explanations, make sense.

With the changes made, when the Application object is retrieved from the dependency injection container, it will have the functionality from ApplicationConfigInjectionDelegator dynamically added to it. Specifically, it will have the injectRoutesFromConfig method. This method is required, as the name implies, to use our ConfigProvider class' getRouteConfig method, to build up the application’s routing table.

The Delegate pattern and Delegator Factories might seem like advanced topics, and perhaps they are. However, they’re well worth learning about and using for the advantages that they provide.

Iteration Six — Let’s Make the Application More Resilient

At this stage, our application’s looking pretty good. However, it’s not that resilient.

Handling Requests to Undefined Routes

For example, try requesting any route other than the one route that we’ve defined. If you do, you’ll see a Fatal error, as in the screenshot below.

The Mezzio application fails because it cannot handle undefined routes
Figure 1. The application fails because it cannot handle undefined routes

In any application, there are going to be routes that don’t exist. However, we should handle them a bit more professionally than this. So we’re now going to see how to use some existing, pre-packaged, middleware, available with Mezzio, to do so. In the process, we’re going to build up our middleware pipeline.

In config/pipeline.php, where we store our application-level middleware pipeline, after the addition of DispatchMiddleware, we’ll add NotFoundHandler into the pipe, as in the example below:

$app->pipe(NotFoundHandler::class);

Now, when the route’s requested, we’ll see a human-readable message, which tells the user, succinctly, what’s happened, without exposing any internals of our application, nor scaring away any non-technical users. Much more professional, no?

Requesting an undefined route, handled with the NotFoundHandler.

Handling Requests to Routes With Undefined Methods

Now let’s make one final improvement and handle requests to routes with HTTP methods that our routes haven’t been configured to handle. Currently, the sole route in our routing table only accepts GET requests.

What about if we send a request with a PUT, POST, or DELETE method? If we do that, you’ll see that it returns an HTTP 404 NOT FOUND response code, and in the body, it displays the message Cannot PUT http://localhost:8000/. You can see an example of that using Postman, in the image below.

Requesting a route using a method that’s not allowed, using Postman.
Figure 2. Requesting a route using a method that’s not allowed, using Postman.

If, however, we added MethodNotAllowedMiddleware to config/pipeline.php, as in the example below, the application could respond in a more meaningful way, returning an HTTP 405 Method Not Allowed status code.

$app->pipe(MethodNotAllowedMiddleware::class);
Requesting a route using a method that’s not allowed, handled by MethodNotAllowedMiddleware, using Postman.
Figure 3. Requesting a route using a method that’s not allowed, handled by MethodNotAllowedMiddleware, using Postman.

You may be okay with the previous functionality, but to me, an HTTP 405 response is the correct approach.

I used Postman in the last two screenshots. However, feel free to use curl or any other tool that you’re more familiar with (or just plain prefer).

What Have We Achieved?

It’s been quite a journey from the start of this chapter until now. Let’s reflect on what we’ve achieved. We’ve created a minimalist application by hand, which can render a simple movie listing. Initially, while it was simple, it was quite inflexible and limited in its ability to be reusable. It also would have been increasingly challenging to maintain.

By the end of the refactoring efforts, it’s much more flexible, more reusable, and much less labour-intense to maintain. What’s more, using some of the available middleware classes, it’s much more robust. By now, we’ve seen a glimpse of just how flexible and minimalist Mezzio-based applications are, and we’ve seen how uncomplicated middleware can be;

Middleware’s not something overly intense. It can be but a class which handles one concern and nothing more.

Summary

While all of this is great, and provides a pretty flexible configuration setup, where we could have a service for anything and everything, along with being able to add any number of services and routes as needed, it was a lot of work to create everything by hand.

Moreover, let’s not forget, now that everything’s done, that we still don’t have a view layer to render the movie output more flexibly than using templates. We’re still using strings, along with other functionality, view output. Besides, given all the steps involved, it is more than likely you would spend more time debugging human errors than you would be creating the application code.

This tutorial was an excerpt from my new book, Mezzio Essentials. If you enjoyed the process of building a Mezzio application, and you’d like to go further, then have a look at the table of contents, or buy a copy, via the link below.

Cheeky book promotion. Skip over it and keep reading, if you're not interested.
Buy Mezzio Essentials Today and learn the fundamentals that you need to begin building applications today! Mezzio Essentials teaches you the fundamentals of PHP's Mezzio framework — the fundamentals that you need — to begin building get paid for applications with the Mezzio framework right away. It’s a practical, hands-on approach, which shows you just enough of about the underlying principles and concepts before stepping you through the process of creating an application. Jump to the table of contents

You might also be interested in...


comments powered by Disqus

Books

Buy Mezzio Essentials. Learn the fundamentals that you need, to begin building applications with the Mezzio framework today! Buy Now

Latest YouTube Video

Learn how to write SQL queries in PhpStorm