Yii2: Routing problem: 1+ rules with same route

Created on 16 Oct 2017  ·  13Comments  ·  Source: yiisoft/yii2

What steps will reproduce the problem?

config:

'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'rules' => [
              '/<lang>/news/<page>' => 'front/default/news',
              '/<lang>/новости/<page>' => 'front/default/news',
            ],
        ],

beforeAction:

Yii::$app->language = Yii::$app->request->get('lang')
  • Case 1:
    current url : /en/news/1
echo Url::current(['page' => 2, 'lang' => Yii::$app->language]);
  • Case 2:
    current url : /ru/новости/1
echo Url::current(['page' => 2, 'lang' => Yii::$app->language]);

What is the expected result?

  • Case 1: /en/news/2
  • Case 2: /ru/новости/2

What do you get instead?

  • Case 1: /en/новости/2
  • Case 2: /ru/новости/2

Additional info

| Q | A
| ---------------- | ---
| Yii version | 2.0.13 - dev
| PHP version | 7.1.*
| Operating system | Ubuntu 16.04.3

same result without <lang>

Is this bug, or I'm doing something wrong?

under discussion

Most helpful comment

@gevorgmansuryan This does not make much sense. Rules should have unique set of route and default params. You should use syntax propsed by @samdark (https://github.com/yiisoft/yii2/issues/14977#issuecomment-336808693) to differentiate URLs for different languages or this:

'rules' => [
    [
        'pattern' => '/новости/<page>',
        'route' => 'front/default/news',
        'defaults' => [
            'page' => 1,
            'lang' => 'ru',
        ],
    ],
    [
        'pattern' => '/news/<page>',
        'route' => 'front/default/news',
        'defaults' => [
            'page' => 1,
            'lang' => 'en',
        ],
    ],
]

Url::to(['front/default/news', 'page' => 2, 'lang' => 'en']); // /news/2
Url::to(['front/default/news', 'page' => 2, 'lang' => 'ru']);  // /новости/2

if you want to hide language prefix from URLs.

All 13 comments

I think you've got your rules wrong. Should be:

'/<lang:en>/news/<page>' => 'front/default/news',
'/<lang:ru>/новости/<page>' => 'front/default/news',

@samdark \

same result with this ruleset

'/news/<page>' => 'front/default/news',
 '/новости/<page>' => 'front/default/news',

in yiisoft/yii2/helpers/BaseUrl.php

    public static function current(array $params = [], $scheme = false)
    {
        $currentParams = Yii::$app->getRequest()->getQueryParams();
        $currentParams[0] = '/' . Yii::$app->controller->getRoute();
        $route = ArrayHelper::merge($currentParams, $params);
        return static::toRoute($route, $scheme);
    }

Yii::$app->controller->getRoute() always returns 'front/default/news'
and Url::toRoute() takes the latest rule from ruleset.

So for 2 cases above it takes '/новости/<page>' or '/news/<page>' for all rules with same route.

UrlManager not able to determine which pattern to use. And takes the last one from list ordered by cacheKey i guess.

@samdark What if we use UrlRule::$name with route?

Yii::$app->controller->getRouteName() or Yii::$app->controller->getRoutePattern().

Values can be set in UrlRule::parseRequest for matched pattern.

something like this:

'rules' => [
    [
        'pattern' => '/новости/<page>',
        'route' => 'front/default/news',
        'name' => 'news_ru',
        'defaults' => [
            'page' => 1
        ],
    ],
    [
        'pattern' => '/news/<page>',
        'route' => 'front/default/news',
        'name' => 'news_en,
        'defaults' => [
            'page' => 1
        ],
    ],
]

Url::to(['news_en', 'page' => 2]); // /news/2
Url::to(['news_ru', 'page' => 2]);  // /новости/2
public function createUrl($manager, $route, $params)
{
//...
// match the route part first
        if ($route !== $this->route) {
            if (($route == $this->name && $this->name != $this->pattern) || $this->_routeRule !== null && preg_match($this->_routeRule, $route, $matches)) {
                //...
            } else {
                //...
            }
        }
}

Your example is incorrect. Since first rule will match front/default/news route it will always be used for creating URL. The only weird thing is that you get /ru/новости/2 instead of /ru/news/2

@rob006 Yeah, I know, that it will use front/default/news for both cases. That's why I'm suggesting add ability differentiation rules with same route using name property (if it's specified in route definition) like in example above.

@gevorgmansuryan This does not make much sense. Rules should have unique set of route and default params. You should use syntax propsed by @samdark (https://github.com/yiisoft/yii2/issues/14977#issuecomment-336808693) to differentiate URLs for different languages or this:

'rules' => [
    [
        'pattern' => '/новости/<page>',
        'route' => 'front/default/news',
        'defaults' => [
            'page' => 1,
            'lang' => 'ru',
        ],
    ],
    [
        'pattern' => '/news/<page>',
        'route' => 'front/default/news',
        'defaults' => [
            'page' => 1,
            'lang' => 'en',
        ],
    ],
]

Url::to(['front/default/news', 'page' => 2, 'lang' => 'en']); // /news/2
Url::to(['front/default/news', 'page' => 2, 'lang' => 'ru']);  // /новости/2

if you want to hide language prefix from URLs.

@rob006 https://github.com/yiisoft/yii2/issues/14977#issuecomment-336812584 is a real case. I think we should introduce an ability to alias a rule so it's used for creating URLs directly w/o searching for it in a rules array.

I agree with @rob006 because we should keep rules configuration as clean as possibile. Introducing aliases in rules some developers could use aliases improperly, just to avoid to write complete requirements for that route.

@yiisoft/core-developers thoughts?

@rob006 in https://github.com/yiisoft/yii2/issues/14977#issuecomment-337350272
it may work for Url::to with specified lang, what about Url::current ?
For example I'm using yii\data\Pagination and it uses Url::current for creating page urls.

Agreed with @rob006

@gevorgmansuryan It will work fine with Url::current(). You don't need to pass current language to Url::current() if it is already in current URL.
http://www.yiiframework.com/doc-2.0/guide-helper-url.html#creating-urls

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kminooie picture kminooie  ·  3Comments

psfpro picture psfpro  ·  3Comments

Locustv2 picture Locustv2  ·  3Comments

schmunk42 picture schmunk42  ·  3Comments

MUTOgen picture MUTOgen  ·  3Comments