UrlManagerを使用してルーティングを構成し、「わかりやすい」URLを作成する方法



親愛なる読者の皆さん、こんにちは! Yii2フレームワークとAngularJSを使用して非定型の大規模プロジェクトを開発した方法に関する一連の記事を続けます。



前の記事で、選択したテクノロジースタックの利点を説明し、 アプリケーションのモジュールアーキテクチャを提案しました。



この記事では、各モジュールのurlManagerを使用して個別にルーティングを構成し、URLを作成する方法について説明します。 また、UrlRuleInterfaceを拡張するクラスを記述することにより、特定のURLに対して独自のルールを作成するプロセスをソートします。 結論として、サイトの公開ページのメタタグの生成と出力の実装方法について説明します。



カットの下で最も興味深い。



URLルール



少なくともCNCを有効にして、URLからindex.phpを隠すために、すでにUrlManagerをすでに使用していると思われます。



//... 'urlManager' => [ 'class' => 'yii\web\UrlManager', 'enablePrettyUrl' => true, 'showScriptName' => false, ], /..
      
      





しかし、UrlManagerはそれ以上のことができます。 美しいURLは、検索エンジンでのサイト配信に大きな影響を与えます。 また、独自のルールを定義することにより、アプリケーションの構造を隠すことができることを考慮する必要があります。



'enableStrictParsing' => trueは、設定済みのルールのみへのアクセスを制限する非常に便利なプロパティです。 設定例では、ルートwww.our-site.comはsite / default / indexを指しますが、 www.our-site.com / site / default / indexはページ404を表示します。



 //... 'urlManager' => [ 'class' => 'yii\web\UrlManager', 'enablePrettyUrl' => true, 'showScriptName' => false, 'enableStrictParsing' => true, 'rules' => [ '/' => 'site/default/index', ], ], /..
      
      





アプリケーションをモジュールに分割し、これらのモジュールを可能な限り相互に独立させるため、URLルールをURLマネージャーに動的に追加できます。 これにより、モジュールが独自のURLルールを管理するため、UrlManagerを設定することなく、モジュールを配布および再利用できます。



動的に追加されたルールをルーティングプロセス中に有効にするには、自己調整段階でルールを追加する必要があります。 モジュールの場合、これは、次のようにyii \ base \ BootstrapInterfaceを実装し、bootstrap()ブートメソッドにルールを追加する必要があることを意味します。



 <?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>' ] ); } }
      
      





このコードを含むBootstrap.phpファイルをモジュールフォルダー/ modules / site /に追加します。 そして、独自のUrlルールを追加するすべてのモジュールにこのようなファイルがあります。



また、これらのモジュールをyii \ web \ Application :: bootstrap()にリストして、自己調整プロセスに参加できるようにする必要があることに注意してください。 これを行うには、/ frontend / config / main.phpファイルのブートストラップ配列のモジュールをリストします。



 //... 'params' => require(__DIR__ . '/params.php'), 'bootstrap' => [ 'modules\site\Bootstrap', 'modules\users\Bootstrap', 'modules\cars\Bootstrap' 'modules\lease\Bootstrap' 'modules\seo\Bootstrap' ], ];
      
      





最初の記事の執筆以降、いくつかのモジュールが追加されていることに注意してください。





カスタムURLルール



標準クラスyii \ web \ UrlRuleはほとんどのプロジェクトに十分な柔軟性があるという事実にもかかわらず、独自のルールクラスを作成する必要がある状況があります。



たとえば、自動車ディーラーのウェブサイトでは、/ new-lease / state / Make-Model-Location / YearなどのURL形式をサポートできます。ここで、state、Make、Model、Year、およびLocationは、ベーステーブルに格納されたデータに対応する必要がありますデータ。 デフォルトクラスは、静的に宣言されたパターンに依存しているため、ここでは機能しません。



コードから少し脱線しましょう。私たちが直面したタスクの本質を説明します。



仕様に従って、Urlおよびメタタグを生成するための対応するルールを使用して、次のタイプのページを作成する必要がありました。



検索結果ページ



これらは、次の3つのタイプに分けられます。



ディーラー広告

url:/ new-lease /(state)/(Make)-(Model)-(Location)

url:/ new-lease /(state)/(Make)-(Model)-(Location)/(Year)


カスタム広告:

url:/ lease-transfer /(状態)/(作成)-(モデル)-(場所)

url:/ lease-transfer /(状態)/(作成)-(モデル)-(場所)/(年)


例:/ new-lease / NY / volkswagen-GTI-New-York-City / 2015



フィルターで場所が指定されていない場合の検索結果:

/(新規リース|リース譲渡)/(作成)-(モデル)/(年)



タイトル:(Make)(Model)(Year)for Lease in(Location)。 (新規リース|リース譲渡)

例:ニューヨークでのリース向けのフォルクスワーゲンGTI 2015。 ディーラーリース。

キーワード:(作成)、(モデル)、(年)、for、リース、in、(場所)、(新規、リース|リース、転送)

説明:リース可能な(場所)の(作成)(モデル)(年)のリスト (ディーラーリース|リース譲渡)。


広告レビューページ



これらは、次の2つのタイプに分けられます。



ディーラーの発表:

url:/ new-lease /(state)/(make)-(model)-(year)-(color)-(fuel type)-(location)-(id)



カスタムアナウンス:

url:/ lease-transfer /(state)/(make)-(model)-(year)-(color)-(fuel type)-(location)-(id)



タイトル:(make)-(model)-(year)-(color)-(fuel type)for lease in(location)

キーワード:(年)、(製造)、(モデル)、(色)、(燃料の種類)、(場所)、for、lease

説明:(年)(メーカー)(モデル)(色)(燃料の種類)リース(場所)


車両情報ページ



url:/ i /(make)-(model)-(year)

タイトル:(製造)-(モデル)-(年)

キーワード:(年)、(製造)、(モデル)

説明:(年)、(製造)、(モデル)


これは、実装する必要があるページの一部にすぎません。 参照条件からの抜粋をここに示します。これにより、Url Managerから何を達成する必要があるか、およびルールがどれほど重要かを理解できます。



Yii2では、カスタムUrlRuleクラスを作成することにより、解析およびURL生成ロジックを介してカスタムURLを定義できます。 独自のUrlRuleを作成する場合は、yii \ web \ UrlRuleからコードをコピーして展開するか、場合によってはyii \ web \ UrlRuleInterfaceを実装します。



以下は、私たちのチームが議論したURL構造のために書いたコードです。 彼のために、ファイル/modules/seo/components/UrlRule.phpを作成しました。 私はこのコードを標準とは考えていませんが、それがタスクを明確に果たしていると確信しています。



 <?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; } }
      
      





使用するには、このクラスをルールの配列yii \ web \ UrlManager :: $ルールに追加するだけです。



これを行うには、/ modules / seoモジュールでBootstrap.phpファイルを作成し(/ modules / siteモジュールのBootstrap.phpファイルと同様)、次のルールを宣言します。

 //... public function bootstrap($app) { $app->getUrlManager()->addRules( [ [ 'class' => 'modules\seo\components\UrlRule, ], ] ); } /..
      
      





この特別なルールは、非常に具体的なユースケース用です。 このルールを他のプロジェクトで再利用する予定はないため、設定はありません。



通常、ルールは構成できないため、yii \ web \ UrlRule、yii \ base \ Object、またはその他のものから拡張する必要はありません。 yii \ web \ UrlRuleInterfaceインターフェースを実装するだけで十分です。 再利用モジュールでこのルールを再利用する予定はないため、SEOモジュールで定義しました。



parseRequest()はルートを調べ、条件内の正規表現と一致する場合、さらに解析してパラメーターを抽出します。



このメソッドでは、生成されたリンクがurlフィールドに格納される補助ルートモデルを使用します。 彼らは、findRouteByUrlメソッドへの準拠を検索します。 このメソッドは、次のフィールドを持つテーブル(ある場合)から1つのレコードを返します。





parseRequest()は、アクションとパラメーターを含む配列を返します。



 [ 'lease/search/view', [ 'node' => new-lease/NY/ volkswagen-GTI-New-York-City/2016, 'role' => 'new-lease', 'state' => 'NY', 'year' => '2016' ] ]
      
      





それ以外の場合、falseを返し、リクエストを解析できないことをUrlManagerに示します。



createUrl()は、提供されたパラメーターからURLを構築しますが、URLがリース/リース/表示、車/情報/表示またはリース/検索/表示のアクションに提案された場合のみです。



パフォーマンス上の理由で



複雑なウェブアプリケーションを開発する場合、URLルールを最適化して、リクエストの解析とURLの作成にかかる時間が短縮されるようにすることが重要です。



URLを解析または作成するとき、URLマネージャーは、宣言された順序でURLルールを解析します。 したがって、URLルールの順序を調整して、使用頻度の低いルールよりも具体的または頻繁に使用されるルールを配置することを検討してください。



アプリケーションがモジュールで構成されていることがよくあります。各モジュールには、モジュールIDと共通のプレフィックスを持つ独自のURLルールのセットがあります。



メタタグの生成と出力



指定されたページタイプに対して特定の形式でメタタグを生成および表示するために、ファイルモジュール/ seo / helpers / Meta.phpにある特別なヘルパーが作成されました。 次のコードが含まれています。



 <?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; } }
      
      





メタタグを設定する必要があるページのビューでこのヘルパーを使用します。 たとえば、広告レビューページの場合、/ modules / lease / views / frontend / lease / view.phpファイルに次の行を追加します



 //... $this->title = \modules\seo\helpers\Meta::all('lease', $model); /..
      
      





メソッドの最初のパラメーターは、メタタグが生成されるページのタイプです。 2番目のパラメーターは、現在の広告のモデルを渡します。



メソッド内では、ページのタイプに応じてメタタグが生成され、yii \ web \ ViewクラスのregisterMetaTagメソッドを使用してヘッドに追加されます。 このメソッドは、タイトルタグ用に生成された文字列を返します。 したがって、yii \ web \ Viewクラスの$ titleプロパティを使用して、ページタイトルを設定します。



ご清聴ありがとうございました!



作成者: greebn9k (Sergey Gribnyak)、pavel -berezhnoy (Pavel Berezhnoy)、 silmarilion (Andrey Khakharev)



All Articles