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.
Join the discussion
comments powered by Disqus