On this page
Caution
The documentation you are viewing is for an older version of this component.
Switch to the latest (v3) version.
Getting Started
Quick Start: Using the Skeleton + Installer
The easiest way to get started with Mezzio is to use the skeleton application and installer. The skeleton provides a generic structure for creating your applications, and prompts you to choose a router, dependency injection container, template renderer, and error handler from the outset.
Create a new project
First, we'll create a new project, using Composer's create-project
command:
$ composer create-project mezzio/mezzio-skeleton mezzio
This will prompt you to choose:
-
Whether to install a minimal skeleton (no default middleware), a flat application structure (all code under
src/
), or a modular structure (directories undersrc/
are modules, each with source code and potentially templates, configuration, assets, etc.). -
A dependency injection container. We recommend using the default, Laminas ServiceManager. (We use ServiceManager in the examples below, so choose it if you want to code along).
-
A router. We recommend using the default, FastRoute.
-
A template renderer. You can ignore this when creating an API project, but if you will be creating any HTML pages, we recommend installing one. (We use Plates in the examples below, so choose it if you want to code along).
-
An error handler. Whoops is a very nice option for development, as it gives you extensive, browsable information for exceptions and errors raised.
Start a web server
The Skeleton + Installer creates a full application structure that's ready-to-go when complete. You can test it out using built-in web server.
From the project root directory, execute the following:
$ composer run --timeout=0 serve
This starts up a web server on localhost port 8080; browse to http://localhost:8080/ to see if your application responds correctly!
Setting a timeout
Composer commands time out after 300 seconds (5 minutes). On Linux-based systems, the
php -S
command thatcomposer serve
spawns continues running as a background process, but on other systems halts when the timeout occurs.As such, we recommend running the
serve
script using a timeout. This can be done by usingcomposer run
to execute theserve
script, with a--timeout
option. When set to0
, as in the previous example, no timeout will be used, and it will run until you cancel the process (usually viaCtrl-C
). Alternately, you can specify a finite timeout; as an example, the following will extend the timeout to a full day:$ composer run --timeout=86400 serve
Development Tools
We ship tools in our skeleton application to make development easier.
Development Mode
laminas-development-mode allows you to enable and disable development mode from your cli.
$ composer development-enable # enable development mode
$ composer development-disable # disable development mode
$ composer development-status # show development status
The development configuration is set in config/autoload/development.local.php.dist
.
It also allows you to specify configuration and modules that should only be enabled
when in development, and not when in production.
Clear config cache
Production settings are the default, which means enabling the configuration cache. However, it must be easy for developers to clear the configuration cache. That's what this command does.
$ composer clear-config-cache
Testing Your Code
PHPUnit and PHP_CodeSniffer are now installed by default. To execute tests and detect coding standards violations, run the following command:
$ composer check
Security Advisories
We have included the security-advisories
package to notify you about installed dependencies with known security
vulnerabilities. Each time you run composer update
, composer install
, or
composer require
, it prevents installation of software with known and
documented security issues.
Modules
Composer will prompt you during installation to ask if you want a minimal application (no structure or default middleware provided), flat application (all source code under the same tree, and the default selection), or modular application. This latter option is new in the version 2 series, and allows you to segregate discrete areas of application functionality into modules, which can contain source code, templates, assets, and more; these can later be repackaged for re-use if desired.
Support for modules is available via the laminas-component-installer and laminas-config-aggregator packages; the mezzio-tooling. package provides tools for creating and manipulating modules in your application.
Component Installer
Whenever you add a component or module that exposes itself as such, the laminas-component-installer composer plugin will prompt you, asking if and where you want to inject its configuration. This ensures that components are wired automatically for you.
In most cases, you will choose to inject in the config/config.php
file; for
tools intended only for usage during development, choose
config/development.config.php.dist
.
Config Aggregator
The laminas-config-aggregator library collects and merges configuration from different sources. It also supports configuration caching.
As an example, your config/config.php
file might read as follows in order to
aggregate configuration from development mode settings, application
configuration, and theoretical User
, Blog
, and App
modules:
<?php // config/config.php
$aggregator = new ConfigAggregator([
// Module configuration
App\ConfigProvider::class,
BlogModule\ConfigProvider::class,
UserModule\ConfigProvider::class,
// Load application config in a pre-defined order in such a way that local settings
// overwrite global settings. (Loaded as first to last):
// - `global.php`
// - `*.global.php`
// - `local.php`
// - `*.local.php`
new PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
// Load development config if it exists
new PhpFileProvider('config/development.config.php'),
], 'data/config-cache.php');
return $aggregator->getMergedConfig();
The configuration is merged in the same order as it is passed, with later entries having precedence.
Config Providers
ConfigAggregator
works by aggregating "Config Providers" passed to its
constructor. Each provider should be a callable class that requires no
constructor parameters, where invocation returns a configuration array (or a PHP
generator) to be merged.
Libraries or modules can have configuration providers that provide default values
for a library or module. For the UserModule\ConfigProvider
class loaded in the
ConfigAggregator
above, the ConfigProvider
might look like this:
<?php
namespace UserModule;
class ConfigProvider
{
/**
* Returns the configuration array
*
* To add some sort of a structure, each section is defined in a separate
* method which returns an array with its configuration.
*
* @return array
*/
public function __invoke()
{
return [
'dependencies' => $this->getDependencies(),
'users' => $this->getConfig(),
];
}
/**
* Returns the container dependencies
*
* @return array
*/
public function getDependencies()
{
return [
'factories' => [
Action\LoginAction::class =>
Factory\Action\LoginActionFactory::class,
Middleware\AuthenticationMiddleware::class =>
Factory\Middleware\AuthenticationMiddlewareFactory::class,
],
];
}
/**
* Returns the default module configuration
*
* @return array
*/
public function getConfig()
{
return [
'paths' => [
'enable_registration' => true,
'enable_username' => false,
'enable_display_name' => true,
],
];
}
}
mezzio-module command
To aid in the creation, registration, and deregistration of modules in your application, the installer will add the mezzio/mezzio-tooling as a development requirement when you choose the modular application layout.
The tool is available from your application root directory via
./vendor/bin/mezzio-module
. For brevity, we will only reference the tool's
name, mezzio-module
, when describing its capabilities.
This tool provides the following functionality:
mezzio-module create <modulename>
will create the default directory structure for the named module, create aConfigProvider
for the module, add an autoloading rule tocomposer.json
, and register theConfigProvider
with the application configuration.mezzio-module register <modulename>
will add an autoloading rule tocomposer.json
for the module, and register itsConfigProvider
, if found, with the application configuration.mezzio-module deregister <modulename>
will remove any autoloading rules for the module fromcomposer.json
, and deregister itsConfigProvider
, if found, from the application configuration.
You can find out more about its features in the command line tooling documentation.
Adding Middleware
The skeleton makes the assumption that you will be writing your middleware as classes, and uses piping and routing to add your middleware.
Piping
Piping is a foundation feature of the
underlying laminas-stratigility
implementation. You can set up the middleware pipeline in config/pipeline.php
.
In this section, we'll demonstrate setting up a basic pipeline that includes
error handling, segregated applications, routing, middleware dispatch, and more.
The error handler should be the first (most outer) middleware to catch all exceptions.
$app->pipe(ErrorHandler::class);
$app->pipe(ServerUrlMiddleware::class);
After the ErrorHandler
you can pipe more middleware that you want to execute
on every request, such as bootstrapping, pre-conditions, and modifications to
outgoing responses:
$app->pipe(ServerUrlMiddleware::class);
Piped middleware may be either callables or service names. Middleware may also
be passed as an array; each item in the array must resolve to middleware
eventually (i.e., callable or service name); underneath, Mezzio creates
Laminas\Stratigility\MiddlewarePipe
instances with each of the middleware listed
piped to it.
Middleware can be attached to specific paths, allowing you to mix and match applications under a common domain. The handlers in each middleware attached this way will see a URI with the MATCHED PATH SEGMENT REMOVED!!!
$app->pipe('/api', $apiMiddleware);
$app->pipe('/docs', $apiDocMiddleware);
$app->pipe('/files', $filesMiddleware);
Next, you should register the routing middleware in the middleware pipeline:
$app->pipeRoutingMiddleware();
Add more middleware that needs to introspect the routing results; this might include:
- handling for HTTP
HEAD
requests - handling for HTTP
OPTIONS
requests - middleware for handling URI generation
- route-based authentication
- route-based validation
- etc.
$app->pipe(ImplicitHeadMiddleware::class);
$app->pipe(ImplicitOptionsMiddleware::class);
$app->pipe(UrlHelperMiddleware::class);
Next, register the dispatch middleware in the middleware pipeline:
$app->pipeDispatchMiddleware();
At this point, if no response is return by any middleware, we need to provide a
way of notifying the user of this; by default, we use the NotFoundHandler
, but
you can provide any other fallback middleware you wish:
$app->pipe(NotFoundHandler::class);
The full example then looks something like this:
// In config/pipeline.php:
use Mezzio\Helper\ServerUrlMiddleware;
use Mezzio\Helper\UrlHelperMiddleware;
use Mezzio\Middleware\ImplicitHeadMiddleware;
use Mezzio\Middleware\ImplicitOptionsMiddleware;
use Mezzio\Middleware\NotFoundHandler;
use Laminas\Stratigility\Middleware\ErrorHandler;
$app->pipe(ErrorHandler::class);
$app->pipe(ServerUrlMiddleware::class);
// These assume that the variables listed are defined in this scope:
$app->pipe('/api', $apiMiddleware);
$app->pipe('/docs', $apiDocMiddleware);
$app->pipe('/files', $filesMiddleware);
$app->pipeRoutingMiddleware();
$app->pipe(ImplicitHeadMiddleware::class);
$app->pipe(ImplicitOptionsMiddleware::class);
$app->pipe(UrlHelperMiddleware::class);
$app->pipeDispatchMiddleware();
$app->pipe(NotFoundHandler::class);
Routing
Routing is an additional feature
provided by Mezzio. Routing is set up in config/routes.php
.
You can setup routes with a single request method:
$app->get('/', App\Action\HomePageAction::class, 'home');
$app->post('/album', App\Action\AlbumCreateAction::class, 'album.create');
$app->put('/album/:id', App\Action\AlbumUpdateAction::class, 'album.put');
$app->patch('/album/:id', App\Action\AlbumUpdateAction::class, 'album.patch');
$app->delete('/album/:id', App\Action\AlbumDeleteAction::class, 'album.delete');
Or with multiple request methods:
$app->route('/contact', App\Action\ContactAction::class, ['GET', 'POST', ...], 'contact');
Or handling all request methods:
$app->route('/contact', App\Action\ContactAction::class)->setName('contact');
Alternately, to be explicit, the above could be written as:
$app->route(
'/contact',
App\Action\ContactAction::class,
Mezzio\Router\Route::HTTP_METHOD_ANY,
'contact'
);
We recommend a single middleware class per combination of route and request method.
Next Steps
The skeleton provides a default structure for templates, if you choose to use them. Let's see how you can create your first vanilla middleware, and templated middleware.
Creating middleware
To create middleware, create a class implementing
Interop\Http\ServerMiddleware\MiddlewareInterface
. This interface defines a
single method, process()
, which accepts a
Psr\Http\Message\ServerRequestInterface
instance and an
Interop\Http\ServerMiddleware\DelegateInterface
instance.
Legacy double-pass middleware
Prior to Mezzio 2.0, the default middleware style was what is termed "double-pass", for the fact that it passes both the request and response between layers. This middleware did not require an interface, and relied on a conventional definition of:
use Psr\Http\Message; function ( Message\ServerRequestInterface $request, Message\ResponseInterface $response, callable $next ) : Message\ResponseInterface
While this style of middleware is still quite wide-spread and used in a number of projects, it has some flaws. Chief among them is the fact that middleware should not rely on the
$response
instance provided to them (as it may have modifications unacceptable for the current context), and that a response returned from inner layers may not be based off the$response
provided to them (as inner layers may create and return a completely different response).Starting in Mezzio 2.0, we add support for http-interop/http-middleware, which is a working group of PHP-FIG dedicated to creating a common middleware standard. This middleware uses what is termed a "single-pass" or "lambda" architecture, whereby only the request instance is passed between layers. We now recommend writing middleware using the http-middleware interfaces for all new middleware.
Middleware using the double-pass style is still accepted by Mezzio, but support for it will be discontinued with version 3.
The skeleton defines an App
namespace for you, and suggests placing middleware
under the namespace App\Action
.
Let's create a "Hello" action. Place the following in
src/App/Action/HelloAction.php
:
<?php
namespace App\Action;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Laminas\Diactoros\Response\HtmlResponse;
use Psr\Http\Message\ServerRequestInterface;
class HelloAction implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
// On all PHP versions:
$query = $request->getQueryParams();
$target = isset($query['target']) ? $query['target'] : 'World';
// Or, on PHP 7+:
$target = $request->getQueryParams()['target'] ?? 'World';
$target = htmlspecialchars($target, ENT_HTML5, 'UTF-8');
return new HtmlResponse(sprintf(
'<h1>Hello, %s!</h1>',
$target
));
}
}
The above looks for a query string parameter "target", and uses its value to provide a message, which is then returned in an HTML response.
Now we need to inform the application of this middleware, and indicate what
path will invoke it. Open the file config/autoload/dependencies.global.php
.
Edit that file to add an invokable entry for the new middleware:
return [
'dependencies' => [
/* ... */
'invokables' => [
App\Action\HelloAction::class => App\Action\HelloAction::class,
/* ... */
],
/* ... */
],
];
Now open the file config/routes.php
, and add the following at the bottom of
the file:
$app->get('/hello', App\Action\HelloAction::class, 'hello');
Once you've completed the above, give it a try by going to each of the following URIs:
- http://localhost:8080/hello
- http://localhost:8080/hello?target=ME
You should see the message change as you go between the two URIs!
Using templates
You likely don't want to hardcode HTML into your middleware; so, let's use templates. This particular exercise assumes you chose to use the Plates integration.
Templates are installed under the templates/
subdirectory. By default, we also
register the template namespace app
to correspond with the templates/app
subdirectory. Create the file templates/app/hello-world.phtml
with the
following contents:
<?php $this->layout('layout::default', ['title' => 'Greetings']) ?>
<h2>Hello, <?= $this->e($target) ?></h2>
Now that we have a template, we need to:
- Inject a renderer into our action class.
- Use the renderer to render the contents.
Replace your src/App/Action/HelloAction.php
file with the following contents:
<?php
namespace App\Action;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Laminas\Diactoros\Response\HtmlResponse;
use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ServerRequestInterface;
class HelloAction implements MiddlewareInterface
{
private $renderer;
public function __construct(TemplateRendererInterface $renderer)
{
$this->renderer = $renderer;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
// On all PHP versions:
$query = $request->getQueryParams();
$target = isset($query['target']) ? $query['target'] : 'World';
// Or, on PHP 7+:
$target = $request->getQueryParams()['target'] ?? 'World';
return new HtmlResponse(
$this->renderer->render('app::hello-world', ['target' => $target])
);
}
}
The above modifies the class to accept a renderer to the constructor, and then calls on it to render a template. Note that we no longer need to escape our target; the template takes care of that for us.
How does the template renderer get into the action? The answer is dependency injection.
For the next part of the example, we'll be creating and wiring a factory for
creating the HelloAction
instance; the example assumes you used the default
selection for a dependency injection container, laminas-servicemanager.
laminas-servicemanager provides a console (command line) tool for generating factories based on reflecting a class; we'll use that to generate our factory. Navigate to your root skeleton-app directory in the console and enter the following:
$ ./vendor/bin/generate-factory-for-class \
> "App\\Action\\HelloAction" > ./src/App/src/Action/HelloActionFactory.php
For more information see generate-factory-for-class
With that in place, we'll now update our configuration. Open the file
config/autoload/dependencies.global.php
; we'll remove the invokables
entry
we created previously, and add a factories
entry:
return [
'dependencies' => [
/* ... */
'invokables' => [
// Remove this entry:
App\Action\HelloAction::class => App\Action\HelloAction::class,
],
'factories' => [
/* ... */
// Add this:
App\Action\HelloAction::class => App\Action\HelloActionFactory::class,
],
/* ... */
],
];
Save that file, and now re-visit the URIs:
- http://localhost:8080/hello
- http://localhost:8080/hello?target=ME
Your page should now have the same layout as the landing page of the skeleton application!
Congratulations
Congratulations! You've now created your application, and started writing middleware! It's time to start learning about the rest of the features of Mezzio: