Что такое хуки php
Хуки в PHP, принцип работы
При разработки масштабируемых приложений часто применяют хуки в PHP. Кому-то хуки нравятся, кому-то нет, но так или иначе с ними приходится иметь дело и проблема в том, что не все понимают, как это работает.
В данной статье я расскажу принцип — как работают хуки в PHP. Использовать хуки можно в любом веб-приложении, например тот, кто работает с WordPress постоянно с ними сталкивается. Использование хуков дает гибкость приложению и позволяет сторонним разработчикам расширять функционал без вмешательства в основной код.
Рассмотрим простой пример
Есть HTML шаблон и он уже заверстан. Предположим, что по каким-то причинам нельзя вмешиваться в код шаблона и что-либо менять, но при этом есть необходимость дополнить его данными.
Чаще всего возникает необходимость дополнить HTML шаблон мета-тегами, стилями, скриптами в шапке или в подвале.
Пусть существует следующий шаблон страницы:
Чтобы дать возможность добавлять мета-теги, стили и скрипты, разработчику необходимо снабдить шаблон хуками и добавить функции обработки. По сути, хуки — это крючки, за которые можно зацепиться, то есть идентифицировать то место, в котором необходимо произвести какое-либо дополнительное действие.
Код приобретет следующий вид:
Как do_action узнает какие функции нужно выполнить
В действительности все названия функций, которые должны быть выполнены на определенном хуке хранятся в массиве. Переменная, содержащая этот массив, является глобальной и доступна во всем приложении, следовательно мы можем дописывать данные в эту переменную.
Пусть массив имеет следующий вид:
Получается, что есть списки, которые складываются в один большой список, где hook_name_x — это название какого-то хука, index_x_x — индекс(порядковый номер) функции function_name_x_x в списке указанного хука.
При этом порядковый номер функции в списке можно использовать для определения последовательности выполнения функций — чем больше значение индекса, тем ниже эта функция будет в списке, а значит тем позже она будет выполнена.
Все это можно объяснить на примере: представьте себе человека, который спускается на лифте и у него в руках тетрадь, каждая страница которой соответствует какому-либо этажу, на котором останавливается лифт, на каждой странице имеется список задач, которые следует выполнить на соответствующем этаже. В данном примере названия этажей являются хуками, а элементы списка(задачи) на каждой странице, являются функциями, привязанными к конкретному хуку. Очередность выполнения задач определяется их положением в списке, сверху вниз.
Привязка функции к хуку
Для того, чтобы указать по какому хуку следует выполнить функцию, необходимо добавить название функции в соответствующий список. При этом должна быть возможность указать приоритет выполнения функции.
Хуки в PHP — расширение функций и контроллеров ядра
Общая информация
Многие функции и методы CS-Cart (Multi-Vendor) имеют специальные хуки.
Хуки позволяют модифицировать и расширять возможности платформы с помощью модуля.
С помощью хука можно:
Хуки расположены в функциях и методах ядра CS-Cart.
Общий принцип использования и работы с хуками:
Доступно очень много хуков:
Как выглядт и как использовать хук?
Хуки в PHP выглядят так:
Чтобы подключиться к хуку, вам необходимо:
Инициализировать подключение к хуку.
В данный файл добавьте функцию:
Если используте несколько хуков, передавайте названия хуков через запятую:
Создайте функцию, которая будет выполняться в хуке.
Функция должна иметь название вида: fn_[id_модуля]_[название_хука]($[параметры_хука_через_запятую])
В функции будут доступны все параметры передаваемые в хук.
Чтобы функция могла влиять на параметры (изменять снаружи), их необходимо передавать как ссылки ( &$param )
Живой пример
Предположим, что нам нужно добавить какую то новую информацию о товаре, если товара нет на складе.
Подключимся к последнему хуку и добавим нужную нам информацию с помощью модуля “Мои изменения”:
Добавим в него код:
Создадим функцию для подключения к хуку.
Создадим файл app/addons/my_changes/func.php
Добавим новую функцию, которая сработает в хуке:
Включим модуль “Мои изменения”.
Как проверить что оно работает?
Используйте функцию fn_print_r($product_data); до хука, после хука или внутри хука. Она распечатает на экран содержимое массива.
Как я могу использовать именно этот пример?
Создайте новую вкладку с SMARTY блоком для карточки товара. В данном SMARTY блоке вы можете использовать информацию из массива <$product_data>, в том числе вашу новую информацию, например, для каких либо условий.
Хуки — это просто
Хуки — это технология перехвата вызовов функций в чужих процессах. Хуки, как и любая достаточно мощная технология, могут быть использованы как в благих целях (снифферы, аудио\видеограбберы, расширения функционала закрытого ПО, логирование, багфиксинг) так и со злым умыслом (трояны, кряки, кейлоггеры). О хуках уже не раз писали и на Хабре и не на Хабре. Но вот в чём беда — почему-то каждая статья о хуках буквально со второго абзаца начинает рассказывать о «таблице виртуальных функций», «архитектуре памяти» и предлагает к изучению огромные блоки ассемблерного кода. Известно, что каждая формула в тексте снижает количество читателей вдвое, а уж такие вещи — так и вовсе вчетверо. Поэтому нужна статья, которая расскажет о хуках просто. Под катом нет ассемблера, нет сложных терминов и буквально два десятка строк очень простого кода на С++. Если вы давно хотели изучить хуки, но не знали с чего начать — начните с этой статьи.
Реальная задача
Для лучшего понимания того, что мы делаем — поставим себе какую-нибудь реальную задачу. Давайте, например сделаем так, чтобы браузер Firefox при заходе на Хабр писал в своём заголовке «Привет, Хабр!» вместо того, что там пишется сейчас (а сейчас там пришется «*** / Хабрахабр — Mozilla Firefox», где *** — меняется в зависимости от раздела). Да, я знаю, что это можно сделать правкой исходников Firefox, браузерными плагинами, юзерскриптами и еще десятком способов. Но мы в учебных целях сделаем это хуками.
Совсем чуть-чуть теории
Когда Вы запускаете любое приложение — операционная система создаёт его процесс. Грубо говоря, exe-файл копируется в память, далее определяется какие именно библиотеки (dll-файлы) ему нужны для работы (эта информация записана в начале каждого exe-файла), эти библиотеки ищутся (в папке с программой и в системных папках) и загружаются в память процесса. Потом определяется, какие именно функции библиотек использует программа и где они находятся (в какой библиотеке и где именно в этой библиотеке). Строится табличка вида «функция SomeFunction1() — библиотека SomeLibrary1.dll — %адрес_функции_SomeFunction1()%». Когда программе понадобиться вызвать эту функцию — она найдет в своей памяти нужную библиотеку, отсчитает нужный адрес и передаст туда управление.
Суть хукинга — заставить программу поверить, что нужная ей функция находится в другом месте.
Делается это таким образом — мы пишем свою библиотеку SomeLibrary2.dll, в которой будет находится наша функция SomeFunction2(). Далее мы загружаем эту библиотеку в память чужого процесса (в ОС Windows есть специальная функция для этого) и изменяем ту самую табличку, о которой я писал чуть выше, так, чтобы теперь она содержала запись «функция SomeFunction1() — библиотека SomeLibrary2.dll — %адрес_нашей_функции_SomeFunction2()%». Для того, чтобы понять, как вручную сделать всё описанное в этом абзаце, нужно знать весьма прилично всего — как устроена память в Windows, как вызываются функции, как им передаются аргументы и т.д. Это сложно. Ну на самом деле не очень, просто можно обойтись и без этого. Если вам это нужно — почитайте какую-нибудь продвинутую статью (а хоть бы из тех, что указаны в начале). Мы пойдем другим путем — используем готовую библиотеку Microsoft Detours, которая сделает всю грязную работу за нас.
Пару слов о Microsoft Detours
Проста в изучении и использовании
Весьма эффективна
Хорошая документация
Содержит много примеров в исходниках
Разработана Microsoft — неплохо «дружит» с ОС
Бесплатна для исследовательских целей и некоммерческих проектов
Не требует знания ассемблера
Закрыта
Стоит приличных денег для коммерческого использования или х64-архитектуры
В целом, я бы посоветовал начинать изучение хуков именно с Detours — если это будет всего лишь вашим разовым развлечением, то этого вполне хватит, у вас быстро всё получится и вам понравится. Если же хуки понадобятся в серьёзном проекте — вы легко переключитесь на бесплатные и открытые (но чуть более сложные) библиотеки типа mhook, купите Detours или напишете свой велосипед (для последних двух решений нужны весьма веские причины).
О том где взять и как собрать Detours я писал вот тут.
Хитрый план
Куда ставить хук
Переходим в Firefox, открываем Хабр, дожидаемся изменения заголовка на нужный и возвращаемся в Api Monitor чтобы остановить мониторинг. Скорее всего, вы будете удивлены количеством вызванных функций — их могут быть сотни тысяч буквально за несколько секунд мониторинга. А мы ведь еще и следим далеко не за всем. Да-да, это всё реально происходит внутри безобидного открытия всего одного сайта в браузере! А вы еще жалуетесь, что эта пара секунд — слишком долго. 🙂
Найти нужную нам функцию поможет поиск по вкладке с результатами мониторинга. Вбиваем в поиск «WM_SETTEXT» и убеждаемся, что действительно имеются вызовы функции SendMessageW с этим параметром — с высокой вероятностью это и есть установка заголовка окна. Обратите внимание на «W» в конце названия функции — оно означает, что используется её юникодная версия. Для установки хуков важно знать точное имя подменяемой функции и теперь мы его знаем.
Делаем свою библиотеку
4. Открываем свойства проекта и на вкладке настроек линкера добавляем в поле Additional Dependencies значение «C:\Program Files\Microsoft Research\Detours Express 3.0\lib.X86\detours.lib». Внимание, у вас путь может быть другой — смотря куда установили библиотеку Detours.
Давайте разберем исходник. В начале мы подключаем заголовочные файлы Windows (чтобы пользоваться функцией SendMessageW) и Detours (чтобы иметь возможность ставить\снимать хуки).
В сложной на первый взгляд строке №3 мы всего лишь сохраняем реальный указатель на функцию SendMessageW в переменную TrueSendMessageW. Это нам понадобиться для двух целей:
Функция DllMain вызывается операционной системой в определенных случаях — например, в моменты аттача\детача библиотеки к процессу. Тут тоже всё просто. В момент аттача нам нужно установить хуки, в момент детача — снять. Библиотека Detour требует делать это транзакциями, и в этом есть смысл — представьте себе что будет, если сразу несколько желающих захотят поставить хуки в один процесс. Самое важное в этом коде это строка
Именно она заставляет процесс «поверить» что теперь вместо настоящей функции SendMessageW нужно вызывать нашу MySendMessageW. Ради этой строки всё и затевалось. Если кому интересно, однажды я писал аналог этой функции вручную. С учетом всех возможных комбинаций типов функций и архитектур это заняло у меня несколько недель. Вы вот только что их сэкономили — поздравляю.
Cotonti / Open Source PHP Content Management FrameworkContent Management Framework
Что такое Хуки или как управлять потоком исполнения системы
#1. Что такое Хуки?
Хук (от англ. hook) это специальное место в программе, вкотором может быть выполнен какой-либо сторонний скрипт (в данном случае соответствующая часть плагина или модуля), с тем чтобы изменить логику исполнения основной программы. Можно рассматривать этот механизм, как некоторое событие, которое должно быть обработано специальным кодом. В реальности Cotonti подключает соответствующий PHP файл, зарегистрированный как обработчик соответствующего события (хука). Рассмотрим программу в которой есть Хук:
Теперь предположим у нас есть обработчик данного хука, который имеет доступ ко всем переменным в данной областивидимости (в контексте вызова). Этот обработчик может выглядеть например так:
Во время выполнения Cotonti, код будет преобразован в следующую последовательность:
Таким образом, хуки это ваши помощники в деле расширения возможностей текущего кода. В Cotonti их существует достаточно большое количество, что позволяет изменить практически все.
#2. Какой хук выбрать?
Первый вопрос на который стоит ответить прежде чем писать очередную часть своего расширения — это то, какой функционал системы мы хотим изменить. А следовательно какой хут стоит для этого использовать. Конкретный список доступных хуков зависит от многих факторов: того какой модуль вызван, какая его часть при этом сработает, и какие действия будут выполнены в процессе работы скрипта. В зависимости от условий будут обработаны соответствующие хуки и вызваны соответствующие им обработчики.
#2.1. Основные хуки
Некоторые хуки в системе вызываются практически всегда и строго в определенном порядке относительно друг друга:
В зависимости от запрошенной страницы сайта и вызова того или иного обработчика (расширения) могут быть вызваны следующие хуки:
Раз уж зашла речь о системных хуках, то стоит упомянуть и третью группу — хуки расширяющие функционал системных функций. Эти хуки определены внутри некоторых функций и срабатывают при их вызове. Давайте взглянем:
(Note: тег доступен для использования начиная с версии 0.9.6 )
#2.2. Правила именования
Поговорим немного о правилах именования хуков. Большинство имен хуков состоит из нескольких слов разделенных точкой. Например:
Таким образом, по имени хука можно сделать выводы о файле в котором расположен код-обработчик и той части кода из которого он вызывается. Поэтому рекомендуется придерживаться этих правил наименования, при разработке собственных дополнений. Ниже мы поговорим об этом подробнее.
#3. Программируем обработчик
Примерный шаблон файла-обработчика представлен ниже:
Ключ Tags используется для описания дополнительных тегов, назначаемых в процессе обработки. Этот ключ используется в качестве информационного — для отображения в админ-панеле списка дополнительных тегов и имен шаблонов в которых они используются.
Что же мы можем сделать используя обработчики? Да почти все что угодно! Вот несколько простеньких примеров:
Основной совет начинающим разработчикам расширений для Cotonti —начните с изучения кода. Если вы хотите знать как писать плагины — посмотрите на код некоторых из них (среди них есть очень простые и понятные даже для начинающих). Если вы хотите знать как изменить поведение той или иной части — найдите код, который за нее отвечает, посмотрите где определены ближайшие точки вызова хуков и проанализируйте что происходит в этом куске кода. Cotonti написан в простой и понятной форме и код исполняется достаточно линейно, поэтому можно быстро в нем разобраться. Как только вы разберете основной алгоритм выполнения, вы с легкостью сможете менять его. Для написания более сложных плагинов вам потребуется иметь дело с несколькими хуками и обработчиками, но это уже будет не намного сложней чем начать..
В некоторых случаях вам даже не надо поробно изучать код вызова для написания своего обработчика. Как пример, можно привети добавление нескольких новых тегов в шаблон заголовка страницы или вывести какую либо информацию из БД.
#4. Расширяем собственные модули
Под модулем здесь понимается любое расширение для Cotonti. После того, как вы изучили, как работать с хуками и писать для них свой обработчик — самое время позаботится о других разработчиках, которые, возможно, захотят дополнить ваше расширение. И самый просто вариант это добавить выхов хуков в местах наиболее вероятного расширения, точно так же как это делает ядро системы:
Заменяем строку ‘ myext.hook.name ‘ на имя нашего хука (помните описанные выше правила именования? ) и почти готово. Возможно вы заметито, что данный код можно записать в одну строку, но мы рекомендуем использовать именно приведенную (6-ти строчную) форму записиruct для того, чтобы он был выделен среди прочего кода, и стороннему разработчику было проще найти точку расширения.
Несколько более громоздким выглядит код в случае, когда надо вызывать обработчик на каждом шаге цикла:
Суть кода вынести вызов функции cot_getextplugins() из цикла для ускорения выполнения скрипта.
#4.1. Списки вызова хуков
В зависимости от запрошенного на сайте URL обработка данных для вывода страницы может идти несколько разными путяни. Ниже приведен упорядоченный список вызова основных хуков, для хороктерных случаев::
Примечание: элементы списка помеченные знаком «(*)" означают, многократный последовательный взов хука. Хуки помещенные в квадратные скобки «[]" подразумевают возможный вызов, т.к. их список и конкретные имена зависят от верси ядра и набора установленных расширений. Хуки отмеченные жирным шрифтом являются системными и описаны выше.above).
Хуки в PHP — расширение функций и контроллеров ядра¶
Общая информация¶
Многие функции и методы CS-Cart имеют специальные хуки.
Хуки позволяют модифицировать и расширять возможности платформы с помощью модуля.
С помощью хука можно:
Хуки расположены в функциях и методах ядра CS-Cart.
Общий принцип использования и работы с хуками:
Доступно очень много хуков:
Как выглядит и как использовать хук?¶
Хуки в PHP выглядят так:
Чтобы подключиться к хуку, вам необходимо:
Инициализировать подключение к хуку.
В данный файл добавьте функцию:
Если используте несколько хуков, передавайте названия хуков через запятую:
Создайте функцию, которая будет выполняться в хуке.
Функция должна иметь название вида: fn_[id_модуля]_[название_хука]($[параметры_хука_через_запятую])
В функции будут доступны все параметры передаваемые в хук.
Чтобы функция могла влиять на параметры (изменять снаружи), их необходимо передавать как ссылки ( &$param )
Живой пример¶
Предположим, что нам нужно добавить какую то новую информацию о товаре, если товара нет на складе.
Подключимся к последнему хуку и добавим нужную нам информацию с помощью модуля “Мои изменения”:
Добавим в него код:
Создадим функцию для подключения к хуку.
Создадим файл app/addons/my_changes/func.php
Добавим новую функцию, которая сработает в хуке:
Включим модуль “Мои изменения”.
Как проверить что оно работает?
Используйте функцию fn_print_r($product_data); до хука, после хука или внутри хука. Она распечатает на экран содержимое массива.
Как я могу использовать именно этот пример?
Создайте новую вкладку с SMARTY блоком для карточки товара. В данном SMARTY блоке вы можете использовать информацию из массива <$product_data>, в том числе вашу новую информацию, например, для каких либо условий.
Разграничение редакций¶
Хуки, которыми будут расширяться редакции, необходимы, чтобы избежать услвоий по типу:
Нам нужно расширить данную функцию в Multi-Vendor. Таким образом, в файле /app/functions/fn.multivendor.php создаем функцию:
Код для определенной редакции¶
Или же использовать отрицание:
Первая часть кода означает, что этот код появится в перечисленных редакциях, вторая же часть — что код НЕ появится в этих редакциях
Хуки в PHP скриптах¶
Версионные теги так же используют фукнцию fn_allowed_for :