laminas/laminas-mvc
Laminas MVC is a modular, event-driven MVC framework for PHP applications. It provides routing, controllers, view integration, dependency injection, and configuration management, helping you build scalable, maintainable web apps and APIs on top of Laminas components.
laminas-mvc now registers Laminas\Mvc\MiddlewareListener as a dispatch listener at
a priority higher than Laminas\Mvc\DispatchListener, allowing dispatch of
PSR-7 middleware. Read the
middleware chapter for details on how to use this new feature.
The constructor signature of Laminas\Mvc\Application has changed. Previously, it
was:
__construct($configuration, ServiceManager $serviceManager)
and internally, it pulled the services EventManager, Request, and Response
from the provided $serviceManager during initialization.
The new constructor signature provides optional arguments for injecting the event manager, request, and response:
__construct(
$configuration,
ServiceManager $serviceManager,
EventManager $events = null,
RequestInterface $request = null,
ResponseInterface $response = null
)
This change makes all dependencies explicit. Starting in v3.0, the new arguments will be required.
The factory Laminas\Mvc\Service\ApplicationFactory was updated to follow the new
signature.
This change should only affect users who are manually instantiating the
Application instance.
laminas-mvc provides two mechanisms for injecting event managers into
EventManagerAware objects. One is the "EventManagerAwareInitializer"
registered in Laminas\Mvc\Service\ServiceManagerConfig, and the other is the
Laminas\Mvc\Controller\ControllerManager::injectEventManager() initializer. In
both cases, the logic was updated to be forwards compatible with
laminas-eventmanager v3.
Previously each would check if the instance's getEventManager() method
returned an event manager instance, and, if so, inject the shared event manager:
$events = $instance->getEventManager();
if ($events instanceof EventManagerInterface) {
$events->setSharedManager($container->get('SharedEventManager'));
}
In laminas-eventmanager v3, event managers are now injected with the shared manager at instantiation, and no setter exists for providing the shared manager. As such, the above logic changed to:
$events = $instance->getEventManager();
if (! $events || ! $events->getSharedManager()) {
$instance->setEventManager($container->get('EventManager'));
}
In other words, it re-injects with a new event manager instance if the instance pulled does not have a shared manager composed.
This likely will not cause regressions in existing code, but may be something to be aware of if you were previously depending on lazy-loaded event manager state.
laminas-servicemanager v3.0 removes Laminas\ServiceManager\ServiceLocatorAwareInterface.
Since laminas-mvc provides initializers around that interface, they needed updates
to allow both forwards compatibility with laminas-servicemanager v3 as well as
backwards compatibility with existing applications.
This was accomplished in two ways:
ServiceLocatorAwareInterface, but continue to define the methods that the
interface defines (namely setServiceLocator() and getServiceLocator().Laminas\Mvc\Service\ServiceManagerConfig and
Laminas\Mvc\Controller\ControllerManager now use duck-typing to determine if
an instance requires container injection; if so it will do so.However, we also maintain that service locator injection is an anti-pattern; dependencies should be injected directly into instances instead. As such, starting in 2.7.0, we now emit a deprecation notice any time an instance is injected by one of these initializers, and we plan to remove the initializers for version 3.0. The deprecation notice includes the name of the class, to help you identify what instances you will need to update before the laminas-mvc v3 release.
To prepare your code, you will need to do the following within your controller:
getServiceLocator(), and identify the services
they retrieve.As an example, consider the following code from a controller:
$db = $this->getServiceLcoator()->get('Db\ApplicationAdapter');
To update your controller, you will:
$db property to your class.$db property.$db = $this->db; or just use the
property directly.The controller then might look like the following:
use Laminas\Db\Adapter\AdapterInterface;
use Laminas\Mvc\Controller\AbstractActionController;
class YourController extends AbstractActionController
{
private $db;
public function __construct(AdapterInterface $db)
{
$this->db = $db;
}
public function someAction()
{
$results = $this->db->query(/* ... */);
/* ... */
}
}
A factory would look like the following:
use Interop\Container\ContainerInterface;
class YourControllerFactory
{
public function __invoke(ContainerInterface $container)
{
return new YourController($container->get('Db\ApplicationAdapter'));
}
}
You then also need to ensure the controller manager knows about the factory. It
likely already does, as an invokable; you will redefine it as a factory in
your module.config.php:
return [
'controllers' => [
'factories' => [
YourController::class => YourControllerFactory::class,
/* ... */
],
/* ... */
],
/* ... */
];
While this may seem like more steps, doing so ensures your code has no hidden dependencies, improves the testability of your code, and allows you to substitute alternatives for either the dependencies or the controller itself.
In some cases, you may have dependencies that are only required for some execution paths, such as forms, database adapters, etc. In these cases, you have two approaches you can use:
How can I help you explore Laravel packages today?