Контент


События в Ko3

Если Вы начали делать более-менее сложное приложение на 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().

Google Bookmarks Digg Reddit del.icio.us Ma.gnolia Technorati Slashdot Yahoo My Web News2.ru БобрДобр.ru RUmarkz Ваау! Memori.ru rucity.com МоёМесто.ru Mister Wong

Опубликовано в напильник.

Теги: , , , , , .


Комментарии (6)

Будьте в курсе обсуждения, подпишитесь на RSS ленту комментариев к этой записи.

  1. Alexander Kupreev пишет:

    Спасибо, очень толково

    А вы не рассматривали порт Bmatt’а? (http://forum.kohanaphp.com/comments.php?DiscussionID=3142)

    У меня, к сожалению, руки не дошли :(

  2. BIakaVeron пишет:

    Могу конечно ошибаться, но не вижу особенных отличий его Hooks от стандартного Event ;) Что там портировать-то? Это обычный статический класс…

  3. ANT пишет:

    Да жалко что события убрали :( , а что говорили Shadowhand по поводу этого? Их добавят или нет?

  4. BIakaVeron пишет:

    Нет смысла их добавлять в ядро (как видим, bootstrap’а для этого более чем достаточно). Пользовательскими событиями не все пользуются, а все, что необязательно, должно быть выкинуто из ядра в модули. В общем, достаточно логичная точка зрения.

  5. Мимошёл пишет:

    Сборка шаблона не происходит при 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. плохо выразил мысль, ну надеюсь поняли о чём я.

  6. BIakaVeron пишет:

    Обычно Request::instance()->response содержит объект View, так что в чистом виде с ним str_replace проводить проблематично. Впрочем, Вы можете для своих целей сделать что-то вроде такого финта:

    $response = Request::instanse()->send_headers()->response;
    Event::run('system.display', $response);
    echo $response;



Можно включить подсветку кода: <code><pre lang="">...</pre></code>
Разрешены некоторые HTML теги

или используйте trackback.