Drupal 8 に実装されたルーティングシステムは、Symfonyコンポーネントを拡張したものであり、Drupal 7まではルーティングを処理するときにhook_menu()
を使っていましたが完全に置き換えられました。
今回は、新しいルーティングシステムを理解するための最低限の説明と、モジュール実装をする上でたびたび使用することになる、RouteMatch
、CurrentRouteMatch
、Url
の3つのAPIについて、簡単なサンプルコードを紹介していきます。
#検証環境
- Macbook Pro (Retina, 15-inch, Mid 2014)
- Mac OS X 10.11.6 El Capitan
- PHPStorm 2017.3
- Vagrant 1.9.4
- CentOS 7.4.1708
- Drupal8.4.4
#ルーティング定義
ルーティング定義とは、そのURLパスにアクセスされたときにどのクラスを読み出して、そのクラスによってデータを返すかを決めるためのものです。
定義するには、まず、モジュール直下にルーティングファイルを作成します。
以下のYAMLファイル内では、次のようなルート定義を行います。
例)my_module.routing.yml
my_module.mymenu:
path: '/mymenu'
defaults:
_content: '\Drupal\my_module\Controller\ExampleController::mymenu'
_title: 'My module example'
requirements:
_permission: 'access content'
1行目では、my_moduleモジュールが読み出されたときのルート経路を定義します。 モジュール名 + ドット + ルート名です。
2行目のdefaults
には、コントローラー(_content
)とページタイトル(_title
)と指定します。
6行目のrequirements
には、ユーザーがページを表示するために必要なアクセス権限を指定します。
以上、基本的なパラメーターのみ紹介しましたが、それ以外の指定可能なパラメーターはDrupal.orgのStructure of routesが参考になります。
今回は時間がなくてコントローラーの中身までは紹介できませんが、実装サンプルを見たいときは何か適当なモジュールを見てみると参考になると思います。
#RouteMatch
RouteMatchは、Routing YAML(my_module.routing.yml)で定義されたルート名に関するルートオブジェクトを取得できます。
Drupal 7のmenu_get_object()
に相当するものというと伝わり易いかも知れません。
例)menu_get_objectによってnodeオブジェクトを取得する in Drupal 7
$node = menu_get_object('node');
次は、Drupal 8でRouteMatchを使った方法を紹介していきます。
例えば、コンテンツページにアクセスしたときのパスは/node/123
などになりますが、それに対応するルートオブジェクト/node/{node}
のパラメーター部分のデータを取得するには次のように書きます。
例)/node/{node} にマッチするルートオブジェクトを取得する
use Drupal\Core\Routing\RouteMatchInterface;
$node_id = \Drupal::routeMatch()->getRawParameter('node');
$node = \Drupal::routeMatch()->getParameter('node');
/node/{node}
のパスに対応するルート名は'entity.node.canonical'
なので、以下のような判定ロジックが書けます。
例)アクセスされたページがコンテンツかどうか判定する
$route_match = \Drupal::routeMatch();
if ($route_match->getRouteName() == 'entity.node.canonical') {
$node = $route_match->getParameter('node');
}
ちなみに/node/{node}
とルート名'entity.node.canonical'
の関連付けは、NodeRouteProviderクラスの中で、YAMLファイルを定義せずにPHPコード上で動的にルーティング定義(Dynamic routes)を行っています。
###ルート名'canonical'
とは?
'canonical'とは、コンテンツ表示する場合のルート名のことを指し、ノードエンティティの'canonical'といえば/node/{node}
になります。
そのほか、ユーザーエンティティの'canonical'といえば/user/{user}
、タクソノミーエンティティの場合は、/taxonomy/term/{taxonomy_term}
となります。
ユーザーエンティティのルート定義はUserRouteProviderで行われており、タクソノミーエンティティは、taxonomy.routing.ymlで定義されています。
#CurrentRouteMatch
RouteMatch()
から返されるオブジェクトと同じ内容ですが、こちらはcurrent_route_match
サービスからアクセス可能です。
現在読み込まれているルートに関するオブジェクトがセットされ、gettersメソッドを介してデータ取得を行います。
###1. *.service.ymlを準備する
current_route_match
サービスにアクセスするための準備として、まず、my_module.service.ymlを設置します。
例)webform.service.ymlより抜粋
services:
webform.entity_reference_manager:
class: Drupal\webform\WebformEntityReferenceManager
arguments: ['@current_route_match', '@current_user', '@user.data']
4行目のarguments
のところで'@current_route_match'
を指定します。
###2. コンストラクタにて、routeMatchオブジェクトを受け取る
次に、*.service.ymlのclass:
に該当するクラスを作成して、以下のようにコンストラクタでrouteMatchオブジェクトを受け取ります。
例)Drupal\webform\WebformEntityReferenceManager より抜粋
use Drupal\Core\Routing\RouteMatchInterface;
public function __construct(RouteMatchInterface $route_match, AccountInterface $current_user, UserDataInterface $user_data) {
$this->routeMatch = $route_match;
$this->currentUser = $current_user;
$this->userData = $user_data;
}
###3. 現在のルートに関する様々な情報を取得する
あとは$this->routeMatch
を通して、現在読み込まれているルートに関するデータ取得方法のいくつかのパターンです。
例)様々な情報を取得する
$route_name = $this->routeMatch->getRouteName();
if ($route_name == 'entity.node.canonical') {
$node = $this->routeMatch->getParameter('node');
}
if ($route_name == 'entity.entity_view_display.node.view_mode') {
$node_type = $this->routeMatch->getParameter('node_type');
}
if ($route_name == 'entity.taxonomy_term.canonical') {
$taxonomy_term = $this->routeMatch->getParameter('taxonomy_term');
}
if ($route_name == 'entity.user.canonical') {
$user = $this->routeMatch->getParameter('user');
}
#Url
RouteMatchはルート名を指定するだけでしたが、こちらはルート名に加えてURLクエリーやabsolute
などのオプションが用意されています。
###Url::fromRoute
まずは、fromRoute()
を使った基本的なデータ取得方法です。
例)Url::fromRoute()から情報を取得する
use Drupal\Core\Url;
$url_front = Url::fromRoute('<front>')->toString();
$url_current = Url::fromRoute('<current>')->toString();
$url_user = Url::fromRoute('user.page')->toString();
ちなみに、->toString()
を->toRenderable()
にするとレンダリング可能な配列形式が返されます。
###Url::fromUserInput
有効なDrupalルートかどうかに関わらず、指定したパスを元にURLを生成できます。
例)任意のパスを元にURLを作成する
use Drupal\Core\Link;
use Drupal\Core\Url;
//検索結果ページ用のURLを生成する
$keyword = 'search-word';
$options = ['absolute' => FALSE, 'query' => ['keyword' => $keyword]];
$url_search = Url::fromUserInput('/search', $options)->toString();
//aタグを生成する
$options = ['absolute' => TRUE, 'attributes' => ['target' => '_blank']];
$url_link = Link::fromTextAndUrl(t('It is a text link'), Url::fromUserInput('/your-path', $options))->toString();
###Url::fromUri
外部URLやbase:robots.txt
のようなルーティングされていないローカルURIの両方に対応するにはfromUri()
を使います。
$url_ext = Url::fromUri('https://www.drupal.org/node/add/')->toString();
$url_int = Url::fromUri('base:/node/add/', ['absolute' => TRUE])->toString();
$url_image = Url::fromUri(file_create_url('public://file.png'));
$node_alias = Url::fromUri('internal:/node/88')->toString();
ルーティングされているパスに関してもfromUri()
でオブジェクト生成可能ですが、基本的にDrupalルートを持つURLを取得する場合はfromRoute()
を使いましょう。
###$nodeを元に、URLオブジェクトを操作する
エンティティオブジェクトのtoUrl()
メソッドを実行するとURLオブジェクトを取得できます。
use Drupal\node\Entity\Node;
$node = Node::load(123);
$url = $node->toUrl('canonical'); //canonical or create-form or etc...
$path_alias = $url->toString();
$internal_path = $url->getInternalPath();
$path_alias_absolute = $url->setAbsolute()->toString();
toUrlのパラメーターには、エンティティに関連付けられているさまざまなルート名を指定できます。
toUrl()に指定可能なルート名と、それに対応するパス
canonical : '/node/{node}'
edit-form : '/node/{node}/edit'
create-form : '/node/add/{entity_subtype}'
version-history : '/node/{node}/revisions'
delete-form : '/node/{node}/delete'
toUrl()の引数に何も指定しなければ'canonical'がデフォルトでセットされます。
###Url in Twigテンプレート
twigテンプレート上で、Drupalルートに対応したURLを出力することもできます。
{{ url('<front>')}}
{{ url('<current>')}}
{{ url('paragraphs.type_add')}}
#メニューリンクを追加する
Drupal 7ではhook_menu()
でパスを定義してメニューに追加することができました。
そして、管理メニューにリンクを表示させたり、公開サイトのグローバルナビに表示させたりなどができました。
Drupal 8 にはhook_menu()
はありませんが、代わりにYAMLファイルを使用してメニューリンクを設定できます。
メニューリンク追加の準備として、まずはモジュール直下に my_module.links.menu.yml というファイルを設置します。
このYAMLファイルの中で、メニューリンクの定義と既存メニューの中のどこに登録するのかを定義します。
例)管理画面のサイト構成(Structure)にリンク追加する
my_module.mymenu:
title: Example Link
description: 'This is a example link'
parent: system.admin_structure
route_name: my_module.mymenu
4行目のparent
に指定するメニュー名によって、管理画面のどこにリンク追加されるかが決まりますが、Drupalコアに入っているsystem.links.menu.yml
ファイルの中で管理メニューのすべてが定義されているので参考になると思います。
5行目のroute_name
に指定するルート名はmy_module.routing.yml
で定義したものを指定します。
あとはキャッシュクリアすれば、メニューにリンク追加されたことを確認できるはずです。
#メニューリンクを書き換える
モジュール上で追加されたメニューは、後からテーマ側で書き換えることができます。
例)テーマ直下の.theme上でメニューを書き換える
use Drupal\Core\Url;
/**
* Implements hook_preprocess_menu().
*/
function mytheme_preprocess_menu(&$variables) {
if ($variables['is_admin'] && $variables['menu_name'] === 'admin') {
foreach ($variables['items'] as $key => &$item) {
if ($key === 'admin_toolbar_tools.help') {
$item['url'] = Url::fromRoute('system.admin');
$attributes = ['class' => ['toolbar-icon', 'toolbar-icon-admin-toolbar-tools-help']];
$item['url']->setOption('attributes', $attributes);
}
}
}
}
#参考ドキュメント
以上、ここで紹介したDrupal 8 ルーティングシステムは、Symfonyコンポーネントが基本になっており、同じ構造を持っていて仕組みがほぼ一緒なのでSymfony公式を読むと理解が深まるはずです。
当然ながらdrupal.orgのドキュメントでも詳しく解説されています。