API хуков и разработка модулей

Перевел следующую часть цикла «20 API за 20 дней». В данной статье рассмотрен API хуков Друпала. Рассказано, что такое хуки и зачем они нужны. Кратко перечислены популярные хуки, показано, как реализовать хук в своем модуле. В конце статьи разъясняется, как создавать собственные хуки.

Оригинальная статья: The Hooks API and Custom Modules.
Автор: Мартин Валасек.

Далее — перевод.

API хуков и разработка модулей

Спросите кого-нибудь, что такое «хук» — по ответу сможете многое понять о человеке. Это слово используется в отношении пиратов, музыки, персонажей в историях у костра, воздухоплавания и т.д. (hook в переводе означает крюк — прим. переводчика) Так или иначе, это слово имеет особенный смысл в программировании и служит важной структурной концепцией большинства современных языков программирования и фреймворков.

Почти всем разработчикам под Друпал хорошо известна его система хуков. Хуки — это пусковые механизмы, срабатывающие, когда происходят особые события в процессе генерации страницы. Они способны модифицировать данные, когда их обрабатывают, и совершать другие действия при определенных условиях. Некоторые из обычных хуков срабатывают, когда загружается нода, генерируется форма или кто-то сохраняет пользовательские данные. В этой части цикла «20 API на 20 дней» рассматривается сущность хуков, и как они функционируют, являясь основой большинства модулей.

Что делает API хуков?

Для тех, кто знаком с объектно-ориентированным программированием, хуки концептуально связаны с основной идеей этой методики. Они представляют стандартные способы взаимодействия со структурами данных, которые действуют независимо от отдельных обратных вызовов. В отличие от традиционных ООП-конструкций, хуки не привязаны к отдельным объектам, они привязаны к процедурным точкам в процессе генерации страницы. Также многие хуки зависят от состояния и выполняют разные операции в зависимости от того, что именно произошло, когда хук был вызван.

Для тех, кто достаточно разумен, чтобы избежать холивара на тему «Что такое ООП», вот что действительно нужно знать о работе API хуков Друпала:

  • Хук — это PHP-функция, реализованная в модуле, она исполняет код в ответ на события или условия, произошедшие во время генерации страницы.
  • Хуки позволяют модулям взаимодействовать с другими модулями, включая модули из ядра Друпала.
  • API хуков задает стандарт именования для хуков, что позволяет разработчикам реализовывать хуки в модулях без нужны во всю ширь заниматься программированием.
  • API хуков позволяет разработчикам создавать собственные хуки, к которым могут получить доступ другие модули, с помощью той же техники, что и в ядре Друпала.
  • Многие хуки зависят от состояния и действуют по-разному в зависимости от того, что Друпал делал, когда сработал хук.

Важно понимать, что эта модель позволяет разработчикам модифицировать функционирование Друпала множеством мелких способов, вместо того, чтобы расписывать весь код в одном месте. Если API хуков под рукой — значит кто-то может добавить к Друпалу модуль и расширить функционал системы без необходимости переписывать какой-либо код. Именно это мы имеем в виду, когда говорим, что Друпал — модульный.

Теперь давайте посмотрим с другой стороны. В ядре Друпала есть хук под названием hook_nodeapi. Он запускается всякий раз, когда на сайте загружается нода (и в других случаях, мы в них еще покопаемся). Загрузка ноды — это то, что мы назовем событием, оно заставляет hook_nodeapi просмотреть все установленные модули и найти те, где зарегистрирован hook_nodeapi. Как домино, Друпал будет запускать код, содержащийся в каждой функции hook_nodeapi каждого модуля, и в результате получится полностью сформированная нода, готовая к показу пользователям.

К примеру, модуль location использует hook_nodeapi, чтобы добавить информацию об адресах к нодам, когда они загружаются. Он также сохраняет информацию об адресах, при сохранении ноды. Именно это мы имеем в виду, когда говорим, что многие хуки зависят от состояния: они знают, сохраняете ли вы что-то, или загружаете, или удаляете и т.п.

Несмотря на важность предмета, иногда документации о многих хуках Друпала оказывается ужасно мало и это превращает изучение хуков в проблему. Для большинства людей путь к пониманию хуков лежит через исследования чужого кода и самостоятельную реализацию хуков в своих проектах. Но есть и те немногие, кто по-настоящему понимает, что делают хуки в ядре Друпала и в чем смысл их взаимодействий. Когда мы в Trellon нанимаем разработчиков, то задаем им серию вопросов о хуках с возрастающей сложностью. За шесть лет собеседований никто ни разу не дал удовлетворительного ответа о том, что делает hook_comment — несмотря на то, что это достаточно очевидно, да и хук существует уже давно. Это подтверждает предыдущее утверждение о том, как люди изучают хуки: у них нет особых причин реализовывать hook_comment и поэтому редко кто хоть что-то о нем знает.

Как распознать хук на природе

У хуков есть стандарт именования, который легко распознать, если знать как он выглядит.

Для регистрации хука разработчики используют PHP-функцию со специальным именем. Это специальное имя составляется так:

  • имя модуля;
  • подчеркивание;
  • имя хука.

Например, если у кого-нибудь есть модуль foo и нужно реализовать hook_bar, нужно поместить в модуль foo функцию под названием foo_bar. Так что реализовать хук не очень-то и сложно.

Передача подходящих данных, однако — другой вопрос. Каждый хук — это PHP-функция, у которой есть аргументы для передачи данных. Многие хуки вдобавок передают переменные как ссылки, чтобы можно было модифицировать данные перед возвратом их Друпалу для дальнейшей обработки.

Типичная ошибка разработчика модулей — передача неправильного набора аргументов. Подсказка: очень удобно держать api.drupal.org открытым в отдельном окне и копировать прямо оттуда примеры из документации по хуку. Это может сэкономить кучу времени начинающим разработчикам.

Есть ряд хуков, используемых чаще других. Они — ключевые в создании модулей. Изучение реализаций этих хуков в любом модуле расскажет вам немало о том, что модуль делает и как. В их ряд входят:

  • hook_help — создает текст подсказки, связанный с вашим модулем.
  • hook_perm — создает набор разрешений для модуля.
  • hook_menu — определяет пункты меню и функции обратного вызова для генерации страниц.
  • hook_menu_alter — позволяет изменять данные, сохраняемые в таблицу {menu_router} после вызова хуков hook_menu.
  • hook_nodeapi — срабатывает при работе с нодами, определенными в других модулях.
  • hook_block — создает блок или набор блоков.
  • hook_cron — используется для выполнения тех или иных действий при запуске крона.
  • hook_user — срабатвает при совершении действий с учетными записями пользователей.
  • hook_form — генерирует форму редактирования ноды.
  • hook_form_alter — осуществляет изменения формы перед ее отображением.
  • hook_form_FORM_ID_alter — позволяет вместо глобальных изменений изменять каждую форму отдельно.
  • hook_theme — регистрирует реализации темизирующих функций модуля (или темы).
  • hook_link — задает внутренние ссылки Друпала.
  • hook_link_alter — производит изменения перед отображением ссылок ноды.

По адресу http://api.drupal.org/api/group/hooks доступен полный список хуков ядра Друпала. Для продвинутых разработчиков также очень важно понимать взаимодействия некоторых хуков. Например, hook_db_rewrite_sql — используется достаточно часто и, в общем, реализуется для поддержки ограничений доступа к содержимому сайта. Это один из тех хуков, за которыми нужно приглядывать, так как модули, реализующие его, могут оказаться несовместимыми друг с другом. Например, и Organic Groups, и Domain Access используют hook_db_rewrite_sql для управления доступом к содержимому, и есть ситуации, в которых эти модули не могут работать вместе. Именно это мы имеем в виду под «взаимодействиями» — то, что два установленных модуля, создающих ограничения доступа, могут серьезно запутать работу.

Как реализовать хук

Реализовать хук довольно легко — нужно просто дать ему правильное имя, передать правильные аргументы и запустить в нем необходимый вам код. Некоторые модули могут собрать для вас начальный набор хуков (мы не хотим их здесь рассматривать, погуглите — найдете).

Вот несколько примеров хуков в действии.

Пример 1: Изменение ссылок ноды перед их отображением:

/**
* Реализация hook_link_alter().
*/
function mymodulename_link_alter(&$links, $node) {
  global $user;
  // Модифицируем ссылку forward для нод текущего пользователя
  if ($user->uid == $node->uid && isset($links['forward_links'])) {
    $links['forward_links']['href'] = 'resume/'. $node->nid .'/send';
    $links['forward_links']['query'] = '';
  }
}

Пример 2: реализация функции обратного вызова для генерации страницы в модуле Contact:

/**
* Реализация hook_menu().
*/
function contact_menu() {
  $items['contact'] = array(
    'title' => 'Contact',
    'page callback' => 'contact_site_page',
    'access arguments' => array('access site-wide contact form'),
    'type' => MENU_SUGGESTED_ITEM,
    'file' => 'contact.pages.inc',
  );
  return $items;
}
Создание собственных хуков

Вы можете дать другим разработчикам возможность расширять функционал вашего модуля, с помощью создания в нем собственных хуков. Самый простой способ создать собственный хук — это вызов функции module_invoke_all('имяхука') (http://api.drupal.org/api/function/module_invoke_all/6) в своем модуле. Тогда коллеги-разработчики смогут реализовать этот хук в модуле, создав там функцию имямодуля_имяхука(). Давайте продемонстрируем все на примере.

Файл: mymodulename.module

/**
* Пример функции модуля с моим собственным хуком.
*/
function mymodulename_foo() {

//переменная $layouts - для примера
$layouts = array();

//давайте вызовем хук 'mymodulename_layouts'
//данный хук прицепит к нашей функции другие модули
$layouts = module_invoke_all('mymodulename_layouts');

//возвращенное значение - массив возвратов каждой вызванной реализации хука
foreach($layouts as $layout){
// Делаем всякое с возвращенными переменными
print $layout;
}
}

Как вы наверное заметили, мы назвали наш хук mymodulename_layouts — включая строку 'mymodulename'. Это позволит нам избежать конфликтов с другими модулями — если бы мы назвали хук просто 'layouts', мог бы найтись другой модуль, который уже ввел хук с таким же именем.

Чтобы протестировать созданный нами хук, мы сделаем другой модуль, назовем его 'test' и поместим в него эту функцию:

Файл: test.module

/**
* Реализация hook_mymodulename_layouts().
*/
function test_mymodulename_layouts() {
  $layout = 'testing';
  return $layout;
}

Функция module_invoke_all() возвращает массив значений, возвращенных реализациями хука. Если реализации в модулях возвращают массивы, они будут склеены в один массив. Вы можете также включать в свои хуки аргументы и даже передавать их как ссылки. Для примера посмотрите на hook_nodeapi: http://api.drupal.org/api/function/hook_nodeapi.

При создании сложных модулей вам могут пригодиться вот эти функции:

  • module_hook — определяет, реализует ли модуль хук.
  • module_implements — определяет, какие модули реализуют хук.
  • module_invoke — вызывает реализацию хука в заданном модуле.
  • module_invoke_all — как мы описали выше, эта функция вызывает хук во всех реализующих его установленных модулях.

Если эта часть цикла «20 API за 20 дней» была вам интересна, можете заглянуть в документацию API хуков по адресу http://api.drupal.org/api/group/hooks. Надеюсь, вам понравится и остальной цикл!

Комментарии