Хуки в 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, не выполняя код своего тела.

В чем недостатки такого подхода?

Это явно не наш метод! До недавних пор, я сам пользовался чем-то подобным, с небольшой модификацией, хукая функцию после ее выполнения:

<?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(). Первым параметром она принимает массив, содержащий список хук-функций, которые следует выполнить. Вторым параметром принимает значение, полученное в результате выполнения родительской функции, в которой вызван хук. Третьим параметром она получает массив, содержащий исходные параметры, переданные родительской функции.

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

Из анализа работы функции hook() мы видим следующее:

  1. Если не объявлено ни одного хука для функции, то hook() просто возвращает полученную переменную $result, родительская функция просто выполняется так как задумывалось изначально, никакого хука не происходит.
  2. Мы можем управлять очередностью выполнения хуков, назначая вторичные ключи переменной $hook самостоятельно. Например, так:

    $hook['my_function'][100]='the_last_hook'

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

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


Спонсоры:

Бесплатные электронные книги самой разнообразной тематики.

Киевская контора, занимающаяся созданием сайтов.

Блог о программном обеспечении для для MacOS и iPhone

Статьи схожей тематики:

Делаем поиск с подсветкой результатов

Neutrino Atomic Edition 0.8.7

Улучшенная система PHP-хуков

Предлагаю вашему вниманию...

PHPDL - скрипт для отображения директорий

Комментарии(13):

rss-лента

Добавлено: 2009-02-03 14:09:28, vikeng

Мне как-то ни разу в процессе программирования не нужно было переопределять уже существующие функции. Но идея интересная. Надо подумать к чему это можно применить. :)

Добавлено: 2009-02-03 14:20:49, Bolzamo

Применить это можно при реализации системы плагинов и модулей в CMS.

Добавлено: 2009-02-04 23:46:14, proft

Имхо, сложный механизм на первый взгляд, из-за которого могут в будущем возникнуть траблы. Я бы для расширения уже существующего функционала воспользовался бы паттерном Декоратор(Decorator), а там где несколько хуков - Наблюдателем (Observer), хотя последний не во всех случаях хорош в сравнении с чем планируется использовать хуки.

Добавлено: 2009-02-04 23:53:26, Bolzamo

Ну я ж говорю, это не инструкция, а описание технологии, которую можно взять за основу при разработке своей системы.

Что же до паттернов - ссылки в студию :)

Добавлено: 2009-02-06 00:32:40, proft

Я у себя в блоге делал небольшой обзор паттернов в PHP - http://www.proft.com.ua/primery-realizacij-patternov-proektirovaniya-v-php/
Welcome ;)

Добавлено: 2009-02-12 15:04:41, Drum Boom

Честно говоря впервые слышу о таком подходе и не совсем разобрался что тут имеется ввиду.
Можно на примере спросить - могу ли я сделать mysql_query стандартно через хук и к этому вызову добавить ещё какой то свой код? Если да - буду очень благодарен за маленький пример.

Спасибо.

Добавлено: 2009-02-12 15:14:15, Bolzamo

Саму функцию mysql_query хукнуть не удастся, потому как нам недоступно ее объявление. Но мы можем заранее предусмотреть хук к ней, сделав функцию-обертку вида:
function mysql_hquery($sql,$db){
$result=mysql_query($sql,$db);
global $hook;
return hook($hook['mysql_hquery'],$return,func_get_args());
}

И пользоваться функцией mysql_hquery вместо mysql_query. Функция hook() и массив $hook должны быть объявлены согласно описанной выше технологии.

Добавлено: 2009-02-12 16:51:07, Drum Boom

Ясно, спасибо, я так и делал раньше (а то думал, что чего нибудь пропустил) только вот теперь понимаю, что использовал ХУК. Спасибо за помощь в терминологии и содержательный ответ. Как я говорю обычно на вопрос о перегрузки функций в PHP - пользуйтесь мозгами и Find/Replace и будет счастье.

Добавлено: 2009-05-07 23:31:36, Vladimir

Посмотрите, как реализована эта функциональность в WordPress (add_filter/apply_filter) — на мой взгляд, там более изящное решение

Добавлено: 2009-07-20 16:13:26, oktopus

это интересно, однако мне кажется, что если существуют такие вещи как как ООП, и в частности, итерфейсы и наследование, то пользоваться для таких целей нужно именно ими.

Добавлено: 2009-07-20 16:14:33, oktopus

*интерфейсы

Добавлено: 2009-07-22 17:22:07, Bolzamo

oktopus, все бы хорошо, да только в php нет перегрузки классов и методов. То есть, объявив класс, трудно будет потом что-то туда добавить.

Кстати, в drupal6 используется система хуков, похожая на предложенную мной.

Добавлено: 2010-07-02 18:44:05, Создание сайта

Имхо, статья не очень. В любом учебнике можно найти поболее.

Добавить комментарий

Ваше имя:*
Ваш email:*(не публикуется)
Ваш блог:
Ваш комментарий:*

Переносы строк и url-адреса преобразуются автоматически, не забудьте отделить их пробелами. html и bb-коды не поддерживаются.

Поставка резервуаров водяного охлаждения Enzotech