Drupal 7 Render Arrays (и новый Render Example)

Примечание от переводчика: На Русский язык односложно перевести словосочетание Render Array, при этом сохранив смысл, довольно трудно. По смыслу это можно перевести как "массивы для генерации содержимого", что довольно громоздко, поэтому словосочетание будет оставлено без перевода.

Если вы похожи на меня, то вам неоднократно приходилось слышать словосочетание "render arrays" или "renderable arrays" снова и снова при разработке сайтов на Drupal 7, но скорее всего вы не до конца понимаете что это такое. Здесь представлен результат моих исследований на эту тему, во время разработки модуля Render Example для сборки примеров Examples for Developers.

Помните Form API? Или секцию content в объекте $node или $user в Druapl 6? Теперь, в Drupal 7, все хранится в подобной форме до самого последнего момента.

Что же означает "rendering"?

Rendering в терминах Drupal означает превращение структурированного массива в HTML код.

Что такое render array?

Render array - это классический структурированный массив Drupal который содержит данные (возможно с вложеностью) с указанием того как это должно отображаться (свойств вроде #type). К примеру страницу можно было бы описать следующим образом:

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

Для чего это было придумано?

До Drupal 7 мы могли изменять некоторые вещи, как например формы с помощью hook_form_alter(), но оставалось много других вещей, которые нужно было изменять в модуле или теме оформления но уже преобразованных в HTML до того как мы с ними что-нибудь могли сделать.

Теперь, в Drupal 7, в модуле или теме оформления, мы можем использовать hook_page_alter() чтобы изменить макет или содержимое страницы в самый последний момент. Это означает, что небольшой кусочек PHP кода в теме оформления может переместить блок в правую боковую панель на конкретной странице, если заказчик захочет этого.

Изменения (Altering)

Блоки и страницы можно изменять так же, как мы когда-то изменяли формы. Много других вещей тоже стали изменяемыми. С помощью hook_page_alter() мы можем делать такие вещи как:

function mymodule_page_alter(&$page) {
  // Перемещаем форму поиска в footer.
  $page['footer']['search_form'] = $page['sidebar_first']['search_form'];
  unset($page['sidebar_first']['search_form']);
 
  // Удаляем блок "Сделано на Drupal"
  unset($page['footer']['system_powered-by']);
}

Создание содержимого с помощью массивов

В отличии от предыдущих версий Drupal, любое содержимое создаваемое модулем должно быть в виде render array. Page callabck должен возвращать render array как и $block['content'] в hook_block_view(). Это позволяет вашему модулю и другим модулям видеть содержимое как данные на протяжении всего процесса генерации страницы. Это также ведет к повышению производительности, возможности кэширования и технике микро-рендеринга, которую мы рассмотрим далее, а так же дает нам простор для творчества, так-как теперь мы можем изменять все что угодно.

Теперь вместо функции возвращающей сгенерированный HTML, как это было в Drupal 6, page callback должен возвращать следующее:

return array(
  'first_para' => array(
    '#type' => 'markup',
    '#markup' => 'A first paragraph',
  ),
  'second_para' => array(
    array('first item', 'second item', 'third item'),
    '#theme' => 'item_list',
  ),
);

Примеры специальных типов массивов

Как и раньше, каждый "элемент" Drupal (смотри hook_element_info(), в Drupal 6 это hook_elements()) имеет свой тип. Доступны все типы предоставляемые ядром или установленными модулями. Если мы посмотрим system_element_info() мы увидим целую кучу уже объявленных типов таких как: page, form, html_tag, value, markup, link, fieldset и еще много других. Вы также можете объявлять свои типы и свойства на лету.

Вот один из примеров взятых из модуля Render Example сборки примеров Examples for Developers.

$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',
  ),
);

Примеры свойств

Множество свойств может быть применено к render array, так же вы можете создавать новые, если есть необходимость. Рассмотрим самые часто используемые.

Тут надо заметить, что все эти свойства задокументированы на странице Form API Reference потому что Form API также использует свойства Render API, который теперь присутствует в Drupal 7.

Свойство Описание
#type Тип элемента. Для каждого типа элемента есть свойства по умолчанию, поэтому во многих случаях это способ быстро задать набор предустановленных свойств, которые могут быть перечисленны в hook_element_info().
#markup Самое простое свойство, оно предоставляет HTML код элемента для типа #type == 'markup'
#prefix/#suffix Строка которая будет вставлена в начале или в конце сгенерированного элемента.
#pre_render Массив функций, которые могут изменить render array перед тем как он будет обработан, они могут изменять порядок, удалять части массива или, например, установить свойство #printed = TRUE, чтобы предотвратить повторную обработку этого элемента.
#post_render Массив функций для обработки уже сгенерированного HTML кода. #post_render функции принимают как сгенерированный HTML код элемента так и сам render array. Это примерно тоже самое что и #theme_wrappers за исключением того, что подсистема темизации здесь не используется
#theme Функция темизации (или массив функций, хотя в большинстве случаев это одна функция) которая может взять полный контроль над обработкой данного элемента массива, включая вложенности. Функция должна быть специально написана с учетом структуры элемента. Надо обратить внимание на то, что #theme в Drupal 7 и #theme в Drupal 6 это разные вещи.
#theme_wrappers Массив функций темизации, которые будут выполнены после того, как будут обработаны вложенные элементы помещенные в #childern. Обычно они используются для добавления HTML оберток к обработанным элементам, а также в тех случаях, когда вложенные элементы обрабатываются рекурсивно. Это свойство редко используется вместе с #theme.
#cache Позволяет закэшировать элемент и установить время действия этого кэша. Как только данный элемент будет сгенерирован, он будет помещен в кэш и будет храниться там до тех пор, пока не истечет его время действия. Кэширование в данном случае осуществляется с помощью стандартных функций Drupal cache_get() и cache_set(). В это свойство передается массив состоящий из следующих элементов:

  • keys - массив ключей из которых будет составлен ключ кэширования (cache key).
  • bin - имя кэш таблицы (например 'cache' или 'cache_page' и т.д.)
  • expire - время истечения кэша в формате Unix timestamp
  • granularity тип кэшированя: DRUPAL_CACHE_PER_PAGE, DRUPAL_CACHE_PER_ROLE или DRUPAL_CACHE_PER_USER

Надо отметить, что кэш элементов с истекшим сроком действия сбрасываться только после запуска cron.

Источники

Автор перевода: Денис Захаров
Оригинальная статья: http://randyfay.com/content/drupal-7-render-arrays-and-new-render-example

Поделись с друзьями:

Комментарии

Здравствуйте, Денис. Для вывода содержимого ноды в D7 в файле node.tpl.php используется ф-я render($content). Я так понимаю это и есть render array. Вопрос, как мне изменить в $content отображение какого-нибудь поля материала или как найти источник создания этого массива? Например поле field_image, как к нему добавить класс и разметку? Хотелось бы убрать обертку div для поля с изображением. В template_preprocess_node - я назначение разметки по-умолчанию не нашел.

Спасибо, я решил задачу через шаблон field.tpl.php. - это практически то, что надо - html шаблон полей. А вот теперь с добавлением классов не понятно. Сколько я не искал, так и не смог найти добавления классов для html тэгов самих полей. Вот для div-оберток варианты есть - хоть руками (в том же template_preprocess_field.), хоть модулями, а например, для того же field_image, в сам тэг класс никак не добавить. Можно конечно обойти и добавить div-ы в шаблоне, и в стиле сделать css-селекторы, но получается, что нужно плодить лишнюю разметку. Или все таки как-то можно?

Можно конечно перебить theme_image, но не понимаю чем не подходит template_preprocess_field и зачем добавлять класс именно к тэгу img? И даже не понятно зачем вообще добавлять новые классы, если цель этого всего только добавить CSS стили то в Drupal по моему и так достаточно оберток и классов (в том числе и уникальных для каждого поля) чтобы добавить стили конкретно для картинок этого поля.

template_preprocess_field - не подходит, т.к. в моем понимании, в нем можно менять только обертки полей. Знание, как добавлять новые классы, в т.ч. именно тегам полей мне нужно на случай использования уже сверстанного дизайна с готовыми стилями, что бы меньше влазить в сам CSS. За совет с theme_image - спасибо.

Если вы применяете сверстанный дизайн то лучше адаптировать CSS под структуру Drupal чем навешивать еще тонну лишних классов. Дополнительная работа конечно но оно того стоит. CSS должен быть написан под структуру а не структура подогнана под CSS. А еще лучше использовать compass.

Очередной раз спасибо за советы)

Здравствуйте. Есть отдельный тип материалов, одно из полей - выпадающий список из нескольких заранее заготовленных вариантов, в настройках поля стоит галка "Custom keys" - в БД хранятся варианты в виде "value_1", "value_2" и т. д., а в поле выбираются, а затем в ноде выводятся уже понятные человеку значения. На данный момент варианты значений - это сокращения - реально там должен выводиться большой кусок текста с очень похожим началом, но если в варианты значений ввести эти нужные куски текста, то при создании/редактировании ноды невозможно точно определить что там за вариант в выпадающем списке. Опять же - есть вьюха, в которой собираются ноды этого типа материалов и вывод полноценных кусков текста вместо сокращений там неудобен. На Drupal.ru всё, что мне подсказали - использовать #pre_render - не могу найти пример его использования для таких целей - не могли бы подсказать?

Если нужно выводить сокращенную и развернутую версию этого значения то я бы не использовал простое поле со списком значений. Лучше сделать поле типа "Ссылка на термин", тогда можно имя термина использовать как короткое значение а Body как развернутое. Второй вариант использовать модуль Paragraphs, сделать бандл в котором будет два поля "Короткое значение" и "Развернутое значение".

Проблема в том, что нод уже тысячи.....