Cookbook

In This Article

How can I autowire routes and pipelines?

Expressive 2.0 switched to programmatic pipelines and routes, versus
configuration-driven pipelines and routing as used in version 1. One drawback
is that with configuration-driven approaches, users could provide configuration
via a module ConfigProvider, and automatically expose new pipeline middleware
or routes; with a programmatic approach, this is no longer possible.

Or is it?

Delegator Factories

One possibility available to version 2 applications is to use delegator
factories on the Zend\Expressive\Application instance in order to inject
these items.

A delegator factory is a factory that delegates creation of an instance to a
callback, and then operates on that instance for the purpose of altering the
instance or providing a replacement (e.g., a decorator or proxy). The delegate
callback usually wraps a service factory, or, because delegator factories
also return an instance, additional delegator factories. As such, you assign
delegator factories, plural, to instances, allowing multiple delegator
factories to intercept processing of the service initialization.

For the purposes of this particular example, we will use delegator factories to
both pipe middleware as well as route middleware.

To demonstrate, we'll take the default pipeline and routing from the skeleton
application, and provide it via a delegator factory instead.

First, we'll create the class App\Factory\PipelineAndRoutesDelegator, with
the following contents:

Why is an array assigned?

As noted above in the description of delegator factories, since each delegator
factory returns an instance, you can nest multiple delegator factories in
order to shape initialization of a service. As such, they are assigned as an
array to the service.

If you reload your application at this point, you should see that everything
continues to work as expected!

Caution: pipelines

Using delegator factories is a nice way to keep your routing and pipeline
configuration close to the modules in which they are defined. However, there is
a caveat: you likely should not register pipeline middleware in a delegator
factory other than within your root application module.

The reason for this is simple: pipelines are linear, and specific to your
application. If one module pipes in middleware, there's no guarantee it will be
piped before or after your main pipeline, and no way to pipe the middleware at a
position in the middle of the pipeline!

As such:

Use a config/pipeline.php file for your pipeline, OR

Ensure you only define the pipeline in a single delegator factory on your
Application instance.

Caution: third-party, distributed modules

If you are developing a module to distribute as a package via
Composer, you should not autowire any delegator
factories that inject pipeline middleware or routes in the Application.

Why?

As noted in the above section, pipelines should be created exactly once, at
the application level. Registering pipeline middleware within a distributable
package will very likely not have the intended consequences.

If you ship with pipeline middleware, we suggest that you:

Document the middleware, and where you anticipate it being used in the
middleware pipeline.

Document how to add the middleware service to dependency configuration, or
provide the dependency configuration via your module's ConfigProvider.

With regards to routes, there are other considerations:

Routes defined by the package might conflict with the application, or with
other packages used by the application.

Routing definitions are typically highly specific to the router implementation
in use. As an example, each of the currently supported router implementations
has a different syntax for placeholders:

You could, of course, detect what router is in use, and provide routing for each
known, supported router implementation within your delegator factory. We even
recommend doing exactly that. However, we note that such an approach does not
solve the other two points above.

However, we still recommend shipping a delegator factory that would register
your routes, since routes are often a part of module design; just do not
autowire that delegator factory. This way, end-users who can use the
defaults do not need to cut-and-paste routing definitions from your
documentation into their own applications; they will instead opt-in to your
delegator factory by wiring it into their own configuration.

Synopsis

We recommend using delegator factories for the purpose of autowiring routes,
and, with caveats, pipeline middleware:

The pipeline should be created exactly once, so calls to pipe() should
occur in exactly one delegator factory.

Distributable packages should create a delegator factory for routes only,
but should not register the delegator factory by default.

ApplicationConfigInjectionDelegator

Since version 2.2

Zend\Expressive\Container\ApplicationConfigInjectionDelegator allows you to
define configuration that is then used to call pipe() or the various routing
methods of Zend\Expressive\Application. This is particularly useful for
injecting routes.