Cotonti / Open Source PHP Content Management FrameworkContent Management Framework

Хуки (или перехват управления)

Документация / Расширяем Cotonti / Расширения / Хуки (или перехват управления)

Что такое Хуки или как управлять потоком исполнения системы

#1. Что такое Хуки?

Хук (от англ. hook) это специальное место в программе, вкотором может быть выполнен какой-либо сторонний скрипт (в данном случае соответствующая часть плагина или модуля), с тем чтобы изменить логику исполнения основной программы. Можно рассматривать этот механизм, как некоторое событие, которое должно быть обработано специальным кодом. В реальности Cotonti подключает соответствующий PHP файл, зарегистрированный как обработчик соответствующего события (хука). Рассмотрим программу в которой есть Хук:

$foo = 'Повелитель добра';
$bar = 'уничтожил';
$baz = 'все зло на планете!';

// ...
// Обработчик хука здесь
// ...

echo "$foo $bar $baz";

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

$foo .= ' и его могущественные помощники';
$bar = 'уничтожили почти ';

Во время выполнения Cotonti, код будет преобразован в следующую последовательность:

$foo = 'Повелитель добра';
$bar = 'уничтожил';
$baz = 'все зло на планете!';

$foo .= ' и его могущественные помощники';
$bar = 'уничтожили почти ';

echo "$foo $bar $baz";

Таким образом, хуки это ваши помощники в деле расширения возможностей текущего кода. В Cotonti их существует достаточно большое количество, что позволяет изменить практически все.

#2. Какой хук выбрать? 

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

#2.1. Основные хуки

Некоторые хуки в системе вызываются практически всегда и строго в определенном порядке относительно друг друга:

  • input — самая первая точка расширения при выполнении скрипта. В текущей точке, несмотря на доступность основных данных (таких как конфигурация, структура, список расширений, данные пользователя) система еще не полностью инициализирована. Поэтому используйте этот хук осмотрительно и если надо переопределить некоторые системные данные на начальном этапе;
  • rc (не используется при AJAX запросе) — обычно используется расширениями для загрузки дополнительных скриптов через класс Recource или в случае, когда надо переопределить переменные касающиеся тем/языковых данных/ресурсов и других параметров;
  • global — для кода, который должен быть выполнен при каждом запросе, сразу после инициализации системы;
  • header.first - срабатывает перед обработкой заголовка страницы;
  • header.main (не используется при AJAX запросе) для переопределения некоторых переменных, перед выводом в заголовок;
  • header.tags (не используется при AJAX запросе) для добавления тегов обрабатываемых в шаблоне заголовка;
  • footer.first — срабатывает перед обработкой «подвала» (нижней части) страницы;
  • footer.main (не используется при AJAX запросе) — для изменения переменных перед выводом нижней части страницы;
  • footer.tags  (не используется при AJAX запросе) — для добавления тегов в шаблон «подвала» (footer.tpl);
  • footer.last - вызывается после обработке всей страницы (вне зависимости от того обычный это запрос или AJAX);
  • output - самый последний хук, вызываемый прямо перед выводом  информации из буфера и завершением скрипта. Используется для управления буфером вывода, финальной обработки выводимой информации и освобождения некоторых ресурсов .

В зависимости от запрошенной страницы сайта и вызова того или иного обработчика (расширения) могут быть вызваны следующие хуки:

  • popup — обработчик вызываемый при выводе всплывающих окон, загружаемых как отдельная страница (замечание: не путайте с диалоговыми окнами);
  • stanalone — точка входа для плагинов работающих на отдельной странице;
  • module — аналогично хуку 'standalone', но используется для модулей;
  • ajax — вызывается при обработке ajax запросов;
  • tools — точка входа для плагинов, которые хотят отображать свои данные на специальной странице в панеле администрирования (раздел «Прочее»);
  • admin — аналогично хуку `tools`, но для модулей

Раз уж зашла речь о системных хуках, то стоит упомянуть и третью группу — хуки расширяющие функционал системных функций. Эти хуки определены внутри некоторых функций и срабатывают при их вызове. Давайте взглянем: 

  • die.message определен в функции cot_die_message()
  • structure определен в функции cot_load_structure()
  • parser.last определен в функцииcot_parse()  
  • usertags.first и usertags.main из cot_generate_usertags()  
  • output из cot_outputfilters() (уже описывался выше).

Кроме перечисленных, в системе присутствует еще большое количество хуков, особенно если вы используетет дополнительные модули или плагины. Для получения полного списка работающих на данной странице хуков в порядке их применения, используйте специальный тег {FOOTER_HOOKS}, вставив его в шаблон footer.tpl. Замечание: этот хук работает только при включенном режиме отладки в файле datas/config.php:

$cfg['debug_mode'] = true;

(Note: тег {FOOTER_HOOKS} доступен для использования начиная с версии 0.9.6)

#2.2. Правила именования

Поговорим немного о правилах именования хуков. Большинство имен хуков состоит из нескольких слов разделенных точкой. Например:

Hook naming convention

Это не просто для удобства чтения, но и имеет логический смысл. Первое слово обычно описывает расширение или ту часть системы из которой он вызывается. Например 'users' подразумевает модуль пользователей, или 'header' говорит о заголовке страницы. Это главная подсказка при поиске точки вызова в реальном коде. Вторая часть обычно указывает на часть или режим того или иного расширения, для примера  'users.register' значит, что хук срабатывает при регистрации пользователя, более того это часто значит, что такой хук можно найти в одноименном файле, в данном случае — users.register.php. В терминах MVC можно считать, что это контролер для конкретного расширения. Далее идет строка указывающая на событие или действие, а так же его состояние или место нахождение в коде (first, last, loop, etc.). В приведенном примере строка 'add.done' говорит нам о том, что новый пользователь добавлен (событие) успешно (состояние). В терминах MVC событие/состояние может именем метода.

Таким образом, по имени хука можно сделать выводы о файле в котором расположен код-обработчик и той части кода из которого он вызывается. Поэтому рекомендуется придерживаться этих правил наименования, при разработке собственных дополнений. Ниже мы поговорим об этом подробнее.

Несколько обособлено стоят повторяющиеся (или циклические) хуки. Имя таких хуков как правил оканчивается словом '.loop', например 'forums.topics.sections.loop'. В отличае от обычных хуков, которые как правило вызываются один раз, циклические хуки вызываются неоднократно по числу итераций в определенном цикле. Наиболее чайтый случай применения таких хуков это вызов внутри скрипта по обработке результатов SQL запроса, когда хук вызывается для каждой строки результатов выборки.

#3. Программируем обработчик

Обработчик события (или в терминах Cotonti — хук) это PHP файл в папке расширения, имя которого согласуется с именем хука. Это не жесткое требование, но рекомендуемое к выполнению правило, как пример файл myext.page.edit.update.done.php в котором содержится код обработчика хука page.edit.update.done.

Примерный шаблон файла-обработчика представлен ниже:

<?php
/* ====================
[BEGIN_COT_EXT]
Hooks=page.tags
Tags=page.tpl:{PAGE_MYEXT_TAG1},{PAGE_MYEXT_TAG2}
Order=10
[END_COT_EXT]
==================== */

defined('COT_CODE') or die('Wrong URL');

// Hook handler code goes here  

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

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

На один хук различными расширениями может быть назначено несколько обработчиков. В таких случаях, иногда, полезно иметь возможность задать порядок вызова обработчиков. Для этой цели используется ключ Order, определяющий порядок вызова обработчика. Число определяющее порядок обычно находится в диапазоне 1-99. В первую оччередь выполняются обработчики чей порядковый номер меньше, т.е. в порядке возрастания номеров. Если параметр Order не задан, обработчику будет присвоен порядковый номер по умолчанию — 10.

Что же мы можем сделать используя обработчики? Да почти все что угодно! Вот несколько простеньких примеров:

  • Делать запросы в БД для извлечения и изменения данных;
  • Изменять текущие значения и объекты;
  • Назначать теги для шаблонов и обрабатывать определенные блоки в шаблонах.

Важная вещь, о которой стоит упомянуть это область видимости. Исторически Cotonti использует процедурный стиль выполнения скрипта, и большая часть хуков определена в глобальной области видимости. Поэтому в большинстве случаев вам не надо предпринимать дополнительных усилий. Однако есть и такие хуки, которые вызываются внутри функций, в таких случаях вам надо позаботится о доступе к глобальным переменным внутри этой функции, используя ключевое слово global. Наиболее часто используемые переменные это: $cfg, $usr, $db, $id, $strucutre, $t. Сейчас не будем рассматривать их назначение, т.к. это тема отдельной статьи.

Обратите внимание, что с версии 0.9.15 все наиболее востребованные глобальные переменные ($cfg$usr$db$id$strucutre и пр.) доступны через специальный «фасад»-класс cot. Таким образом нет необходимости использовать явное указание переменных в списке global, а можно обратиться к ним напрямую через свойство объекта: cot::$cfg (например). Полный список переменных доступных через «фасад» следующий: $cache, $cfg, $cot_extrafields, $db, $db_x, $env, $L, $out, $R, $structure, $sys, $usr.

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

В некоторых случаях вам даже не надо поробно изучать код вызова для написания своего обработчика. Как пример, можно привети добавление нескольких новых тегов в шаблон заголовка страницы или вывести какую либо информацию из БД.

#4. Расширяем собственные модули

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

/* === Hook === */
foreach (cot_getextplugins('myext.hook.name') as $pl)
{
	include $pl;
}
/* ===== */

Заменяем строку 'myext.hook.name' на имя нашего хука (помните описанные выше правила именования? ) и почти готово. Возможно вы заметито, что данный код можно записать в одну строку, но мы рекомендуем использовать именно приведенную (6-ти строчную) форму записиruct для того, чтобы он был выделен среди прочего кода, и стороннему разработчику было проще найти точку расширения. 

Все, для чего нужна здесь функция cot_getextplugins() это получить массив с именами файлов обработчиков указанного хука. Далее просто подключаем файл через include или include_once.

Несколько более громоздким выглядит код в случае, когда надо вызывать обработчик на каждом шаге цикла:

/* === Хук - часть1 : установка === */
$extp = cot_getextplugins('myext.some.loop');
/* ===== */

// Цикл может быть любым foreach/while/do
foreach ($items as $item)
{
	// некоторый код

        /* === Хук - часть2 : вызов обработчика === */
	foreach ($extp as $pl)
	{
		include $pl;
	}
	/* ===== */

	// еще какой-то код
}

Суть кода вынести вызов функции cot_getextplugins() из цикла для ускорения выполнения скрипта.

Рекомендуется создавать точки расширения (хуки) для любого значитального события в вашем модуле, которое потенциально может быть расширен, как правило это сбытия следующих типов: create/update/delete/display.

#4.1. Списки вызова хуков

В зависимости от запрошенного на сайте URL обработка данных для вывода страницы может идти несколько разными путяни. Ниже приведен упорядоченный список вызова основных хуков, для хороктерных случаев::

  • запрошена главныя страница: input, rc, global, [pagetags.first, pagetags.main], parser.last (*), index.first, index.main, header.first, header.main, header.body, header.user.tags, header.tags, index.tags, [polls.index.tags, news.first, news.loop, news.tags], parser.last (*), [usertags.first, usertags.main], parser.last (*), footer.first, footer.main, footer.tags, footer.last, output;
  • запрошена какая-либо страница новостей: input, [urleditor.rewrite.first,] rc, global, [pagetags.first, pagetags.main, parser.last, page.first, page.main,] header.first, header.main,header.body, header.user.tags, header.tags, parser.last (*), usertags.first, usertags.main, parser.last (*), page.tags, comments.count.query, comments.main, comments.newcomment.tags, comments.query, comments.tags, parser.last (*), ratings.main, ratings.tags, footer.first, footer.main, footer.tags, footer.last, output;
  • вызов страницы конкретного плагина: input, urleditor.rewrite.first, rc, global, [pagetags.first, pagetags.main, parser.last (*), search.first, search.page.catlist, search.list, search.tags,] header.first, header.main, header.body, header.user.tags, header.tags, footer.first, footer.main, footer.tags, footer.last, output;
  • AJAX запрос данных: inputrc, global, [pagetags.first, pagetags.main, parser.last (*),] ajax, footer.last
  • загрузка Административной страницы для модуля: input, rc, global, admin, admin.main, [forums.admin.first, forums.admin.tags,] header.first, header.main, header.body, header.user.tags, header.tags, admin.tags, footer.first, footer.main, footer.tags, footer.last, output;
  • загрузка Административной страницы для плагина: input, rc, global, tools, admin.main, admin.other.first, [admin.comments.first, admin.comments.loop, admin.comments.tags,] header.first, header.main, header.body, header.user.tags, header.tags, admin.tags, footer.first, footer.main, footer.tags, footer.last, output;

Примечание: элементы списка помеченные знаком "(*)" означают, многократный последовательный взов хука. Хуки помещенные в квадратные скобки  "[]" подразумевают возможный вызов, т.к. их список и конкретные имена зависят от верси ядра и набора установленных расширений. Хуки отмеченные жирным шрифтом являются системными и описаны выше.above).



Комментарии отсутствуют
Добавление комментариев доступно только зарегистрированным пользователям