Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Yii 2 canonical URL in urlManager configuration

I've got urlManager section in app configuration with several URLs per route:

    'urlManager' => [
        'enablePrettyUrl' => true,
        'showScriptName' => false,
        'enableStrictParsing' => true,
        'rules' => [
            'article-a' => 'article/a', // canonic comes first
            'article-deprecated-a' => 'article/a',
            'article-another-a-is-deprecated' => 'article/a',
            'b-annoucement' => 'announcement/b', // canonic comes first
            'legacy-b-annoncement' => 'announcement/b',
            ...

SEF URLs for routes are stored in frontend/config/main.php as an array, with multiple URLs per route. The first URL for the given route (i.e. /article-a) is canonical and the rest are legacy URLs.

What's the most natural way to specify canonical URL for a group of URLs that are pointing to the same route? It can be either rel="canonical" in view or 301/302 redirect to canonical URL.

Canonical URLs should be preferably specified in a place where the routes were defined (frontend/config/main.php configuration file in this case). The requirement here is that canonical URL should be defined outside of the controller, not hard-coded to controller.

like image 496
Estus Flask Avatar asked Mar 11 '16 03:03

Estus Flask


People also ask

Where do you put Canonicals?

The canonical tag is a page-level meta tag that is placed in the HTML header of a webpage. It tells the search engines which URL is the canonical version of the page being displayed.


3 Answers

I'm not sure how exactly you need to manage your rules so I'll go for a general use case and I'll base my answer on what I did understand from Paddy Moogan's Article which I will resume within the following example and I hope it helps on designing your required solution:

requirement:

Assuming a Search Engine did send a robot to check page B in my website and I'm not fine with people getting to page B instead of page A. So this is how I can clarify my point to the robot:

  1. Forcing a 301 redirect to page A:

    Telling the Search Engine that this page is permanently moved to page A. So please don't send more people to it. Send them to page A instead.

  2. Forcing a 302 redirect to page A:

    Telling the Search Engine that this page is temporary moved to page A. So do whatever you think it is appropriate.

  3. Opening page B (200 status code) but insert a Canonical link element pointing to page A:

    Telling the Search Engine that this page is working fine but it is to me a secondary page and I would suggest sending the next visitors to page A instead.


design:

So based on that this is how I would see a possible structure to my rules configuration:

'rules' => [
    [
        // by default: 'class' => 'yii\web\UrlRule',
        'pattern' => '/',
        'route' => 'site/index',
    ],
    [
        // the custom class
        'class' => 'app\components\SEOUrlRule',

        'pattern' => 'about',
        'route' => 'site/about',

        'permanents' => [
            'deprecated-about',
            'an-older-deprecated-about'
        ],

        'temporaries' => [
            'under-construction-about',
        ],

        'secondaries' => [
            'about-page-2'
        ]
    ],
    [
        // different route with own action but canonical should be injected
        'class' => 'app\components\SEOUrlRule',
        'pattern' => 'experimental-about',
        'route' => 'whatever/experimental',
        'canonical' => 'about'
    ],
]

This way I can chain as much arrays as I need to use Yii's default class yii\web\UrlRule while I can have a custom one in my app components folder dedicated to SEO related controllers.

Before going to code, this is how I would expect my website to behave :

  • You visit the /about page you get a 200 response (no canonical added).
  • You visit the /deprecated-about page you get redirected to /about with 301 status code.
  • You visit the /under-construction-about page you get redirected to /about with 302 status code.
  • You visit the /about-page-2 page you get a 200 response (rendered by index/about action). No redirections except a similar tag to this is automatically injected into source code: <link href="http://my-website/about" rel="canonical">
  • You visit the /experimental-about page you get a 200 response (rendered by its own action whatever/experimental) but with that same canonical tag above injected.

code:

The SEOUrlRule will simply extend \yii\web\UrlRule and override its parseRequest method to define the extra attributes based on which we will force a HTTP redirection or call parent::parseRequest() after registering the canonical link tag to the Yii::$app->view:

namespace app\components;

use Yii;

class SEOUrlRule extends \yii\web\UrlRule
{
    public $permanents  = [];
    public $temporaries = [];
    public $secondaries = [];

    public $canonical = null;

    public function parseRequest($manager, $request)
    {
        $pathInfo = $request->getPathInfo();

        if(in_array($pathInfo, $this->permanents)) 
        {
            $request->setPathInfo($this->name);
            Yii::$app->response->redirect($this->name, 301);
        }

        else if(in_array($pathInfo, $this->temporaries)) 
        {
            $request->setPathInfo($this->name);
            Yii::$app->response->redirect($this->name, 302);
        }

        else if($this->canonical or in_array($pathInfo, $this->secondaries)) 
        {
            $route = $this->name;

            if ($this->canonical === null) $request->setPathInfo($route);
            else $route = $this->canonical;

            Yii::$app->view->registerLinkTag([
                'rel' => 'canonical', 
                'href' => Yii::$app->urlManager->createAbsoluteUrl($route)
            ]);
        }

        return parent::parseRequest($manager, $request);
    }
}

And that is all what it needs. Note that Yii::$app->controller or its related actions won't be yet available at this early stage of solving routes as it is shown in this lifecycle diagram but it seems that Yii::$app->view is already initialized and you can use its $params property to set custom parameters (as it is done in this example) which may be useful for more advenced cases where more data should be shared or populated to final output.

like image 124
Salem Ouerdani Avatar answered Oct 20 '22 09:10

Salem Ouerdani


I think you will have problems when creating the URL from the application to "article/a".

Why not use htaccess or the vhost file to do a 302 redirect to the proper URL?

If you want to handle it through the urlManager, I think you can just register the canonical link

$this->registerLinkTag(['rel' => 'canonical', 'href' => 'article/a']); 

in the view. Mode details here: http://www.yiiframework.com/doc-2.0/yii-helpers-baseurl.html#canonical()-detail

like image 4
Mihai P. Avatar answered Oct 20 '22 11:10

Mihai P.


Yii2 provides a tool to generate canonnical urls based on your rules.

\helpers\Url::canonical()

The idea is that it will provide you an url to 'article-a'.

like image 2
s1lent1um Avatar answered Oct 20 '22 10:10

s1lent1um