UrlHelper

Zend\Expressive\Helper\UrlHelper provides the ability to generate a URI path based on a given route defined in the Zend\Expressive\Router\RouterInterface. If injected with a route result, and the route being used was also the one matched during routing, you can provide a subset of routing parameters, and any not provided will be pulled from those matched.

Usage

When you have an instance, use either its generate() method, or call the instance as an invokable:

// Using the generate() method:
$url = $helper->generate('resource', ['id' => 'sha1']);

// is equivalent to invocation:
$url = $helper('resource', ['id' => 'sha1']);

The signature for both is:

function (
    $routeName,
    array $routeParams = [],
    $queryParams = [],
    $fragmentIdentifier = null,
    array $options = []
) : string

Where:

Each method will raise an exception if:

Signature changes

The signature listed above is current as of version 3.0.0 of zendframework/zend-expressive-helpers. Prior to that version, the helper only accepted the route name and route parameters.

Registering the pipeline middleware

For the UrlHelper to work, you must first register the UrlHelperMiddleware as pipeline middleware following the routing middleware, and before the dispatch middleware:

use Zend\Expressive\Helper\UrlHelperMiddleware;

// Programmatically:
$app->pipe(RouteMiddleware::class);
// ...
$app->pipe(UrlHelperMiddleware::class);
$app->pipe(DispatchMiddleware::class);

Skeleton configures helpers

If you started your project using the Expressive skeleton package, the UrlHelper and UrlHelperMiddleware factories are already registered for you, as is the UrlHelperMiddleware pipeline middleware.

Using the helper in middleware

Compose the helper in your middleware (or elsewhere), and then use it to generate URI paths:

<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Expressive\Helper\UrlHelper;

class FooMiddleware implements MiddlewareInterface
{
    private $helper;

    public function __construct(UrlHelper $helper)
    {
        $this->helper = $helper;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        $response = $handler->handle($request);
        return $response->withHeader(
            'Link',
            $this->helper->generate('resource', ['id' => 'sha1'])
        );
    }
}

Base Path support

If your application is running under a subdirectory, or if you are running pipeline middleware that is intercepting on a subpath, the paths generated by the router may not reflect the base path, and thus be invalid. To accommodate this, the UrlHelper supports injection of the base path; when present, it will be prepended to the path generated by the router.

As an example, perhaps you have middleware running to intercept a language prefix in the URL; this middleware could then inject the UrlHelper with the detected language, before stripping it off the request URI instance to pass on to the router:

<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Expressive\Helper\UrlHelper;

class LocaleMiddleware implements MiddlewareInterface
{
    private $helper;

    public function __construct(UrlHelper $helper)
    {
        $this->helper = $helper;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        $uri = $request->getUri();
        $path = $uri->getPath();
        if (! preg_match('#^/(?P<locale>[a-z]{2,3}([-_][a-zA-Z]{2}|))/#', $path, $matches)) {
            return $handler->handle($request);
        }

        $locale = $matches['locale'];
        Locale::setDefault(Locale::canonicalize($locale));
        $this->helper->setBasePath($locale);

        return $handler->handle($request->withUri(
            $uri->withPath(substr($path, strlen($locale) + 1))
        ));
    }
}

(Note: if the base path injected is not prefixed with /, the helper will add the slash.)

Paths generated by the UriHelper from this point forward will have the detected language prefix.

Router-specific helpers

Occasionally, you may want to provide a different router instance to nested pipeline middleware; in particular, this may occur when you want to segregate a pipeline by path.

In such situations, you cannot reuse the UrlHelper instance, as a different router is in play; additionally, it may need to define a base path so that any generated URIs contain the full path information (since path segregation strips the specified path prefix from the request).

To facilitate such scenarios, the factories for the UrlHelper and UrlHelperMiddleware allow providing optional arguments to allow varying behavior:

As an example, let us consider a module named Auth where we want to define a path-segregated middleware pipeline that has its own router and route middleware. We might define its dependency configuration as follows:

namespace Auth;

use Zend\Expressive\Helper\UrlHelperFactory;
use Zend\Expressive\Helper\UrlHelperMiddlewareFactory;
use Zend\Expressive\Router\FastRouteRouter;
use Zend\Expressive\Router\Middleware\RouteMiddlewareFactory;

return [
    'dependencies' => [
          'factories' => [
              // module-specific class name => factory
              Router::class                 => FastRouteRouterFactory::class,
              RouteMiddleware::class        => new RouteMiddlewareFactory(Router::class),
              UrlHelper::class              => new UrlHelperFactory('/auth', Router::class),
              UrlHelperMiddleware::class    => new UrlHelperMiddlewareFactory(UrlHelper::class),
          ],
    ],
];

We could then create a path-segregated pipeline like the following:

$app->pipe('/auth', [
    \Auth\RouteMiddleware::class,     // module-specific routing middleware!
    ImplicitHeadMiddleware::class,
    ImplicitOptionsMiddleware::class,
    MethodNotAllowedMiddleware::class,
    \Auth\UrlHelperMiddleware::class, // module-specific URL helper middleware!
    DispatchMiddleware::class,
]);

Any handlers that the module-specific router routes to can then also compose the same UrlHelper instance via their factories:

namespace Auth;

use Psr\Container\ContainerInterface;

class SomeHandlerFactory
{
    public function __invoke(ContainerInterface $container) : SomeHandler
    {
        return new SomeHandler(
            $container->get(UrlHelper::class) // module-specific URL helper!
        );
    }
}

This instance will then be properly configured to generate links using the module-specific router.