Здравствуйте, дорогие читатели! Я продолжаю цикл статей о том, как мы разрабатывали нетипичный, крупный проект с использованием Yii2 framework и AngularJS.
В предыдущей статье я описал преимущества, выбранного нами стека технологий, и предложил модульную архитектуру нашего приложения.
В этом материале речь пойдет о настройке роутинга и создании URL при помощи urlManager для каждого модуля по отдельности. Также разложу по полочкам процесс создания собственных правил для специфических URL, с помощью написания класса, который расширяет UrlRuleInterface. В завершении опишу, как мы реализовали генерацию и вывод мета тегов для публичных страниц сайта.
Самое интересное под катом.
//...
'urlManager' => [
'class' => 'yii\web\UrlManager',
'enablePrettyUrl' => true,
'showScriptName' => false,
],
/..
//...
'urlManager' => [
'class' => 'yii\web\UrlManager',
'enablePrettyUrl' => true,
'showScriptName' => false,
'enableStrictParsing' => true,
'rules' => [
'/' => 'site/default/index',
],
],
/..
<?php
namespace modules\site;
use yii\base\BootstrapInterface;
class Bootstrap implements BootstrapInterface
{
/**
* @inheritdoc
*/
public function bootstrap($app)
{
$app->getUrlManager()->addRules(
[
// объявление правил здесь
'' => 'site/default/index',
'<_a:(about|contacts)>' => 'site/default/<_a>'
]
);
}
}
//...
'params' => require(__DIR__ . '/params.php'),
'bootstrap' => [
'modules\site\Bootstrap',
'modules\users\Bootstrap',
'modules\cars\Bootstrap'
'modules\lease\Bootstrap'
'modules\seo\Bootstrap'
],
];
url: /new-lease/(state)/(Make)-(Model)-(Location)
url: /new-lease/(state)/(Make)-(Model)-(Location)/(Year)
url: /lease-transfer/(state)/(Make)-(Model)-(Location)
url: /lease-transfer/(state)/(Make)-(Model)-(Location)/(Year)
Title: (Make) (Model) (Year) for Lease in (Location). (New Leases|Lease Transfers)
Например: Volkswagen GTI 2015 for Lease in New York City. Dealer Leases.
Keywords: (Make), (Model), (Year), for, Lease, in, (Location), (New, Leases|Lease, Transfers)
Description: List of (Make) (Model) (Year) in (Location) available for lease. (Dealer Leases|Lease Transfers).
Title: (make) — (model) — (year) — (color) — (fuel type) for lease in (location)
Keywords: (year), (make), (model), (color), (fuel type), (location), for, lease
Description: (year) (make) (model) (color) (fuel type) for lease (location)
url: /i/(make) — (model) — (year)
Title: (make) — (model) — (year)
Keywords: (year), (make), (model)
Description: (year), (make), (model)
<?php
namespace modules\seo\components;
use modules\seo\models\Route;
use modules\zipdata\models\Zip;
use yii\helpers\Json;
use Yii;
use yii\web\UrlRuleInterface;
class UrlRule implements UrlRuleInterface
{
public function createUrl($manager, $route, $params)
{
/**
* Lease module create urls
*/
if ($route === 'lease/lease/view') {
if (isset($params['state'], $params['node'], $params['role'])) {
$role = ($params['role'] == 'dealer') ? 'new-lease' : 'lease-transfer';
return $role . '/' . $params['state'] . '/' . $params['node'];
}
}
if ($route === 'lease/lease/update') {
if (isset($params['state'], $params['node'], $params['role'])) {
$role = ($params['role'] == 'dealer') ? 'new-lease' : 'lease-transfer';
return $role . '/' . $params['state'] . '/' . $params['node'] . '/edit/update';
}
}
/**
* Information Pages create urls
*/
if ($route === 'cars/info/view') {
if (isset($params['node'])) {
return 'i/' . $params['node'];
}
}
/**
* Search Pages create urls
*/
if ($route === 'lease/search/view') {
if (!empty($params['url'])) {
$params['url'] = str_replace(' ', '_', $params['url']);
if($search_url = Route::findRouteByUrl($params['url'])) {
return '/'.$params['url'];
} else {
$route = new Route();
$route->url = str_replace(' ', '_', substr($params['url'],1) );
$route->route = 'lease/search/index';
$route->params = json_encode(['make'=>$params['make'], 'model'=>$params['model'], 'location'=>$params['location'] ]);
$route->save();
return '/'.$params['url'];
}
}
if (isset($params['type']) && in_array($params['type'], ['user','dealer'])) {
$type = ($params['type'] == 'dealer')? 'new-lease' : 'lease-transfer';
} else {
return false;
}
if ((isset($params['zip']) && !empty($params['zip'])) || (isset($params['location']) && isset($params['state']))) {
// make model price zip type
if (isset($params['zip']) && !empty($params['zip'])) {
$zipdata = Zip::findOneByZip($params['zip']);
} else {
$zipdata = Zip::findOneByLocation($params['location'], $params['state']);
}
// city state_code
if (!empty($zipdata)) {
$url = $type . '/' . $zipdata['state_code'] . '/' . $params['make'] . '-' . $params['model'] . '-' . $zipdata['city'];
if (!empty($params['year'])) {
$url.='/'.$params['year'];
}
$url = str_replace(' ', '_', $url);
if($search_url = Route::findRouteByUrl($url)) {
return '/'.$url;
} else {
$route = new Route();
$route->url = str_replace(' ','_',$url);
$route->route = 'lease/search/index';
$pars = ['make'=>$params['make'], 'model'=>$params['model'], 'location'=>$zipdata['city'], 'state'=>$zipdata['state_code'] ]; //, 'zip'=>$params['zip'] ];
if (!empty($params['year'])) {
$pars['year']=$params['year'];
}
$route->params = json_encode($pars);
$route->save();
return $route->url;
}
}
}
if (isset($params['make'], $params['model'] )) {
$url = $type . '/' . $params['make'] . '-' . $params['model'] ;
if (!empty($params['year'])) {
$url.='/'.$params['year'];
}
$url = str_replace(' ', '_', $url);
if($search_url = Route::findRouteByUrl($url)) {
return '/'.$url;
} else {
$route = new Route();
$route->url = str_replace(' ','_',$url);
$route->route = 'lease/search/index';
$pars = ['make'=>$params['make'], 'model'=>$params['model'] ];
if (!empty($params['year'])) {
$pars['year']=$params['year'];
}
$route->params = json_encode($pars);
$route->save();
return $route->url;
}
}
}
return false;
}
/**
* Parse request
* @param \yii\web\Request|UrlManager $manager
* @param \yii\web\Request $request
* @return array|boolean
*/
public function parseRequest($manager, $request)
{
$pathInfo = $request->getPathInfo();
/**
* Parse request for search URLs with location and year
*/
if (preg_match('%^(?P<role>lease-transfer|new-lease)\/(?P<state>[A-Za-z]{2})\/(?P<url>[._\sA-Za-z-0-9-]+)\/(?P<year>\d{4})?%', $pathInfo, $matches)) {
$route = Route::findRouteByUrl($pathInfo);
if (!$route) {
return false;
}
$params = [
'node' => $matches['url'] . '/' . $matches['year'],
'role' => $matches['role'],
'state' => $matches['state'],
'year' => $matches['year']
];
if (!empty($route['params'])) {
$params = array_merge($params, json_decode($route['params'], true));
}
return [$route['route'], $params];
}
/**
* Parse request for search URLs with location and with year
*/
if (preg_match('%^(?P<role>lease-transfer|new-lease)\/(?P<url>[._\sA-Za-z-0-9-]+)\/(?P<year>\d{4})%', $pathInfo, $matches)) {
$route = Route::findRouteByUrl($pathInfo);
if (!$route) {
return false;
}
$params = [
'node' => $matches['url'] . '/' . $matches['year'],
'role' => $matches['role'],
'year' => $matches['year']
];
if (!empty($route['params'])) {
$params = array_merge($params, json_decode($route['params'], true));
}
return [$route['route'], $params];
}
/**
* Parse request for leases URLs and search URLs with location
*/
if (preg_match('%^(?P<role>lease-transfer|new-lease)\/(?P<state>[A-Za-z]{2})\/(?P<url>[_A-Za-z-0-9-]+)?%', $pathInfo, $matches)) {
$route = Route::findRouteByUrl([$matches['url'], $pathInfo]);
if (!$route) {
return false;
}
$params = [
'role' => $matches['role'],
'node' => $matches['url'],
'state' => $matches['state']
];
if (!empty($route['params'])) {
$params = array_merge($params, json_decode($route['params'], true));
}
return [$route['route'], $params];
}
/**
* Parse request for search URLs without location and year
*/
if (preg_match('%^(?P<role>lease-transfer|new-lease)\/(?P<url>[._\sA-Za-z-0-9-]+)?%', $pathInfo, $matches)) {
$route = Route::findRouteByUrl($pathInfo);
if (!$route) {
return false;
}
$params = [
'node' => $matches['url'],
'role' => $matches['role'],
];
if (!empty($route['params'])) {
$params = array_merge($params, json_decode($route['params'], true));
}
return [$route['route'], $params];
}
/**
* Parse request for Information pages URLs
*/
if (preg_match('%^i\/(?P<url>[_A-Za-z-0-9-]+)?%', $pathInfo, $matches)) {
$route = Route::findRouteByUrl($matches['url']);
if (!$route) {
return false;
}
$params = Json::decode($route['params']);
$params['node'] = $route['url'];
return [$route['route'], $params];
}
return false;
}
}
//...
public function bootstrap($app)
{
$app->getUrlManager()->addRules(
[
[
'class' => 'modules\seo\components\UrlRule,
],
]
);
}
/..
[
‘lease/search/view’,
[
'node' => new-lease/NY/ volkswagen-GTI-New-York-City/2016,
'role' => ‘new-lease’,
'state' => ‘NY’,
'year' => ‘2016’
]
]
<?php
namespace modules\seo\helpers;
use Yii;
use yii\helpers\Html;
/**
* @package modules\seo\helpers
*/
class Meta
{
/**
* Генерирует meta теги title, keywords, description и возвращает строку Заголовка страницы.
*
* @param string $type Тип страницы, для которой генерируются meta теги
* @param object $model
* @return string $title Заголовок страницы
*/
public static function all($type, $model = null)
{
$title = 'Carvoy | A new generation of leasing a car!'; // Заголовок страницы по-умолчанию.
switch ($type) {
case 'home':
$title = 'Carvoy | A new generation of leasing a car!';
Yii::$app->view->registerMetaTag(['name' => 'keywords','content' => 'lease, car, transfer']);
Yii::$app->view->registerMetaTag(['name' => 'description','content' => 'Carvoy - Change the way you lease! Lease your next new car online and we\'ll deliver it to your doorstep.']);
break;
case 'lease':
$title = $model->make . ' - ' . $model->model . ' - ' . $model->year . ' - ' . $model->exterior_color . ' - ' . $model->engineFuelType . ' for lease in ' . $model->location;
Yii::$app->view->registerMetaTag(['name' => 'keywords','content' => Html::encode($model->year . ', ' . $model->make . ', ' . $model->model . ', ' . $model->exterior_color . ', ' . $model->engineFuelType . ', ' . $model->location . ', for, lease')]);
Yii::$app->view->registerMetaTag(['name' => 'description','content' => Html::encode($model->year . ' ' . $model->make . ' ' . $model->model . ' ' . $model->exterior_color . ' ' . $model->engineFuelType . ' for lease in ' . $model->location)]);
break;
case 'info_page':
$title = $model->make . ' - ' . $model->model . ' - ' . $model->year;
Yii::$app->view->registerMetaTag(['name' => 'keywords','content' => Html::encode($model->year . ', ' . $model->make . ', ' . $model->model)]);
Yii::$app->view->registerMetaTag(['name' => 'description','content' => Html::encode($model->year . ' ' . $model->make . ' ' . $model->model)]);
break;
case 'search':
if ($model['role'] == 'd') $role = 'Dealer Lease';
elseif ($model['role'] == 'u') $role = 'Lease Transfers';
else $role = 'All Leases';
if (isset($model['make']) && isset($model['model'])) {
$_make = (is_array($model['make']))? (( isset($model['make']) && ( count($model['make']) == 1) )? $model['make'][0] : false ) : $model['make'];
$_model = (is_array($model['model']))? (( isset($model['model']) && ( count($model['model']) == 1) )? $model['model'][0] : false ) : $model['model'];
$_year = false;
$_location = false;
if (isset($model['year'])) {
$_year = (is_array($model['year']))? (( isset($model['year']) && ( count($model['year']) == 1) )? $model['year'][0] : false ) : $model['year'];
}
if (isset($model['location'])) {
$_location = (is_array($model['location']))? (( isset($model['location']) && ( count($model['location']) == 1) )? $model['location'][0] : false ) : $model['location'];
}
if ( ($_make || $_model) && !(isset($model['make']) && ( count($model['make']) > 1)) ) {
$title = $_make . (($_model)? ' ' . $_model : '') . (($_year)? ' ' . $_year : '') . ' for Lease' . (($_location)? ' in ' . $_location . '. ' : '. ') . $role . '.';
} else {
$title = 'Vehicle for Lease' . (($_location)? ' in ' . $_location . '. ' : '. ') . $role . '.';
}
Yii::$app->view->registerMetaTag(['name' => 'keywords','content' => Html::encode( ltrim($_make . (($_model)? ', ' . $_model : '') . (($_year)? ', ' . $_year : '') . ', for, Lease' . (($_location)? ', in, ' . $_location : '') . ', ' . implode(', ', (explode(' ', $role))), ', ') ) ]);
Yii::$app->view->registerMetaTag(['name' => 'description','content' => Html::encode( 'List of '. ((!$_model && !$_make)? 'Vehicles' : '') . $_make . (($_model)? ' ' . $_model : '') . (($_year)? ' ' . $_year : '') . (($_location)? ' in ' . $_location : '') . ' available for lease. ' . $role . '.' )]);
} else {
$title = 'Search results';
}
break;
}
return $title;
}
}
//...
$this->title = \modules\seo\helpers\Meta::all('lease', $model);
/..
К сожалению, не доступен сервер mySQL