Если Вы начали делать более-менее сложное приложение на Kohana v3, то наверняка вспоминали такой родной и необходимый инструмент, как неразлучная парочка Hooks & Events из Kohana v2.3. Все, что предоставляет нам Ko3 — файл bootstrap.php (управление ходом работы приложения) и возможность добавления «двух центов» модулями через файлы init.php. Согласитесь, это как после роскошного «мерседеса» с кожаным салоном пересесть в старенький «пежо» с битыми бамперами — все работает, но нет легкости и ощущения всемогущества. Давайте это исправим.
В первую очередь надо добавить класс Events в наше приложение. Взять его можно из версии 2.3.4 (ссылка в репозитории). Добавляем в какой-либо отдельный модуль с собственными наработками (в моем случае это модуль core), либо в Application (если класс потребуется только в данном проекте).
Далее надо определиться с событиями. Как мы помним, события могут быть системными и пользовательскими (классификация моя ).
- Системные события в 2.3.4 вызывались внутри классов фреймворка по мере выполнения (system.ready, system.execute, system.shutdown и т.д.). Если вы забыли последовательность стандартных событий, загляните сюда.
- Пользовательские события инициируются пользователем, обычно в контроллере (примеры таких событий — user.login или article.create). Как правило, пользовательские события нужны для отработки дополнительных действий вне контроллера (например, при использовании системы плагинов).
Если с пользовательскими событиями проблем не будет, то для того, чтобы расставить вызовы системных событий, придется править bootstrap.php (кстати, ничего страшного в этом нет — все, что в Application, отдано нам на растерзание разработчиками). Вот пример моего bootstrap‘а:
<?php defined('SYSPATH') or die('No direct script access.'); //-- Environment setup -------------------------------------------------------- /** * Set the default time zone. * * @see http://docs.kohanaphp.com/features/localization#time * @see http://php.net/timezones */ date_default_timezone_set('Europe/Moscow'); /** * Enable the Kohana auto-loader. * * @see http://docs.kohanaphp.com/features/autoloading * @see http://php.net/spl_autoload_register */ spl_autoload_register(array('Kohana', 'auto_load')); //-- Configuration and initialization ----------------------------------------- I18n::$lang = 'ru-RU'; /** * Initialize Kohana, setting the default options. * * The following options are available: * * - string base_url path, and optionally domain, of your application NULL * - string index_file name of your index file, usually "index.php" index.php * - string charset internal character set used for input and output utf-8 * - string cache_dir set the internal cache directory APPPATH/cache * - boolean errors enable or disable error handling TRUE * - boolean profile enable or disable internal profiling TRUE * - boolean caching enable or disable internal caching FALSE */ Kohana::init(array('base_url' => '/', 'index_file' => '')); /** * Attach the file write to logging. Multiple writers are supported. */ Kohana::$log->attach(new Kohana_Log_File(APPPATH.'logs')); /** * Attach a file reader to config. Multiple readers are supported. */ Kohana::$config->attach(new Kohana_Config_File); /** * Enable modules. Modules are referenced by a relative or absolute path. */ Kohana::modules(array( 'auth' => MODPATH.'auth', // Basic authentication 'core' => MODPATH.'core', 'database' => MODPATH.'database', // Database access 'orm' => MODPATH.'orm', // Object Relationship Mapping // и прочие модули )); Event::run('system.ready'); /** * Set the routes. Each route must have a minimum of a name, a URI and a set of * defaults for the URI. */ Route::set('default', '(<controller>(/<action>(/<id>)))') ->defaults(array( 'controller' => 'welcome', 'action' => 'index', )); Event::run('system.execute'); /** * Execute the main request. A source of the URI can be passed, eg: $_SERVER['PATH_INFO']. * If no source is specified, the URI will be automatically detected. */ Request::instance()->execute(); Event::run('system.display'); echo Request::instance() ->send_headers() ->response; Event::run('system.shutdown'); |
Поскольку не хочется вмешиваться в системные файлы, получилось расставить только основные события (например, system.pre_controller и system.execute пришлось объединить). Кроме того, для своевременного вызова события system.display (до вывода содержимого буфера на экран) необходимо было разделить работу объекта Request на две части — собственно выполнение контроллера (вместе с определением роутинга) и вывод на экран. В конце вызывается событие system.shutdown.
Однако не все так просто. К сожалению, ход выполнения скрипта может быть нарушен редиректом. Дело в том, что Request->redirect()
отрабатывает моментально и не дает никуда вставить событие system.redirect (если конечно не считать ручную правку файла или его потомка). Единственный выход, который я нашел на данный момент — использование функции register_shutdown_function(), которая позволяет добавить свой хук на завершение работы скрипта. Его можно добавить в bootstrap.php, но я использовал файл init.php модуля ядра:
<?php defined('SYSPATH') or die('No direct script access.'); Route::set('media', 'media(/<action>(/<id>))') ->defaults(array( 'controller' => 'media', 'action' => 'css', )); // добавляем хуки Event::add('system.ready', array('core', 'init')); Event::add('system.ready', array('message', 'init')); Event::add('system.shutdown', array('message', 'save')); // регистрируем функцию окончания работы скрипта register_shutdown_function(array('core', 'shutdown')); |
В данном файле помимо добавления маршрутов (собственно это одна из главных целей файлов инициализации) присутствует привязка к системным событиям (класс message я уже как-то описывал, он работает с сессией, поэтому для него необходимо сохранять данные при окончании работы скрипта и считывать их при новом вызове), и напоследок — регистрация функции core::shutdown()
. Внутри можно прописывать все, что угодно, я сделал так:
public static function shutdown() { $status = intval(Request::instance()->status); if ($status>=300 AND $status<400) { Event::run('system.redirect'); Event::run('system.shutdown'); } } |
Дело в том, что при редиректе обычно используется HTTP-статус между 300 и 307, поэтому мы можем проанализировать значение свойства $status
объекта Request и если имел место редирект, вызывать нужные события.
Помимо system.redirect я вызываю также и system.shutdown, так как в файле bootstrap.php до этого вызова дело не дойдет. Если же редиректа нет, то цепочка событий пойдет именно так, как мы в boostrap‘е описывали.
Вот кажется и все. Используйте события, это удобно!
UPDATE. Как обычно, не обошлось без накладок. Естественно, второй вызов register_shutdown_function()
ничего в итоге не даст, т.к. первым будет добавлен стандартный обработчик (Kohana::shutdown_handler()
), который просто завершит работу приложения после себя. А добавить свой хук ДО него (т.е. перед Kohana::init()
) не получится, т.к. к этому моменту модули еще не подключены. В общем, единственное на данный момент решение, найденное мной — использование промежуточной функции для редиректов:
protected function _redirect($url, $code = 302) { Event::run('system.redirect'); $this->request->redirect($url, $code); } |
Данный метод прописывается в одном из базовых классов-контроллеров, и в дальнейшем все редиректы должны идти не через $this->request->redirect()
, а через $this->_redirect()
.
Спасибо, очень толково
А вы не рассматривали порт Bmatt’а? (http://forum.kohanaphp.com/comments.php?DiscussionID=3142)
У меня, к сожалению, руки не дошли
Могу конечно ошибаться, но не вижу особенных отличий его Hooks от стандартного Event Что там портировать-то? Это обычный статический класс…
Да жалко что события убрали , а что говорили Shadowhand по поводу этого? Их добавят или нет?
Нет смысла их добавлять в ядро (как видим, bootstrap’а для этого более чем достаточно). Пользовательскими событиями не все пользуются, а все, что необязательно, должно быть выкинуто из ядра в модули. В общем, достаточно логичная точка зрения.
Сборка шаблона не происходит при Request::instance()->execute();, а значит это не аналог system.display, пример:
Request::instance()->execute();
Request::instance()->response = str_replace(‘foo’,'bar’,Request::instance()->response);
echo Request::instance()->send_headers()->response;
Если в шаблонах функция выводит текст ‘foo’ (или переменная), то str_replace ничего не обнаружит.
Хотя в 2.3.4 system.display обозначало, что Kohana::output содержит готовый к выводу html документ.
Всё равно как никрути, а события были сильной стороной коханы, к примеру теперь мне чтобы юзать идин из своих хелперов, всегда потребуется в буте писать вызов перед выводом. Т.е. пропала такая полезное свойство, написал раз и забыл.
P.s. плохо выразил мысль, ну надеюсь поняли о чём я.
Обычно Request::instance()->response содержит объект View, так что в чистом виде с ним str_replace проводить проблематично. Впрочем, Вы можете для своих целей сделать что-то вроде такого финта: