MKroeders
MKroeders

Reputation: 7752

ZF2 hostname match assemble url and 404

I have the following code to match a route with a hostname;

'router' => [
    'router_class' => 'Zend\Mvc\Router\Http\TranslatorAwareTreeRouteStack',
    'routes' => [
        'website' => [
            'type' => 'hostname',
            'may_terminate' => true,
            'options' => [
                'route' => ':locale.domain.:tld',
                'constraints' => [
                    'language' => '(nl|en)',
                    'tld' => '(dev|org)',
                ],
            ],
        ]
    ],
],

And some routes to match the uri.

So in dev I can have routes like en.domain.dev and nl.domain.dev. The translation of the route also works like a charm but then my 'issues' occur

1. Assemble url

For every url I now need the locale and tld variable. To do this I do <?= $this->url('website/languages', [], true); ?>, but that makes it a mess. Because I only want to use the TLD and locale of the current route.

So I came with the solution to extend the URL view helper. And set the locale and tld param when it was not set with the route match. This worked for 1 'minor' problem, see 2. But was wondering if there was a beter solution for this.

2. 404 paga

In 1 I use the current route match to get the locale/tld to assemble url's this all works, except for the 404 page. Because than there is no route match, obviously.

I started digging arround and when I found the router (from the service locator). I tried to get the route for website it only returned Zend\Mvc\Router\Http\Part. The protected $route was the Zend\Mvc\Router\Http\Hostname which is not accessible. So no luck that way.

The only way I got the Hostname route was by constructing it my self, which I don't really want to do.

So the question is how do you easily reuse the hostname params, and get the hostname params on a 404 page?

I know that for example test.domain.test will not match the hostname and will provide a broken Hostname, but that is a matter of Nginx. So Nginx will make sure the hostname are always correct

Upvotes: 0

Views: 315

Answers (1)

MKroeders
MKroeders

Reputation: 7752

Well I continued searching and thinking and came with a 'solution'.

To tackle 2. I created a listener that listens on the MvcEvent::EVENT_DISPATCH_ERROR.

Code;

// use are above the class declaration
use Zend\Mvc\Router\Http\TranslatorAwareTreeRouteStack;
use Zend\EventManager\AbstractListenerAggregate;
use Zend\EventManager\EventManagerInterface;
use Zend\Http\PhpEnvironment\Request;
use Zend\Mvc\Router\Http\RouteMatch;
use Zend\Mvc\MvcEvent;

public function onDispatchError(MvcEvent $event)
{
    if (is_null($event->getRouteMatch())) {
        $router = $event->getRouter();
        $request = $event->getRequest();

        if ($router instanceof TranslatorAwareTreeRouteStack && $request instanceof Request) {
            $match = $router->getRoute('website')->match($request, strlen($request->getUri()->getPath()), array(
                'translator' => $router->getTranslator(),
            ));
            if ($match instanceof RouteMatch) {
                $match->setMatchedRouteName('website');
                $event->setRouteMatch($match);
            }
        }

    }
}

What it does;

  1. check if there is a route match, if not it is a 404

  2. check the $router is TranslatorAwareTreeRouteStack and the $request is a HttpRequest

  3. get the route for website (this is the route for the hostname`

  4. then it matches it (the trick is that offset is set so only the hostname is checked)

  5. sets the route matched name (is empty if not set which will lead to issues)

  6. then the routematch is injected in the event

To tackle 1. I now had always access to a route match so I could do some own magic. I extended the url viewhelper.

public function __invoke(
    $name = null,
    $params = array(),
    $options = array(),
    $reuseMatchedParams = false
) {
    if ($this->routeMatch instanceof RouteMatch) {
        foreach(['locale', 'tld'] as $key) {
            if (!array_key_exists($key, $params)) {
                $params[$key] = $this->routeMatch->getParam($key);
            }
        }
    }

    // This is needed for the magic that can happen when `reuseMatchedParams` is used at the third position
    if (is_bool($options)) {
        $reuseMatchedParams = $options;
        $options = array();
    }

    return parent::__invoke(
        $name,
        $params,
        $options,
        $reuseMatchedParams
    );
}

Not the most elegant method but it works for now

Upvotes: 1

Related Questions