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.
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.
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:
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:
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.
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.
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.
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 :
200
response (no
canonical added).301
status code.302
status code.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">
200
response (rendered by its own action whatever/experimental
) but with that same canonical tag above injected.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.
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
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'.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With