Reputation: 1607
If you want to have another middleware/object in a middleware you have to use a factory like
namespace App\Somnething;
use Interop\Container\ContainerInterface;
class MyMiddlewareFactory
{
public function __invoke(ContainerInterface $container, $requestedName)
{
return new $requestedName(
$container->get(\App\Path\To\My\Middleware::class)
);
}
}
So MyMiddleware
would have been injected with \App\Path\To\My\Middleware
and we would be able to access it.
Question: would it be wrong to inject the middleware with the app itself or the container? Like:
namespace App\Somnething;
use Interop\Container\ContainerInterface;
use Zend\Expressive\Application;
class MyMiddlewareFactory
{
public function __invoke(ContainerInterface $container, $requestedName)
{
return new $requestedName(
$container->get(Application::class)
);
}
}
This way it would be possible to get anything ~on the fly. Like
namespace App\Somnething;
use Zend\Expressive\Application;
class MyMiddleware
{
/** @var Application $app */
protected $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public function __invoke($some, $thing)
{
if ($some and $thing) {
$ever = $this->app
->getContainer()
->get(\Path\To\What\Ever::class);
$ever->doSome();
}
}
}
Upvotes: 0
Views: 1179
Reputation: 2039
Injecting the application or container in your middleware is possible but it is not good idea at all:
1) Inversion Of Control (IoC)
It violated the inversion of control principle, your class must not have any knowledge about the IoC container.
2) Dependency Inversion Principle (DIP)
Dependency inversion principle states that "high-level modules should not depend on low-level modules", so your higher level middleware class depends on the infrastructure/framework.
3) Law of Demeter (LoD)
According to the law of demeter, the unit should have limited knowledge about other units, it should know only about its closely related units.
The MyMiddleware::class
has too much knowledge other units, first of all, it knows about the Application::class
, then it knows that the Application
knows about the Container
, then it knows that the Container
knows about the What\Ever::class
and so on.
This kind of code violates some of the most important OOP principles, leads to horrible coupling with the framework, it has implicit dependencies and least but not last, it is hard to be read and understood.
Upvotes: 0
Reputation:
You don't inject middleware into other middleware. You inject dependencies like services or repositories. Each middleware takes care of a specifc task like authentication, authorization, localization negotiation, etc. They are executed one after the other. They mess around with the request and pass the request to the next middleware. Once the middleware stack has been exhausted, the response is returned all the way back through all the middleware in reverse order until it finally reaches the outer layer which displays the output. You can find a flow overview in the expressive docs.
I wouldn't advice to inject the container and certainly not the app itself. Although it might be easy during development, your application becomes untestable. If you inject only the services that are needed into a middleware, action or service you can easily mock those during tests. After a while you get used to writing factories where needed and it goes pretty fast.
The same goes for injecting the entity manager (if you use doctrine). It's easier to test an application if you only inject the needed repositories, which you can easily mock.
Having said this, if you are looking for an easy way to inject dependencies, zend-servicemanager can do that. Take a look at abstract factories. With an abstract factory you can create one factory for all your action classes:
<?php
namespace App\Action;
use Interop\Container\ContainerInterface;
use ReflectionClass;
use Zend\ServiceManager\Factory\AbstractFactoryInterface;
class AbstractActionFactory implements AbstractFactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
// Construct a new ReflectionClass object for the requested action
$reflection = new ReflectionClass($requestedName);
// Get the constructor
$constructor = $reflection->getConstructor();
if (is_null($constructor)) {
// There is no constructor, just return a new class
return new $requestedName;
}
// Get the parameters
$parameters = $constructor->getParameters();
$dependencies = [];
foreach ($parameters as $parameter) {
// Get the parameter class
$class = $parameter->getClass();
// Get the class from the container
$dependencies[] = $container->get($class->getName());
}
// Return the requested class and inject its dependencies
return $reflection->newInstanceArgs($dependencies);
}
public function canCreate(ContainerInterface $container, $requestedName)
{
// Only accept Action classes
if (substr($requestedName, -6) == 'Action') {
return true;
}
return false;
}
}
I wrote a blog post about that.
At the end of the day it's your own decision, but best practice is not injecting the app, container or the entity manager. It will make your life easier if you need to debug your middleware and / or write tests for it.
Upvotes: 4