Skip to content

[Routing] UrlGenerator: add a method 'hasRoute' to check whether a route exists #19273

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed

[Routing] UrlGenerator: add a method 'hasRoute' to check whether a route exists #19273

wants to merge 6 commits into from

Conversation

voronkovich
Copy link
Contributor

@voronkovich voronkovich commented Jul 2, 2016

Q A
Branch? 3.4
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets -
License MIT
Doc PR -

This method is usefull when you want just to check whether a route exists:

if ($router->hasRoute('homepage')) {
    // ...
}

See #19270 and symfony/symfony-docs#6710

*
* @return bool
*/
public function hasRoute($name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because of backwards compatibility breaks, it cannot be added to the interface before Symfony 4.0.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case, I would not add it even in 4.0 as this does not belongs to a generator interface IMHO.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, indeed, it would make more sense in the Router interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wouterj, @fabpot I've removed the hasRoute method from the UrlGeneratorInterface

@unkind
Copy link
Contributor

unkind commented Jul 2, 2016

Can you describe use-case when you need optional routes?

From my point of view, optional routes make code more fragile and unpredictable.

@wouterj
Copy link
Member

wouterj commented Jul 2, 2016

@unkind imagine loading routes from a database (e.g. using Symfony CMF's DynamicRouter). This is one of the core concepts of a CMS (and thus a core concept of all CMSes built on Symfony).

@voronkovich
Copy link
Contributor Author

@unkind, For example, I want to automatically create CRUD buttons for resources like 'products', 'options', and etc:

$resource = 'product';
...
$actions = [];
foreach ([ 'view', 'edit', 'delete' ] as $action) {
    if ($router->hasRoute($resource . '_' . $action)) {
        $actions[$action] = $router->generate($resource . '_' . $action, [ 'id' => $resourceId ]);
    }
}

*/
public function hasRoute($name)
{
return $this->getGenerator()->hasRoute($name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the interface doesn't have this method anymore, you shouldn't directly use it.
getGenerator() may return different implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@unkind, you're rigth! I think we could add a method availability checking:

public function hasRoute($name)
{
    if (!method_exists($this->getGenerator(), 'hasRoute')) {
        throw new \RuntimeException("Current implementation of an url generator doesn't support this method.");
    }

    return $this->getGenerator()->hasRoute($name);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's terrible IMO.
It's clear that new interface is missing.

@unkind
Copy link
Contributor

unkind commented Jul 2, 2016

@wouterj does it make sense to make separate contract DynamicRouter with hasRoute method then?

This behaviour is specific for dynamic routers, but in general it's not great.

@voronkovich
Copy link
Contributor Author

@unkind I think that the word "dynamic" means nothing in this case. IMO the word "dynamic" means that we can to add new routes at the runtime. But we just want to check a route existence.

@unkind
Copy link
Contributor

unkind commented Jul 2, 2016

Maybe "dynamic" is not good naming for this case, but I failed to find better one. RoutePresenceChecker?

@voronkovich
Copy link
Contributor Author

@unkind RoutePresenceCheckableInterface?

@voronkovich
Copy link
Contributor Author

@unkind, I've added the RoutePresenceCheckableInterface

@unkind
Copy link
Contributor

unkind commented Jul 3, 2016

I'm sorry for confusion, but my idea is to make truly separate service which has nothing to do with Router class.
If Router can't guarantee implementation of the hasRoute, then this method shouldn't be there.
Anyway, wait for Symfony team comments, I'm just casual observer.

@Tobion
Copy link
Contributor

Tobion commented Jul 4, 2016

Seems strange that only the generator implements hasRoute but the matcher doesn't.

@voronkovich
Copy link
Contributor Author

@Tobion, the url matche is dumped to class with match method like this:

public function match($pathinfo)
{
    $allow = array();
    $pathinfo = rawurldecode($pathinfo);
    $context = $this->context;
    $request = $this->request;

    if (0 === strpos($pathinfo, '/_')) {

        // _wdt
       if (0 === strpos($pathinfo, '/_wdt') && preg_match('#^/_wdt/(?P<token>[^/]++)$#s', $pathinfo, $matches)) {
            return $this->mergeDefaults(array_replace($matches, array('_route' => '_wdt')), array ('_controller' => 'web_profiler.controller.profiler:toolbarAction'));
        }

        if (0 === strpos($pathinfo, '/_profiler')) {

            // _profiler_home
            if (rtrim($pathinfo, '/') === '/_profiler') {
                 if (substr($pathinfo, -1) !== '/') {
                     return $this->redirect($pathinfo.'/', '_profiler_home');
                 }

                return array (  '_controller' => 'web_profiler.controller.profiler:homeAction',  '_route' => '_profiler_home',);
            }
        }

    // ...
}

So, we haven't access to the route names after matcher was dumped.

use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RoutePresenceCheckableInterface;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order change of use statements must be reverted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fabpot, Fixed.

@voronkovich voronkovich changed the base branch from master to 3.4 July 6, 2017 17:02
*
* @author Oleg Voronkovich <oleg-voronkovich@yandex.ru>
*/
interface RoutePresenceCheckableInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add this method instead in Symfony 4.0 and not create this RoutePresenceCheckableInterface? It looks overkill to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@javiereguiluz, So, to which interface the hasRoute method should be moved?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any hint @javiereguiluz ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know about the internal details, but I'd like to call this method on the router object:

// old Symfony
public function indexAction()
{
    if ($this->get('router')->hasRoute('special_route')) {
        // ...
    }
}

// new Symfony
public function indexAction(RouterInterface $router)
{
    if ($router->hasRoute('special_route')) {
        // ...
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@javiereguiluz, the router implements the RoutePresenceCheckableInterface interface, so your code should work fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I was suggesting to add it in UrlGeneratorInterface (only in Sf 4.0) to avoid creating micro-interfaces.

@nicolas-grekas nicolas-grekas modified the milestones: 3.4, 4.1 Oct 8, 2017
@nicolas-grekas
Copy link
Member

I'm really no fan of the extra interface, so 👎 to add it in the Routing component.

Instead, I'd propose to add a new twig has_route function (because the same use case exists in twig templates), and add a ControllerTrait::hasRoute() method. For other use cases, the implementation is simple (try/catch) so I ppl could easily create their own helper if needed.

@nicolas-grekas
Copy link
Member

What about making this interface a bit more capable, to cover #19302 also?
Isn't the compiled data structure already containing all the required info to recreate the original route collection? We could add a getRouteCollection method here, and make its implementation "clever" (ie using just-in-time Route instantiantion and generators?

@voronkovich
Copy link
Contributor Author

@nicolas-grekas, The dumper doesn't dump some matching-related data like route conditions and maybe something else.

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Mar 23, 2018 via email

@nicolas-grekas nicolas-grekas modified the milestones: 4.1, next Apr 20, 2018
@fabpot
Copy link
Member

fabpot commented Sep 4, 2018

@nicolas-grekas How do we move forward here?

@nicolas-grekas
Copy link
Member

Instead, I'd propose to add a new twig has_route function (because the same use case exists in twig templates), and add a ControllerTrait::hasRoute() method. For other use cases, the implementation is simple (try/catch) so I ppl could easily create their own helper if needed.

@voronkovich would that work for you? up to implement it?

(let's forget about #19273 (comment))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants