Хуки в PHP
Как известно, в PHP нельзя переназначить функцию, или как-то изменить ее функциональность после объявления. Чтобы обойти это ограничение, веб-программистами используются так называемые хуки, которые в простейшем варианте выглядят примерно так:
<?php
function my_function($param1,$param2,$param3){
if(function_exists('hook_my_function'))
return hook_my_function($param1,$param2,$param3);
# далее идет код, самой функции, например:
return $param1*$param2-$param3;
}
?>
Давайте разберемся, как это работает. В начале самой функции мы проверяем, не был ли создан хук, который должен заменить собой эту самую фукнцию. Если хук-фукнция объявлена, то ей передаются все те же параметры, что были переданы родительской функции(my_function), my_function возвращает значение, возвращенное функцией hook_my_function, не выполняя код своего тела.
В чем недостатки такого подхода?
- Хук к этой функции мы также можем объявить только один раз. После, конечно, можно будет хукнуть хук этой функции по тому же принципу, но хук хука не выполнится, если не выполнится сам хук функции. В итоге, мы получаем карточный домик с множеством зависимостей, и, в результате, непредсказуемым поведением.
- Даже если нам надо чуть подправить функцию, например умножить результат на 2, то нам придется переписывать все тело функции с нуля. Если функция сложная, код становится довольно объемным.
Это явно не наш метод! До недавних пор, я сам пользовался чем-то подобным, с небольшой модификацией, хукая функцию после ее выполнения:
<?php
function my_function($param1,$param2,$param3){
# Тело функции, результатом работы которого является переменная $result, например:
$result=$param1*$param2-$param3;
if(is_function('hook_my_function'))$result=hook_my_function($result,func_get_args());
return $result;
}
>
Суть методики в том, что мы выполняем функцию, получив результат ее исполнения, и, если объявлен хук для этой функции, мы передаем ему результат выполнения родительской функции в первом параметре, и массив параметров родительской функции во втором параметре. В итоге, мы можем работать как с отработанными данными, так и с исходными. Это значительно добавляет гибкости в нашу функцию.
Кто-то, возможно, скажет, что этот вариант теряет в производительности по сравнению с первым, но, тем не менее, в гибкости он получает огромный прирост. Кроме того, никто не заставляет нас использовать его постоянно.
Так или иначе, мы так и не решили проблему множественного объявления хуков к функции, а она от нас никуда не делась. Поразмышляв на досуге над этой проблемой, я придумал не просто решение, а настоящий алгоритм расширения возможностей системы хуков. При всей своей простоте кода, этот подход раскрывает практически неограниченные возможности в расширении возможностей функций. Итак, вот код:
<?php
function hook($functions,$result,$args){
# Основная функция данного подхода.
if(is_array($functions){
ksort($functions);
foreach($functions as $function)
if($function&&function_exists($function))
$result=$function($result,$args);
}
return $result;
}
# Основная переменная данного подхода.
# Здесь регистрируются хук-функции.
$hook=array();
# Пример использования технологии:
function my_function($param1, $param2){
$return=$param1+$param2-$param1*$param2;
# Далее хукаем:
global $hook;
return hook($hook['my_function'],$return,func_get_args());
}
# Объявляем первую хук-фукнцию
function hook_my_function_1($return,$args){# пример хука
return $return*2;
}
# Регистрируем нашу хук-функцию
$hook['my_function'][]='hook_my_function_1';
# Объявляем вторую хук-функцию
function hook2_my_function($return,$args){
return $return / $args[1];
}
# Регистрируем вторую хук-функцию
$hook['my_function'][]='hook2_my_function';
# Выполняем.
my_function(3,12);
?>
Длинный получился листинг? Хороший пример важен для понимания. Сейчас мы рассмотрим все по пунктам.
Функция hook() и переменная $hook являются ключевыми в нашей технологии, вокруг них вся пляска, поэтому объявление каждой функции, к которой в дальнейшем предполагается делать перехват должно заканчиваться строчками
global $hook;
return hook($hook['my_function'],$return,func_get_args());
переменная $hook является двумерным массивом, ключи первого уровня которого являются названиями функций, которым предназначаются хуки, а значениями второго уровня - хук-функции для них. В приведенном выше примере, структура переменной $hook выглядит так:
array(
'my_function' => array(
0 => 'hook_my_function_1',
1 => 'hook2_my_function'
)
)
Из этого листинга можно сделать выводы:
- Хукать одну и ту же функцию можно бесконечно
- Имена хук-функций могут быть произвольными, лишь бы были уникальными
- Все хук-функции должны принимать одинаковый набор параметров. Первый - обработанные данные, второе - исходные данные.
- При создании хук-функции, ее обязательно следует регистрировать в переменной $hook, причем именно для той функции, которую будем хукать.
С переменной вроде разобрались, рассмотрим поподробней функцию hook(). Первым параметром она принимает массив, содержащий список хук-функций, которые следует выполнить. Вторым параметром принимает значение, полученное в результате выполнения родительской функции, в которой вызван хук. Третьим параметром она получает массив, содержащий исходные параметры, переданные родительской функции.
После этого проверяется, точно ли в первом параметре был передан массив. Сортируем его по ключам, после чего по очереди выполняем все хук-функции, с каждым разом обновляя переменную $result, и передавая ее следующей хук-функции уже обновленной. Вместе с $result, мы передаем хук-функции и исходные данные. После выполнения всех хук-функций, мы возвращаем переменную $result, тем самым передавая ее родительской функции, а та, в свою очередь, возвращает ее, завершая свою работу.
Из анализа работы функции hook() мы видим следующее:
- Если не объявлено ни одного хука для функции, то hook() просто возвращает полученную переменную $result, родительская функция просто выполняется так как задумывалось изначально, никакого хука не происходит.
- Мы можем управлять очередностью выполнения хуков, назначая вторичные ключи переменной $hook самостоятельно. Например, так:
Таким образом, те хук-функции, индексный ключ которых больше, выполняются позже, а те, индексный ключ которых меньше - соответственно, выполняются раньше.$hook['my_function'][100]='the_last_hook' - Перед тем как применять эту методику, ее следует основательно обдумать, и полностью понять. Очередность перехватов желательно тщательно планировать, если это не обработка текста, в котором, скажем, нужно подсветить определенное слово, а потом захотелось заменить какое-то другое свлово на синоним, и т.д.
Как вы, наверное, уже догадались, это не скрипт, готовый к применению, а технология, еще одна монетка в копилку разума, или, если вам так угодно, информация для размышления. Так или иначе, эта статья - не четкая инструкция к действию, а размышления вслух, предназначенные исключительно для ознакомления.
Спонсоры:
Бесплатные электронные книги самой разнообразной тематики.
Киевская контора, занимающаяся созданием сайтов.
Блог о программном обеспечении для для MacOS и iPhone
- Статьи схожей тематики:

Комментарии(12):
rss-лентаДобавлено: 2009-02-03 14:09:28, vikeng
Добавлено: 2009-02-03 14:20:49, Bolzamo
Добавлено: 2009-02-04 23:46:14, proft
Добавлено: 2009-02-04 23:53:26, Bolzamo
Добавлено: 2009-02-06 00:32:40, proft
Добавлено: 2009-02-12 15:04:41, Drum Boom
Добавлено: 2009-02-12 15:14:15, Bolzamo
Добавлено: 2009-02-12 16:51:07, Drum Boom
Добавлено: 2009-05-07 23:31:36, Vladimir
Добавлено: 2009-07-20 16:13:26, oktopus
Добавлено: 2009-07-20 16:14:33, oktopus
Добавлено: 2009-07-22 17:22:07, Bolzamo