8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

DrupalAdvent Calendar 2017

Day 5

Drupal 8のルーティングシステム(メニューシステム)の解説とサンプルコード

Last updated at Posted at 2018-01-01

Drupal 8 に実装されたルーティングシステムは、Symfonyコンポーネントを拡張したものであり、Drupal 7まではルーティングを処理するときにhook_menu()を使っていましたが完全に置き換えられました。

drupal8_ symfony.png

今回は、新しいルーティングシステムを理解するための最低限の説明と、モジュール実装をする上でたびたび使用することになる、RouteMatchCurrentRouteMatchUrlの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のドキュメントでも詳しく解説されています。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?