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 is the MVC layer shipped with Laminas and above, and focuses on performance and flexibility.
The MVC layer is built on top of the following components:
Laminas\Http\PhpEnvironment namespace, which provide objects that ensure the
request is injected with the current environment (including query parameters,
POST parameters, HTTP headers, etc.), and that the response will interact
correctly with the SAPI and output buffering.Laminas\Stdlib\DispatchableInterface. All
"controllers" are simply dispatchable objects.Within the MVC layer, several sub-components are exposed:
Laminas\Mvc\Controller, a set of abstract "controller" classes with basic
responsibilities such as event wiring, action dispatching, etc., as well as
controller plugins.Laminas\Mvc\Service provides a set of laminas-servicemanager factories and
definitions for the default application workflow.Laminas\Mvc\View provides default wiring for renderer selection, view script
resolution, helper registration, and more; additionally, it provides a
number of listeners that tie into the MVC workflow, providing features such
as automated template name resolution, automated view model creation and
injection, etc.The gateway to the MVC is the Laminas\Mvc\Application
object (referred to as Application henceforth). Its primary responsibilities
are to bootstrap resources, to route the request, and to retrieve and
dispatch the controller matched during routing. Once accomplished, it will
render the view, and finish the request, returning and sending the
response.
The basic application structure follows:
application_root/
config/
application.config.php
autoload/
global.php
local.php
// etc.
data/
module/
vendor/
public/
.htaccess
index.php
The public/index.php script marshals all user requests to your website,
retrieving an array of configuration from config/application.config.php. On
return, it run()s the Application, processing the request and returning a
response to the user.
The config directory as described above contains configuration used by
laminas-modulemanager to load modules and merge configuration (e.g., database
configuration and credentials); we will detail this more later.
The vendor sub-directory should contain any third-party modules or libraries
on which your application depends. This might include Laminas, custom
libraries from your organization, or other third-party libraries from other
projects. Libraries and modules placed in the vendor sub-directory should not
be modified from their original, distributed state. Typically, this directory
will be managed by Composer.
Finally, the module directory will contain one or more modules delivering your
application's functionality.
Let's now turn to modules, as they are the basic units of a web application.
A module may contain anything: PHP code, including MVC functionality; library
code; view scripts; and/or public assets such as images, CSS, and JavaScript.
The only requirement — and even this is optional — is that a module
acts as a PHP namespace and that it contains a Module class under that
namespace. This class is eventually consumed by laminas-modulemanager to perform a
number of tasks.
The recommended module structure follows:
module_root<named-after-module-namespace>/
Module.php
autoload_classmap.php
autoload_function.php
autoload_register.php
config/
module.config.php
public/
images/
css/
js/
src/
<module_namespace>/
<code files>
test/
phpunit.xml
bootstrap.php
<module_namespace>/
<test code files>
view/
<dir-named-after-module-namespace>/
<dir-named-after-a-controller>/
<.phtml files>
Since a module acts as a namespace, the module root directory should be that namespace. This namespace could also include a vendor prefix of sorts. As an example a module centered around "User" functionality delivered by Laminas might be named "LaminasUser", and this is also what the module root directory will be named.
Source and test code organization
The above details a PSR-0 structure for the source and test code directories. You can also use PSR-4 so long as you have setup autoloading correctly to do so.
The Module.php file directly under the module root directory will be in the
module namespace shown below.
namespace LaminasUser;
class Module
{
}
Module class location
If you have an autoloader defined, such as detailed later around the various
autoload_*.phpfiles or using Composer's autoloading features, then yourModule.phpfile can be co-located with your source code.Our current recommendation is to define autoloading for your application-specific modules via Composer.
When an init() method is defined, this method will be triggered by a
laminas-modulemanager listener when it loads the module class, and passed a
ModuleManager instance by default. This allows you to perform tasks such as
setting up module-specific event listeners. But be cautious, the init() method
is called for every module on every page request and should only be
used for performing lightweight tasks such as registering event listeners.
Similarly, an onBootstrap() method (which accepts an MvcEvent instance) may
be defined; it is also triggered for every page request, and should be used for
lightweight tasks as well.
The three autoload_*.php files are not required, but recommended if you are
not using Composer to provide autoloading for your module. They provide the
following:
| File | Description |
|---|---|
autoload_classmap.php |
Should return an array classmap of class name/filename pairs (with the filenames resolved via the __DIR__ magic constant). |
autoload_function.php |
Should return a PHP callback that can be passed to spl_autoload_register(). Typically, this callback should utilize the map returned by autoload_classmap.php. |
autoload_register.php |
should register a PHP callback (is typically returned by autoload_function.php with spl_autoload_register(). |
The point of these three files is to provide reasonable default mechanisms for autoloading the classes contained in the module, thus providing a trivial way to consume the module without requiring laminas-modulemanager (e.g., for use outside a Laminas application).
The config directory should contain any module-specific configuration. These
files may be in any format laminas-config supports. We recommend naming the main
configuration module.config.<format> (e.g., for PHP-based configuration,
module.config.php). Typically, you will create configuration for the router as
well as for the service manager.
The src directory should be a PSR-0 or
PSR-4 compliant directory structure with
your module's source code.
The test directory should contain your unit tests. Typically, these are written using
PHPUnit.
The public directory can be used for assets that you may want to expose in
your application's document root. These might include images, CSS files,
JavaScript files, etc. How these are exposed is left to the developer.
The view directory contains view scripts related to your controllers.
The Application has seven basic dependencies.
Traversable.ServiceManager, by the service name "EventManager".ServiceManager, by the service name "SharedEventManager"; this is injected
into the EventManager instance, and then pushed into every new
EventManager instance created.ServiceManager, by the service name "ModuleManager".ServiceManager,
by the service name "Request".ServiceManager,
by the service name "Response".These may be satisfied at instantiation:
use Laminas\EventManager\EventManager;
use Laminas\EventManager\SharedEventManager;
use Laminas\Http\PhpEnvironment;
use Laminas\ModuleManager\ModuleManager;
use Laminas\Mvc\Application;
use Laminas\ServiceManager\ServiceManager;
$config = include 'config/application.config.php';
$serviceManager = new ServiceManager();
$serviceManager->setService('SharedEventManager', new SharedEventManager());
$serviceManager->setService('ModuleManager', new ModuleManager($config));
$serviceManager->setService('Request', new PhpEnvironment\Request());
$serviceManager->setService('Response', new PhpEnvironment\Response());
$serviceManager->setFactory('EventManager', function ($serviceManager) {
return new EventManager($serviceManager->get('SharedEventManager'));
});
$serviceManager->setShared('EventManager', false);
$application = new Application($config, $serviceManager);
Once you've done this, there are two additional actions you can take. The first is to "bootstrap" the application. In the default implementation, this does the following:
Laminas\Mvc\RouteListener).Laminas\Mvc\MiddlewareListener)
(v2.7.0 and up).Laminas\Mvc\DispatchListener).ViewManager listener (Laminas\Mvc\View\ViewManager).MvcEvent, and injects it with the application, request, and
response; it also retrieves the router (Laminas\Router\Http\TreeRouteStack)
at this time and attaches it to the event.If you do not want these actions, or want to provide alternatives, you can do so by extending the
Application class and/or manually coding what actions you want to occur.
The second action you can take with the configured Application is to run()
it. Calling this method performs the following:
When done, it triggers the "finish" event, and then returns the response instance. If an error occurs during either the "route" or "dispatch" event, a "dispatch.error" event is triggered as well.
This is a lot to remember in order to bootstrap the application; in fact, we haven't covered all the
services available by default yet. You can greatly simplify things by using the default
ServiceManager configuration shipped with the MVC.
use Laminas\Loader\AutoloaderFactory;
use Laminas\Mvc\Service\ServiceManagerConfig;
use Laminas\ServiceManager\ServiceManager;
// setup autoloader
AutoloaderFactory::factory();
// get application stack configuration
$configuration = include 'config/application.config.php';
// setup service manager
$serviceManager = new ServiceManager(new ServiceManagerConfig());
$serviceManager->setService('ApplicationConfig', $configuration);
// load modules -- which will provide services, configuration, and more
$serviceManager->get('ModuleManager')->loadModules();
// bootstrap and run application
$application = $serviceManager->get('Application');
$application->bootstrap();
$application->run();
You can make this even simpler by using the init() method of the
Application. This is a static method for quick and easy initialization of the
Application instance.
use Laminas\Loader\AutoloaderFactory;
use Laminas\Mvc\Application;
use Laminas\Mvc\Service\ServiceManagerConfig;
use Laminas\ServiceManager\ServiceManager;
// setup autoloader
AutoloaderFactory::factory();
// get application stack configuration
$configuration = include 'config/application.config.php';
// The init() method does something very similar with the previous example.
Application::init($configuration)->run();
The init() does the following:
service_manager key,
creating a ServiceManager instance with it and with the default services
shipped with laminas-mvc;ApplicationConfig with the application configuration array;ModuleManager service and loads the modules;bootstrap()s the Application and returns its instance.ApplicationConfig service
If you use the
init()method, you cannot specify a service with the name of 'ApplicationConfig' in your service manager config. This name is reserved to hold the array fromapplication.config.php. The following services can only be overridden fromapplication.config.php:
ModuleManagerSharedEventManagerEventManagerandLaminas\EventManager\EventManagerInterfaceAll other services are configured after module loading, thus can be overridden by modules.
You'll note that you have a great amount of control over the workflow. Using the
ServiceManager, you have fine-grained control over what services are
available, how they are instantiated, and what dependencies are injected into
them. Using the EventManager's priority system, you can intercept any of the
application events ("bootstrap", "route", "dispatch", "dispatch.error",
"render", and "finish") anywhere during execution, allowing you to craft your
own application workflows as needed.
While the previous approach largely works, where does the configuration come from? When we create a modular application, the assumption will be that it's from the modules themselves. How do we get that information and aggregate it, then?
The answer is via laminas-modulemanager. This component allows you to specify what modules the application will use; it then locates each module and initializes it. Module classes can tie into various listeners in order to provide configuration, services, listeners, and more to the application. Sounds complicated? It's not.
The first step is configuring the module manager. Inform the module manager which modules to load, and potentially provide configuration for the module listeners.
Remember the application.config.php from earlier? We're going to provide some
configuration.
// config/application.config.php
return [
'modules' => [
/* ... */
],
'module_listener_options' => [
'module_paths' => [
'./module',
'./vendor',
],
],
];
As we add modules to the system, we'll add items to the modules array.
Each Module class that has configuration it wants the Application to know
about should define a getConfig() method. That method should return an array
or Traversable object such as a Laminas\Config\Config instance. As an example:
namespace LaminasUser;
class Module
{
public function getConfig() : array
{
return include __DIR__ . '/config/module.config.php'
}
}
There are a number of other methods you can define for tasks ranging from
providing autoloader configuration, to providing services to the
ServiceManager, to listening to the bootstrap event. The
ModuleManager documentation
goes into more detail on these.
laminas-mvc is incredibly flexible, offering an opt-in, easy to create modular
infrastructure, as well as the ability to craft your own application workflows
via the ServiceManager and EventManager. The ModuleManager is a
lightweight and simple approach to enforcing a modular architecture that
encourages clean separation of concerns and code re-use.
How can I help you explore Laravel packages today?