Drupal 7 のレンダリング

~ Render Arrays in Drupal 7 和訳 ~

「レンダリング配列」はDrupal7より導入されたページ構築用の配列です。ページ(もしくはその一部)の構築に使用されるデータは、テーマシステムにおいて実際にレンダリングが行われるまで、構造化された配列の形を維持します。これはページのレイアウトやコンテンツを変更する際に、多大な柔軟性をもたらすだけでなく、将来的にはパフォーマンスの向上も見込まれています。

レンダリングとは

Drupalにおけるレンダリングとは、レンダリング配列をHTMLに起こす事をいいます。

レンダリング配列とは

レンダリング配列は古典的なDrupalの構造化された配列で、それ自体がどのようにレンダリングされるかといった情報を含むものです。たとえばページのレンダリング配列は次のようになります。

<?php
$page = array(
  '#show_messages' => TRUE,
  '#theme' => 'page',
  '#type' => 'page',
  'content' => array(
    'system_main' => array(...),
    'another_block' => array(...),
    '#sorted' => TRUE,
  ),
  'sidebar_first' => array(
    ...
  ),
  'footer' => array(
    ...
  ),
  ...
);
?>

導入の経緯

Drupal7より前のバージョンでは、例えばフォームは、hook_form_alter() などのフックを使ってその構造をあとから変更することが出来ましたが、ほかの多くのものは、モジュールやテーマで変更する必要がある場合においても、その時点で既にHTMLにレンダリングされてしまっていました。

Drupal7以降では、モジュールやテーマでhook_page_alter() を使うことにより、ページレイアウトやコンテンツの変更を処理のかなり最後の段階で行うことが出来ます。これは例えば、1つのページの右サイドにブロックを追加したりといったようなことが、テーマの中にちょっとしたPHPコードを追加することで可能となることを意味します。

レンダリング配列の変更操作

これまでページ構築処理の途中でフォームの変更操作が可能だったように、ブロックとページも変更が可能です。そのほかのタイプのものも同様です。hook_page_alter()を使ってモジュールとテーマは次のようなことが出来ます。

<?php
function mymodule_page_alter(&$page) {
  // 検索フォームをフッターに移動
  $page['footer']['search_form'] = $page['sidebar_first']['search_form'];
  unset($page['sidebar_first']['search_form']);
  
  // 「powered by Drupal」ブロックを撤去。
  unset($page['footer']['system_powered-by']);
}
?>

レンダリング配列とエレメント

モジュールはhook_element_info()を実装することによって、デフォルトのレンダリング配列をもつ「エレメント」を定義し登録することが出来ます。エレメントは「#type」やその他のプロパティを持つ、あらかじめ登録されたレンダリング配列です。配列をレンダリングする際には、エレメントの「#type」の値をもとに、hook_element_info()からデフォルトのプロパティがロードされます。

コンテンツをレンダリング配列として生成する

Drupalの以前のバージョンとは異なり、モジュールはコンテンツを生成する際は常に、レンダリング配列の形式にする必要があります。ページコールバックも例外ではありません。hook_block_view() の $block['content'] のように記述することが求められます。これにより、ページ生成のより多くのプロセスにおいて、他のモジュールがそれをデータとして扱うことが可能になります。

Drupal6のメソッドでは、ページコールバックはHTMLとして受け取りレンダリングしていましたが、新しい方法では次のようになります。: 

<?php
function mymodule_menu() {
  $items['mypage-html'] = array(
    'title' => 'My page with HTML-style function',
    'page callback' => 'mymodule_html_page',
    'access callback' => TRUE,
  );

  $items['mypage-ra'] = array(
    'title' => 'My page with render array function',
    'page callback' => 'mymodule_ra_page',
    'access callback' => TRUE,
  );

  return $items;
}

// ページ生成の旧式のメソッド(HTMLを返す。これでも動作します)。
function mymodule_html_page {
  $output = '<p>A paragraph about some stuff...</p>';
  $output .= '<ul><li>first item</li><li>second item</li><li>third item</li></ul>';
  return $output;
}

// レンダリング配列として生成する新しいメソッド。
function mymodule_ra_page {
  $output =  array(
    'first_para' => array(
      '#type' => 'markup',
      '#markup' => '<p>A paragraph about some stuff...</p>',
    ),
    'second_para' => array(
      '#items' => array('first item', 'second item', 'third item'),
      '#theme' => 'item_list',
    ),
  );
  return $output;
}
?>

要素タイプ

このように、Drupalにはあらゆる「エレメント」(hook_element_info()を参照。Drupal6ではhook_elements())があり、これらは「要素タイプ」として定義されます。コアがエレメントとして提供するものや、モジュールが提供するものがあります。system_element_info() を見てみると、ページ、フォーム、htmlタグ、値、マークアップ、リンク、フィールドセットなど、「要素タイプ」として様々なエレメントの設定が定義されています。規則により、これらの要素タイプで使用される「#-プロパティ」(「#」で始まるプロパティ)についての説明は、それぞれの要素タイプの使用するテーマ関数のドキュメントコメントに記載されます。たとえば #type => 'html_tag' と定義されたエレメントのプロパティの説明は、theme_html_tag() のドキュメントコメントに記載されています。開発者はまた独自の要素タイプ、プロパティを定義することが出来ます。

これはExamples Project'sのレンダリングの例です。

<?php
$demos = array(
  t('Super simple #markup')  => array(
    '#markup' => t('Some basic text in a #markup (shows basic markup and how it is rendered)'),
  ),

  'prefix_suffix' => array(
    '#markup' => t('This one adds a prefix and suffix, which put a div around the item'),
    '#prefix' => '<div><br/>(prefix)<br/>',
    '#suffix' => '<br/>(suffix)</div>',
  ),

  'theme for an element' => array(
    'child' => array(
      t('This is some text that should be put together'),
      t('This is some more text that we need'),
    ),
    '#separator' => ' | ',  // Made up for this theme function.
    '#theme' => 'render_example_aggregate',
  ),
);
?>

プロパティの例

レンダリング配列には必要なだけ非常に多くのプロパティを設定できます。ここでは最も使用頻度の高いものについて説明します。

これらの多くのドキュメントはForm API Referenceにあります。これはフォームAPIは常にレンダリングAPIを必要とすることと、レンダリングAPIはこれまで主にフォームのレンダリングに使用されてきたことに由来します。

プロパティ説明
#typeエレメントのタイプです。hook_element_info()で設定されたものが各タイプのデフォルトのプロパティとなります。
#markupもっともシンプルなプロパティです。これは単に #type == 'markup' のマークアップ文字列を提供します。
#prefix/#suffixレンダリングされる際に接頭辞、接尾語として扱われる文字列です。
#pre_renderレンダリング配列が実行される前に、その構造を変更するための関数の配列です。アレンジの変更やパーツの撤去、 #printed = TRUE をセットしてレンダリング済みとするなどの操作が可能です。
#post_renderレンダリング後に生成されたHTMLを操作するための関数の配列です。これらの関数は生成されたHTMLと、元のレンダリング関数の両方を受け取ります。テーマ化サブシステムが使用されないことを除けば、それは #theme_wrappers とよく似ています。
#theme

レンダリング配列が自身のレンダリングを行うテーマフックです。(通常は1つですが、複数の関数の配列の場合もあります)。It has predetermined knowledge of the structure of the element. Drupal7の #theme と、Drupal6の #theme は実際には別物ですので、Drupal6の #theme について考えるのはここではやめましょう。基本的には、 '#theme' = 'function_name' は theme_function_name() をコールし、'#var_name' = $value のように表されるレンダリング配列の他の値は、テーマ関数に引数として渡されます。

すべてのデフォルトのテーマフックはここで見ることができます。http://api.drupal.org/api/drupal/modules--system--theme.api.php/group/themeable/7

#theme_wrappers子孫の配列がレンダリングされ配置された後に、レンダリング配列に追加の操作を可能にするテーマフックの配列です。通常はレンダリングされた子孫にHTMLラッパーを追加するのに使用されます。一般的に子孫が自身のテーマ情報に基づいて再帰的にレンダリングされる際に使用されます。#theme と一緒に使用されることはあまりありません。
#cache

レンダリング配列がキャッシュ可能かどうか、その有効期限などを指定します。レンダリング配列は一度レンダリングされると、その有効期限までは再度レンダリングされることはありません。キャッシュシステムはDrupalのcache_get()と、cache_set()が使われます。これは次のような配列です。

  • 'keys' => キャッシュID 生成の材料となるキーの配列です。
  • 'bin' => キャッシュを格納するテーブル名です。( 'cache' 、'cache_page' など)
  • 'expire' => キャッシュの有効期限をあらわす UNIX タイムスタンプです。
  • 'granularity' => キャッシュのタイプを示すビットマスクです。 これは DRUPAL_CACHE_PER_PAGE, DRUPAL_CACHE_PER_ROLE, or DRUPAL_CACHE_PER_USER といった定数を使ってください。

有効期限が指定されていても、クロンが実行されるまでは期限切れにならないことに注意してください。

すべての「エレメント」は自身でプロパティを定義できるので、実際にはこのほかにもたくさんあります。これらの多くはForm API Reference handbook pageに詳細が記載されています。

Resources

コア: 
Drupal7