How to Create a Zend Expressive Module

How to Create a Zend Expressive Module

I've been reflecting recently on the things that I commonly have to do when I begin building Zend Expressive applications. Of the list that I created, I found that one of the most common — and uninteresting — of them is setting up the rendering of static page content.

Unfortunately, despite being uninteresting from a code perspective, static content is necessary. Even the largest of sites usually need such content as terms and conditions, disclaimers, privacy policies and, here in Germany at least an Impressum — if you've never heard of one, be happy.

To solve this, I've usually manually created modules to handle the rendering of static page content. These module's usually had little more than a PageAction class that returned an HtmlResponse object, whose body was a rendered template file.

And depending on the size of the project, I'd either have a number of these classes, large switch statements, or something similar. Why I kept repeating myself and — perhaps more importantly — why I didn't simplify it sooner, I don't know. But the fact is, I didn't. But now I have.

If you're in a similar position to where I was and want to collate the logic into a reusable module, then follow along with me in this tutorial I'm going to step you through how to create a module which you can reuse across any Zend Expressive project.

If you want to skip ahead, have a look at the new module which I created recently. You can use it to follow along that much easier.

What is a Module?

But before we dive in, what is a Zend Expressive module? In short, they're a way of packaging up the code, along with the related files (CSS, JavaScript, configuration, etc.) into a logical, reusable, namespace. If you're familiar with Zend Framework 2, modules in Zend Expressive are not the same thing. They do have a similar result, but they're created and managed quite differently.

Whereas Zend Framework 2 used the Module Manager to manage modules, Zend Expressive's modules only require two things:

  1. That the files are in a PSR-4 namespaced directory,
  2. That they have a ConfigProvider class to wire their dependencies and configuration into the application in which they're used.

Setup Your Environment

Now that we've covered why you should create a module and what it is, let's get our environment setup so that we have all that we need to create one. By this, I'm not referring to components such as Apache, Nginx, PHP, and so on. I'm referring to the 3rd party packages that we'll need to build the module.

We're going to need just three, these are:

  • Zend Expressive: Naturally, we're going to need Zend Expressive, as we're working with such classes and interfaces as TemplateRendererInterface, ServerRequestInterface, DelegateInterface, and Zend\Expressive\Router\RouteResult.
  • PHP: To help and encourage people to upgrade, I always specify a minimum version of PHP. This might seem a little controversial, but while my preference is for at least PHP 7.0, I appreciate and accept that not everyone is in the position to use it (or a higher version) — yet. So for that reason, I allow for PHP 5.6 but ideally 7.0 or higher.
  • Roave Security Advisories: While the package isn't strictly necessary, but I've come to depend on it to ensure that the third-party packages that I use don't have known security vulnerabilities.

Note: we don’t require the Zend Expressive Skeleton Installer to create the module.

In addition to these three requirements, we'll also need a PSR-4 autoload namespace for the source files that we'll be creating. This will be called StaticPages, which will also be the name of the project directory.

So let’s start off by creating the project's directory structure, by running the following command in your terminal, from wherever you want the project directory to be located:

mkdir -p StaticPages/src/Action;

At this point, you could run a series of composer require commands, and then edit the generated composer.json file. Or you could copy the composer.json configuration below into a new composer.json file in the root of your project directory and run composer install.

{
    "require": {
        "php": "^5.6 || ^7.0",
        "roave/security-advisories": "dev-master",
        "zendframework/zend-expressive": "^2.0.2"
    },
    "autoload": {
        "psr-4": {
            "StaticPages\\": "src/"
        }
    }
}

After a little bit of time, the dependencies will be installed, and you'll be ready to start in your IDE or editor of choice.

Note: I've deliberately not included PHPUnit in this part of the series. That's coming up in part two.

What Are We Creating?

I've not fully explained what we're going to do yet. So let's do that. The module will link template files to routes. Take the following route:

$app->get(
  '/static/disclosure',
  StaticPages\Action\StaticPagesAction::class,
  'static.disclosure'
);

When the user requests /static/disclosure, the template file /templates/static-pages/disclosure.phtml (the examples use Zend-View) will be rendered by StaticPagesAction. Once the module's installed, the developer will only have to:

  1. Create the static template files, storing them in /templates/static-pages, in the format and with the file extension applicable for the application's template engine
  2. Create a route with a name that is a combination of 1) the template name, minus the file extension, and full path, and 2) the prefix static.

On thinking it through further, I no longer need the static. prefix. That's a carry over from when I was first creating the module. But it's there for now. I'll remove it in the future.

The PageAction Class

We're now ready to create the code, so let's get to it. The first class that we'll create is StaticPagesAction. This class, similar to PageAction classes generated by the Zend Expressive Skeleton Installer, will work as follows:

  1. Retrieve the name of requested route.
  2. Generate the template name based on that route name.
  3. Return a new HtmlResponse object, which is the result of rendering the generated template name.
<?php

namespace StaticPages\Action;

use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Expressive\Router\RouteResult;
use Zend\Expressive\Router\RouterInterface;
use Zend\Expressive\Template\TemplateRendererInterface;

class StaticPagesAction implements ServerMiddlewareInterface
{
    const TEMPLATE_NS = 'static-pages';

    private $template;

    public function __construct(
      TemplateRendererInterface $template = null
    ) {
        $this->template = $template;
    }

In the code above, we start off by specifying the namespace and classes that we'll make use of. We then create a constructor that accepts a TemplateRendererInterface object, so that we will later have access to the application's template engine so that we can render the static template file. We also define TEMPLATE_NS which will store the template directory prefix.

    public function process(
      ServerRequestInterface $request,
      DelegateInterface $delegate
    ) {
        $routeClass = Zend\Expressive\Router\RouteResult::class;
        $routeName = ($request->getAttribute($routeClass))
                        ->getMatchedRouteName();
        $templateName = sprintf(
          '%s::%s',
          self::TEMPLATE_NS,
          substr($routeName, strrpos($routeName, '.') + 1)
        );

        return new HtmlResponse(
          $this->template->render($templateName)
        );
    }
}

We next define the class' process method, which all Expressive 2.0 middleware classes require. This method first retrieves the RouteResult attribute from the current request, an attribute available in all requests. On that object, we call it's getMatchedRouteName method, which provides us the name of the current route. From there, we then generate a template name, based on the route's name.

Let's assume that the route name was static.disclosure. Based on the code above, the generated template would be static-pages::disclosure. The code first strips off static., leaving the string disclosure. From there, it then prepends static-pages:: plus the double colon separator.

return [
    'templates' => [
        'paths' => [
            'static-pages' => [
              __DIR__ . '/../../templates/static-pages'
            ]
        ]
    ]
];

To help clarify what's going on, consider the template path configuration above which the module will use: In it, we've added a template path called static-pages, which points to the /../../templates/static-pages.

Assuming that the requested route's name was 'static.disclosure', the code would generate the template name: static-pages::disclosure. The template engine would then attempt to retrieve and render /../../templates/static-pages/terms.phtml (assuming that you're using Zend-View as your template engine).

Where's the Exception Handling?

Looking through the code, you may be wondering where the exception handling or other defensive code is. And I'd not blame you for doing so. The reason there is none is that if the template doesn't exist, Zend Expressive will return a 404 Not Found response, effectively handling the problem for us.

This may not be the best way to go. But it's a pragmatic choice — for now. We could log the failure to find the template so that when a 404 happens, we know why. Feel free to create and submit a PR if you like. But I'm not going to cover that in this tutorial.

Create the Instantiating Factory Class

With our class created, we now need to handle its instantiation. As its constructor requires a TemplateRendererInterface object, we'll need a factory class to instantiate it. Let's flesh one out, calling it StaticPagesFactory.

Original hey? No. But it's consistent and predictable. I don't like magic in code.

Here's the definition:

<?php

namespace StaticPages\Action;

use Interop\Container\ContainerInterface;
use Zend\Expressive\Router\RouterInterface;
use Zend\Expressive\Template\TemplateRendererInterface;

class StaticPagesFactory
{
    public function __invoke(ContainerInterface $container)
    {
        $template = $container->has(TemplateRendererInterface::class)
            ? $container->get(TemplateRendererInterface::class)
            : null;

        return new StaticPagesAction($template);
    }
}

You can see that the code first checks if a TemplateRendererInterface object is available in the DI container. If it is, it's retrieved and used to initialize $template. If not, $template's initialized to null. $template is then passed to StaticPagesAction's constructor, which is then returned.

Create the Module's ConfigProvider Class

Now that we've defined all the necessary classes, we'll next create a ConfigProvider class to contain the majority of the module's configuration. That way, we can drop the module into any other project. Here's what it looks like:

<?php

namespace StaticPages;

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

    public function getDependencies()
    {
        return [
            'factories'  => [
                Action\StaticPagesAction::class => Action\StaticPagesFactory::class,
            ],
        ];
    }
}

Here, we're adding Action\StaticPagesAction::class as a container dependency, or service, which will be provided by Action\StaticPagesFactory::class.

The Module's Configuration

At this stage, you may be wondering where the routes are, or how the module will be enabled when it's installed. Both are good questions.

The reason that I've not defined any routes, nor accompanying templates, is because while the package is designed to render them, it's up to the individual to create the template files and reference them in the appropriate routes.

As for the configuration and enabling the module, this can be done one of two ways. You can either add instructions in the project's README file, which is always a good thing to do.

Or, you can create further classes that make use of Composer's event-handling functionality. What I'm currently implementing at the moment is a class that responds to Composer's post-install-cmd event.

This would:

  1. Copy the configuration which we saw above into the application's config/autoload directory, likely calling it static-pages.global.php
  2. Create the templates directory, likely calling it templates/static-pages, off of the project's root directory. That way, any number of template files can be created and stored there, being easy to find and maintain.
  3. Add \StaticPages\ConfigProvider::class, to the ConfigAggregator array in config/config.php, thus enabling the module.

With those three tasks completed, the module would be ready to use. All the user would then have to do is to create both the template files and the accompanying routes in config/routes.php.

In Conclusion

And that's the essentials of creating a distributable and reusable module for Zend Expressive. I tried to keep the example as concise as possible so that we could focus on the core concepts. Having said that, I hope that you now have a basic working knowledge of how to create a module.

As well as PHPUnit, I've also not covered pushing the code to GitHub and creating a Packagist package which references it, so that people can install the code with composer require. However, I'll be doing that in part three of the series. So stay tuned.

So, are you keen to create a module for Zend Expressive? Are you already doing so and would do it differently? ** **Either way, share your feedback in the comments. I'm keen to know what you think.

A special thank you to Rob Allen (@akrabat) for being the technical reviewer of this article.

Need Help and Support?

If you need further help and assistance, tweet me — anytime —; I'm @zfmastery. Otherwise, if you're not already, jump in Zend Framework's Slack channel. The IRC channel's been closed in favor of Slack.

Alternatively, become a member of the Facebook group and ask for help there. Despite what people may tell you, there's loads of support available for Zend Framework.



About Matthew

Matthew Setter Matthew Setter is a PHP & Zend Framework specialist. If you're in need of a custom software application, need to migrate an existing legacy application, or want to know your current application's GPA - get in touch.

Want To Be A Zend Framework Guru?

Drop your email in the box below, and get awesome tutorialsjust like this one — straight to your inbox, PLUS exclusive content only available by email.