Как стать автором
Обновить

Разработка для Drupal 7 с помощью новой концепции сущностей (Entity)

Время на прочтение 10 мин
Количество просмотров 16K
Концепция сущностей (Entity), которая будет рассматриваться в данной статье является одной из новинок, представленных в Drupal 7. Для того чтобы осознать всю новизну предлагаемого подхода, следует сделать небольшой экскурс в историю и вспомнить, как все было в Drupal 6.

Что такое, с чем едят?


Все модули, написанные под Drupal условно можно разделить на две категории. Первая — модули, которые фактически не объявляют новых типов данных и работают с данными уже где-то определенными и хранимыми. Например модуль lightbox2 позволяет изменить представление изображений на сайте, а модуль devel предоставляет различные утилиты, полезные разработчику. И хотя модуль devel сохраняет некоторую информацию в базе данных (время выполнения sql запросов, например) фактически это нельзя назвать полноценной моделью данных.
Вторая категория — это модули которые позволяют создавать новые типы объектов, определяют новые модели данных. К таким модулям, например относятся модуль webform (позволяет создавать формы опросов), а также входящий в ядро модуль user (создавать (регистрировать) новых пользователей, выполнять с ними различные операции).

Когда у Вас, как у разработчика, модуля появляется необходимость в объявлении нового типа данных, у Вас есть два варианта. Первый вариант — определить Ваш тип данных как новый тип материала (node type). Второй вариант — создавать все с нуля. У обоих подходов есть свои преимущества и недостатки. Понятно, что второй вариант является более гибким и не накладывает на Вас практически никаких ограничений в реализации. Первый же вариант является более удобным и быстрым в исполнении за счет готовых средств, предоставляемых Node API.

Однако, не только из-за скорости и удобства разработки, большинство серьезных модулей, созданных под Drupal 6 и реализующих некоторую новую модель данных, объявляют ее как node type. Важной особенностью является то, что все материалы независимо от типа имеют некую общую структуру и общую схему внешнего взаимодействия. Такой подход позволяет написать один модуль для расширения функциональности всех типов материала существующих в системе. По сути, Вы можете даже не знать о существовании некоторого типа материала и при этом создать модуль, который будет влиять на него и сможет им оперировать. Яркими примерами являются модули CCK и Views.

Казалось бы, все прекрасно, но остается одна проблема. Как было указано ранее, реализация через node type имеет некоторые ограничения, которые, в ряде случаев, делают неудобным или невозможным использование подобного подхода. В частности, сложно себе представить, чтобы пользователи или комментарии в Drupal 6 были реализованы как типы содержимого. Конечно, здесь сколь угодно долго можно разводить полемику на тему возможности или невозможности реализации пользователей в виде типов материалов, однако изящным и правильным такой подход в любом случае не назовешь.

Что же поменялось с приходом Drupal 7? Фактически был создан новый уровень абстракции. И имя ему — Entity (сущность). Фактически, Entity является более низким уровнем абстракции, чем node в Drupal 6. Сразу же оговорюсь, что в Drupal 7 ноды никуда не исчезли, однако, теперь, являются надстройкой над Entity. Самое же прекрасное то, что пользователи, комментарии и многое другое в Drupal 7 — тоже Entity. Ощутить разницу Вы можете уже «из коробки». Например, раньше, используя модуль CCK, вы могли добавить новые поля только к типам материалов. В Drupal 7 вы можете проделать то же самое с пользователями, комментариями и даже тегами таксономии. Все потому, что теги, материалы, комментарии и пользователи — все являются подклассами Entity.

Вскрытие покажет


Перед тем как начать обсуждение на тему внутреннего устройства системы сущностей, следует напомнить о таком чудесном ресурсе как api.drupal.org, где Вы всегда можете ознакомиться со всеми интерфейсами, классами и методами, рассматриваемыми далее, на языке Шекспира.

Итак, пришло время посмотреть, как же все это устроено. Несмотря на то, что (как будет видно позднее) в Drupal 7 стало значительно активнее использоваться Объектно Ориентированное Программирование, для того, чтобы сообщить системе о том, что наш модуль реализует новую сущность, мы должны будем реализовать хук. Интересующий нас хук — hook_entity_info(). Из него мы должны возвратить ассоциативный массив, ключами которого будут названия сущностей, которые мы будем реализовывать. В свою очередь каждой сущности будет соответствовать массив ее параметров. Ниже приведен перечень параметров, которые мы можем указать:
  • label: Так называемое «human-readable» имя типа нашей сущности. Должно быть обернуто в t() если Вы хотите чтобы лэйбл был переводим.
  • controller class: Имя класса-контрОллера который будет отвечать за загрузку данных. Класс должен реализовывать интерфейс DrupalEntityControllerInterface. Если не указать данный параметр, в качестве контроллера будет использован контроллер по умолчанию — DrupalDefaultEntityController (Контроллеры сущностей будут рассмотрены далее по ходу статьи).
  • base table: Базовая таблица в БД для нашей сущности. Формально параметр является опциональным, т.к. используется классом DrupalDefaultEntityController и унаследованными от него. Т.е. Вы можете по своему реализовать интерфейс DrupalEntityControllerInterface и получать данные не из таблицы БД, а хоть с марсианского спутника, но если в качестве контроллера Вы решили использовать DrupalDefaultEntityController или производный от него — извольте данный параметр указать. Забегая вперед скажу, что в большинстве случаев контроллер имеет смысл унаследовать от DrupalDefaultEntityController или от еще более высокоуровневого класса, как например EntityAPIController (будет рассмотрен позднее).
  • revision table: Аналогично предыдущему, но в данном случае таблица ревизий. Здесь справедливо прослеживается аналогия с нодами. Если различных ревизий Ваша сущность не предполагает, параметр можно опустить.
  • static cache: Еще один параметр используемый DrupalDefaultEntityController. Включает статическое кэширование сущностей во время запроса страницы. Т.е. в данном случае речь идет не о кэшировании между запросами (когда данные кэшируются на некоторый промежуток времени и отдаются из кэша при каждом новом запросе вплоть до времени истечения кэша), а о кэшировании в течении одного запроса. Таким образом, если походу запроса не планируется изменять сущность, а потом заново загружать ее, имеет смысл установить в TRUE (или просто опустить параметр, так как по умолчанию он итак TRUE).
  • field cache: Здесь речь уже идет о постоянном кэше в отношении полей (Field API), которые, как будет показано позже, можно прикреплять к сущностям. Официальная документация говорит о том, что устанавливать данный параметр в FALSE имеет смысл только в том случае, если постоянному кэшированию подвергается целиком вся сущность. Иными словами, если не знаете точно, зачем оно Вам надо, лучше опустить этот параметр и помнить, что по умолчанию он установлен в TRUE.
  • load hook: Еще один параметр, используемый в DrupalDefaultEntityController. Вызывается функцией DrupalDefaultEntityController:attachLoad и позволяет другим модулям прикреплять свою информацию к сущности. Функция attach_load в любом случае вызовет hook_entity_load для всех сущностей, указанный же load hook позволит вызвать отдельный хук для Вашей сущности (как например hook_node_load в модуле node).
  • uri callback: В качестве аргумента принимает саму сущность. Возвращает uri компоненты сущности (такие как path и options) из которых впоследствии с помощью функции uri() может быть получен адрес сущности. Существуют разные подходы к определению этой функции. В модуле Entity API в частности существует функция entity_class_uri которая просто вызывает из себя функцию $entity->uri() переданной ей сущности, давая, таким образом, возможность самой сущности определять свой адрес.
  • label callback: Опциональный параметр, используемый для получения названия сущности. В качестве аргументов принимает саму сущность и тип сущности. В случае если название сущности эквивалентно некоторому параметры этой сущности (как например название ноды эквивалентно полю title) лучше использовать 'label' — элемент 'entity keys' (будет описан далее). Если же название формируется как-то по-особому (например, как комбинация некоторых полей), можно установить его использовавав 'label callback'.
  • fieldable: Как говорится «and this is the magick». Установив в TRUE и при условии, что Ваш контроллер реализует данную функциональность (или унаследован от DrupalDefaultEntityController) Вы делаете Вашу сущность расширяемой с помощью Drupal Field API (Аналог CCK для Drupal 6 в Drupal 7 интегрированный в ядро). Т.е. как и к пользователям, типам нод, тегам и многому другому к Вашей сущности можно будет прикреплять различные поля.
  • translation: Не вдаваясь в подробности — это поле дает возможность делать Вашу сущность и прикрепленные к ней поля переводимыми. Если не пусто, в качестве значения указывается ассоциативный массив, где ключами являются имена модулей предоставляющих возможность перевода, а значениями являются информационные структуры любого вида необходимые для перевода.
  • entity keys: Параметр используемый Field API. Ассоциативный массив который может включать в себя следующие элементы:
  • — id: Поле сущности, содержащее уникальный первичный идентификатор сущности. Обязательный параметр если fieldable установлено в TRUE. Значение должно быть положительным целым числом.
  • — revision: Имя параметра содержащего ID ревизии. Field API полагает, что все Entity внутри типа имеют уникальный ID ревизий. Если Ваша сущность не реализует модель версий (ревизий), то параметр можно опустить.
  • — bundle: Параметр, в котором указано к какому бандлу (подтипу) принадлежит сущность. Для каждого бандла устанавливается свой набор полей. Здесь хорошим примером будут типы нод, где для каждого типа можно указать свои поля (не смотря на то, что в итоге, все типы являются одной сущностью). Если Вы не собираетесь реализовывать подобные возможности в Вашем типе сущности (не планируете наличие разных бандлов и разных наборов полей для них), можете опустить этот параметр. Тогда, в качестве параметра автоматически будет использовано имя типа сущности.
  • — label: Параметр, в котором указано название сущности. Если заголовок должен формироваться динамически, следует воспользоваться параметром label callback описанным ранее.
  • bundles: Собственно определяет набор бандлов (подтипов) типа нашей сущности. Ассоциативный массив ключами которого являются машинные названия бандлов (в том виде в котором они хранятся в поле 'bundle' определенном в параметре 'entity keys'). Названиям бандлов в свою очередь сопоставлены ассоциативные массивы со следующими ключами:
  • — label: «human-readable» имя бандла. Как обычно оборачиваем в t() если хотим, чтобы название можно было переводить.
  • — uri callback: Тоже самое, что 'uri callback' для типа сущности, только для бандла. Если указано и то и то, то будет использован callback для бандла.
  • — admin: Массив с информацией, которая позволит Field API встроить свои страницы в страницы администрирования сущности.
  • — path: Путь к странице администрирования бандла. Если путь включает в себя заглушку (placeholder), потребуется также указать значения для 'bundle argument' и 'real path' (будут рассмотрены ниже).
  • — — bundle argument: Положение заглушки в пути. Принцип тот же что и при работе с путями меню.
  • — — real path: Путь к странице администрирования бандла без заглушки. Будет использован для генерации ссылок.
  • — — access callback: Также как в hook_menu(). Если оставить пустым будет использован user_access().
  • — — access arguments: Также как в hook_menu().
  • view modes: Различные способы (виды) отображения сущности. Хорошим примером являются full и teaser в node. Ассоциативный массив, в котором в качестве ключей используются машинные имена видов. По умолчанию у каждой сущности есть вид default. Все остальные виды могут унаследовать от него значения по умолчанию, если будет установлено в TRUE значение 'custom settings' параметров вида. Каждый вид представляет собой массив со следующими ключами:
  • — label: «human-readable» имя вида. Как обычно оборачиваем в t() если хотим, чтобы название можно было переводить.
  • — custom settings: Строго говоря, данный параметр относится преимущественно к полям. Если установлен в FALSE (значение по умолчанию), то изначально вид унаследует настройки отображения полей от вида default. Потом настройки можно будет изменить через Field UI.

Вот собственно и все параметры, которые можно указать для типа сущности. Надо отметить, что надстройки над DrupalDefaultEntityController, такие как, например EntityAPIController могут требовать\использовать дополнительные параметры. EntityAPIController например использует параметр 'entity class'. Такая возможность достигается благодаря функции entity_get_info($entity_type = NULL), которая позволяет получить данные о нужном типе сущности.

Контроллер сущности

Теперь немного подробнее остановимся на контроллере сущности, который определяется в параметре 'controller class'. По сути, контроллер — это способ управления данными Вашей сущности. Что мне нравится в ООП, так это то, что оно позволяет не только сделать сам код эффективнее, но и упрощает понимание процессов внутри него происходящих. Для того, чтобы понять как работает контроллер начнем рассмотрение с самого низа. По определению, в параметре 'controller class' должен быть указан класс реализующий интерфейс DrupalEntityControllerInterface. И не смотря на кажущуюся сложность концепции сущностей, данный интерфейс на удивление минималистичен. Он предполагает наличие в нашем классе имплементации всего лишь трех методов:
DrupalEntityControllerInterface::load — загружает одну или несколько сущностей. В качестве первого аргумента принимает массив ID сущностей, которые требуется загрузить. В качестве второго аргумента принимает некий массив условий в форме 'поле'=>'значение'.
DrupalEntityControllerInterface::resetCache — очищает (сбрасывает) статический кэш для всех сущностей (если аргумент не задан) или для выбранного набора сущностей (если в качестве аргумента передан соответствующий массив id)
DrupalEntityControllerInterface::__construct — Конструктор. В качестве единственного аргумента принимает тип сущности для которой нужно создать экземпляр контроллера. Таким образом, зная тип сущности для которой создается контроллер, можно получить параметры данного типа с помощью entity_get_info($entity_type) и кастомизировать создаваемый экземпляр контроллера.

Вот собственно и все методы, которые должны быть реализованы в Вашем классе, чтобы он имел полное право называться контроллером сущности. Тем не менее, в большинстве случаев имеет смысл не реализовывать интерфейс с нуля, а унаследовать ваш класс контроллера от класса DrupalDefaultEntityController который мы сейчас и рассмотрим.
Что же нам дает DrupalDefaultEntityController?
  1. Возможность загружать любые сущности независимо от типа из таблиц базы данных указанных в параметрах 'base table' и 'revision table' типа сущности.
  2. Прикрепляет к нашей сущности поля с помощью Field API, если для типа сущности установлен в TRUE параметр fieldable.
  3. Возможность загружать сущности эффективно. В классе реализована поддержка статического кэширования. (Хотя по сути, метод resetCache интерфейса DrupalEntityControllerInterface намекает на то, что статическое кэширование должно быть реализовано в любом контроллере сущностей).

Таким образом, DrupalDefaultEntityController не ставит целью реализацию как таковой функциональности для конечного пользователя, он лишь дает некоторую точку отсчета для разработчика. Более того, в тех случаях, когда Ваша сущность должна так или иначе реализовывать концепцию CRUD (Create Read Update Delete) имеет смысл унаследовать Ваш контроллер от еще более высокоуровневого класса — EntityAPIController, представленном в модуле Entity API. Однако рассмотрение принципов работы с Entity API выходит за рамки данной статьи и будет освящено в другой раз.
Теги:
Хабы:
+35
Комментарии 16
Комментарии Комментарии 16

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн
PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн