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. Надеюсь, вам понравится и остальной цикл!

demimurych (гость)
Аватар пользователя demimurych

Глобальной ошибкой всей статьи является то, что людям дают понять будто бы хуки, привязаны к имени модуля.
Да, в некоторых случаях это так. НО ЭТО НЕ ЯВЛЯЕТСЯ ПРАВИЛОМ.

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

graker
Аватар пользователя graker

Отписал на Д.ру. Вы неправильно/невнимательно прочитали.

В статье нигде не утверждается, что ЭТО ЯВЛЯЕТСЯ ПРАВИЛОМ. Вместо этого:
а) говорится, что для реализации хука нужно в своем модуле реализовать функцию имямодуля_имяхука;
б) при создании своих хуков рекомендуется начинать их название с имени объявляющего модуля.

Dmitry (гость)
Аватар пользователя Dmitry

Хорошая статья. продолжайте переводить в том же духе.

graker
Аватар пользователя graker

Спасибо, скоро будет еще.

Добрый день.
Готов помочь с переводом. Опыта, правда, немного - несколько переводов на api.drupal.ru.
Может быть, зальете на сервер коллективных переводов, и возьмете на себя модераторские функции?

graker
Аватар пользователя graker

Добрый день.
У меня здесь, в нутре домовой страницы, действует свой сервер коллективных переводов ;) Поэтому перевожу все прямо тут.

Если есть интерес, зарегистрируйтесь и отпишите мне с нового аккаунта в форму обратной связи - дам доступ.

toxed (гость)
Аватар пользователя toxed

спасиба за перевод. искал инфу у module_invoke_all()
нашел для себя ответ здесь

graker
Аватар пользователя graker

На здоровье.
Насчет module_invoke_all() - так вроде на соответствующей странице api.drupal.org про нее нормально все написано, разве нет?

Гость (гость)
Аватар пользователя Гость

мне надо изменить форму, теорию прочитала, а как теперь на практике реализовать то это? с него начать?

graker
Аватар пользователя graker

С написания модуля, наверное.
Но если возникает такой вопрос — видимо не всю теорию еще прочитали.

Layla (гость)
Аватар пользователя Layla

Я новичок, только только начинаю писать модули. Какие книги порекомендуете?

graker
Аватар пользователя graker

Вот этого хватит, остальное усваивается на практике по докам с api.drupal.org.

Гость (гость)
Аватар пользователя Гость

Заметил опечатку почти в самом начале перевода: не смыл, а смысл

graker
Аватар пользователя graker

Спасибо, поправил.

inza (гость)
Аватар пользователя inza

А что, авторы серии до конца ее не опубликовали?

graker
Аватар пользователя graker

Нет, не опубликовали. Feeds and Aggregation — так и осталась последней статьей цикла.

Что лишний раз подтверждает бессмысленность жестких обещаний по срокам в нефинансируемом опенсурсе.

Отправить комментарий

CAPTCHA
Пройдите, пожалуйста, проверку. Она нужна, чтобы отличать людей от спам-роботов. А если не хотите проходить эту проверку регулярно — зарегистрируйтесь.
Image CAPTCHA
Введите цифры, изображенные на картинке (без пробелов).