简体   繁体   中英

How to get matched route name in View - Zend Expressive

I know that I can generate URL passing the route name

<?php echo $this->url('route-name') #in view file ?>

But can I get information in opposite direction? From current URL/URI, I need to get route name.

Real case is: I have layout.phtml where is the top menu (html). Current link in the menu need to be marked with css class. So, example what I need is:

<?php // in layout.phtml file
  $index_css   = $this->getRouteName() == 'home-page' ? 'active' : 'none'; 
  $about_css   = $this->getRouteName() == 'about'     ? 'active' : 'none'; 
  $contact_css = $this->getRouteName() == 'contact'   ? 'active' : 'none';  
?>

I am using fast route, but I am interesting in any solution. Solution doesn't have to be in View file.

From my research, there is such information in RouteResult instance in the public method getMatchedRouteName() . The problem is how to reach to this instance from the View.

We know that we can get RouteResult, but from the Request object which is in a Middleware's __invoke() method.

public function __invoke($request, $response, $next){
    # instance of RouteResult
    $routeResult = $request->getAttribute('Zend\Expressive\Router\RouteResult');
    $routeName   = $routeResult->getMatchedRouteName();
    // ... 
}

As @timdev recommended we will find inspiration in existing helper UrlHelper and make almost the same implementation in custom View Helper.

In short we will create 2 classes.

  1. CurrentUrlHelper with method setRouteResult() and
  2. CurrentUrlMiddleware with __invoke($req, $res, $next)

We will inject the CurrentUrlHelper in CurrentUrlMiddleware and in __invoke() method call the CurrentUrlHelper::setRouteResult() with appropriate RouteResult instance. Later we can use our CurrentUrlHelper with RouteResult instance in it. Both classes should have an Factory too.

class CurrentUrlMiddlewareFactory {
    public function __invoke(ContainerInterface $container) {
        return new CurrentUrlMiddleware(
            $container->get(CurrentUrlHelper::class)
        );
    }
}

class CurrentUrlMiddleware {
    private $currentUrlHelper;

    public function __construct(CurrentUrlHelper $currentUrlHelper) {
        $this->currentUrlHelper = $currentUrlHelper;
    }

    public function __invoke($request, $response, $next = null) {
        $result = $request->getAttribute('Zend\Expressive\Router\RouteResult');
        $this->currentUrlHelper->setRouteResult($result);

        return $next($request, $response); # continue with execution
    }
}

And our new helper:

class CurrentUrlHelper {
    private $routeResult;

    public function __invoke($name) {
        return $this->routeResult->getMatchedRouteName() === $name;
    }

    public function setRouteResult(RouteResult $result) {
        $this->routeResult = $result;
    }
}


class CurrentUrlHelperFactory{
    public function __invoke(ContainerInterface $container){
        # pull out CurrentUrlHelper from container!
        return $container->get(CurrentUrlHelper::class);
    }
}

Now we only need to register our new View Helper and Middleware in the configs:

dependencies.global.php

'dependencies' => [
    'invokables' => [
        # dont have any constructor! 
        CurrentUrlHelper::class => CurrentUrlHelper::class, 
    ],
]

middleware-pipeline.global.php

'factories' => [
    CurrentUrlMiddleware::class => CurrentUrlMiddlewareFactory::class,
], 
'middleware' => [
    Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE,
    Zend\Expressive\Helper\UrlHelperMiddleware::class,
    CurrentUrlMiddleware::class,         # Our new Middleware
    Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE,
],

And finaly we can register our View Helper in templates.global.php

'view_helpers' => [
    'factories' => [
        # use factory to grab an instance of CurrentUrlHelper
        'currentRoute' => CurrentUrlHelperFactory::class 
    ]
],
  • it's important to register our middleware after ROUTING_MIDDLEWARE and before DISPATCH_MIDDLEWARE!

  • Also, we have CurrentUrlHelperFactory only to assign it to the key 'currentRoute'.

Now you can use helper in any template file :)

<?php // in layout.phtml file
  $index_css   = $this->currentRoute('home-page') ? 'active' : 'none'; 
  $about_css   = $this->currentRoute('about') ? 'active' : 'none'; 
  $contact_css = $this->currentRoute('contact') ? 'active' : 'none';  
?>

As you note in your self-answer, UrlHelper is a useful thing to notice. However, creating a new helper that depends on UrlHelper (and reflection) isn't ideal.

You're better off writing your own helper, inspired UrlHelper but not dependent on it.

You can look at the code for UrlHelper, UrlHelperFactory and UrlHelperMiddleware to inform your own implementation.

You could wrap the template renderer in another class and pass the Request to there, subtract what you need and inject it into the real template renderer.

Action middleware:

class Dashboard implements MiddlewareInterface
{
    private $responseRenderer;

    public function __construct(ResponseRenderer $responseRenderer)
    {
        $this->responseRenderer = $responseRenderer;
    }

    public function __invoke(Request $request, Response $response, callable $out = null) : Response
    {
        return $this->responseRenderer->render($request, $response, 'common::dashboard');
    }
}

The new wrapper class:

<?php

declare(strict_types = 1);

namespace Infrastructure\View;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Zend\Diactoros\Stream;
use Zend\Expressive\Router\RouteResult;
use Zend\Expressive\Template\TemplateRendererInterface;

class ResponseRenderer
{
    private $templateRenderer;

    public function __construct(TemplateRendererInterface $templateRenderer)
    {
        $this->templateRenderer = $templateRenderer;
    }

    public function render(Request $request, Response $response, string $templateName, array $data = []) : Response
    {
        $routeResult       = $request->getAttribute(RouteResult::class);
        $data['routeName'] = $routeResult->getMatchedRouteName();

        $body = new Stream('php://temp', 'wb+');
        $body->write($this->templateRenderer->render($templateName, $data));
        $body->rewind();

        return $response->withBody($body);
    }
}

Code is borrowed from GitHub .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM