Скажем хакам "нет!" или как использовать плагины

Часто возникают ситуации, когда надо вставить свои изменения в чужой код. Причем такие изменения которые не будут там постоянно, а нужны толко на какое-то время. Например на время конкурса.
Обычно это делается при помощи if'ов, констант и других тем (т.н. «Хаки»). Код становится запутанным, непонятным. Можно обойтись и без этого.

Расскажу про конкретный случай — «конкурс блогов», который проходил на Wsetut.uz.

Мне надо было сделать следующие вещи:
1) расставить банеры по странице блогов
2) добавить в блоги страницу на которой будут выводится записи дневников, что участвуют в конкурсе.
3) Добавить пункт в меню для этой страницы
4) Сделать так чтоб запись дневника вылетала из конкурса если ее изменили

Можно было наставить ифов, но т.к. рибозз легких путей не ищет, я решил сделать это внеся минимальные изменения в код блогов. (Зверски не люблю всякие хаки-буки).
Я использовал паттерн plugin.

Создаем новый плагин

Все плагины лежат в папке inc/framework/plugins.
Файл плагина называется "[plugin_name].php". Например BlogContest.php.
Класс должен объявлятся внутри этого файла. К названию плагина добавляется преикс Plugin_:

class Plugin_BlogContest extends Plugin_Base { ... }


Чтоб включить плагин, надо добавить вызов Rate_Core::load_plugin в файл inc/framework/plugins.inc.php:

Rate_Core::load_plugin('BlogContest');

Вместо BlogContest нужно написать имя своего плагина.

Можно включать выключать его в зависимости от какого-то условия. Например так:

$contest_data = cache_get_data('contest_system_data', 1);

// конкурс блогов
if ($contest_data['type_id'] == 15) {
	Rate_Core::load_plugin('BlogContest');
}


Библиотека событий

Плагин встраивается в чужой код посредством событий («Events»). Для этого во фреймыорке создана специальная библиотека «Events».

Работает это так:

1) Регистрируется обработчик события:

Event::addLast('SomePluginName', 'Some_EventName');

можно также использовать ::addFirst, чтоб добавить событие не в конец очереди а в начало (обработчиков может быть несколько).

2) Где-то в коде просиходит вызов события:

Event::call('Some_EventName', $params)

$params — совершенно произвольный массив с параметрами. Он передасятся в функцию обработки события.

3) Если зарегистрирован хотябы один плагин обрабатывающий это событие, то происходит вызов события:


class SomePluginName extends Plugin_Base {
    function event_SomeEventName(Array $args)
	{
                // обработка события
	}
}


Добавляем банеры

ЧТоб прочуствоват что это за вешь такая, попробуем воткнуть два очарователных банера с назойливой рекламой на страницу блогов.
Вставить надо его в самый верх страницы, поэтому лезем в самый главный щаблон блогов — «view/blog/index_one_column.php».
В самом верху вставляем вот такой вот код:

<?=Event::call('Blog_HtmlBeforeMainPinkBlock')?>


Все, событие мы добавили. Запоминаем это название и топем в наш центр управления полетами — «plugins/BlogContest.php».

создаем конструктор и регистрируем обработчик события:


        /**
	 * Конструктор подгружает эвенты, необходимые для работы плагина
	 */
	public function __construct()
	{
		parent::__construct();
		
		// Сюда воткнем верхний банер для конкурса
		Event::addLast(__CLASS__, 'Blog_HtmlBeforeMainPinkBlock');
        }


Создаем функцию обработчи события:


        /**
	 * Вставляем верхний баннер конкурса
	 */
	function event_Blog_HtmlBeforeMainPinkBlock() 
	{
		$content = new View('plugins/blog_contest/top_blog_banner');
		return $content->render();
	}


Все теперь в блогах появится банер. Хе-хе-хе.

NB! Как обработчиков так и событий с одинаковыми именами может быть несоклько.

Более сложный пример — добавление новой страницы в блоги

В блог контроллере выбор страницы происходит по параметру $mode примерно вот так:


		} elseif($mode == 'popular') { // Популярные блоги
			
			if($page == 1) {
				$blog_list = cache_get('popular_blogs', 1);
			}

Инжектим свой if:

}
		elseif(Event::exists('Blog_ListRequested_'.$mode)) {
			
			$blog_list = Event::call('Blog_ListRequested_'.$mode, compact('page','page_size','extra'));
			
		} elseif($mode == 'popular') { // Популярные блоги
			
			if($page == 1) {
				$blog_list = cache_get('popular_blogs', 1);
			}


Последний параметр — кое-какие вещи необходимые при выводе страницы — они передадутся обработчику в качестве массива $args.
Этот код будет одинаковый для всех плагинов которые могут появится здесь в будущем.

«Event::exists('Blog_ListRequested_'.$mode)» — проверяет есть ли обработчик для этого события (::call Тоже выполнет эту проверку, но здесь нам важно знать точно есть оно или нет, еще до запуска события).

$mode — какая-то строка-параметр приходящая с Url'a.
Я выбрал себе $mode == 'contest'.
Поэтому регистриую обработчик события «Blog_ListRequested_contest» и добавляю код обработчика события, который и выведет страницу:

/**
	 * Выдает список блогов для странички конкурса
	 * @param $blog
	 */
	function event_Blog_ListRequested_contest(Array $args) 
	{
		if($args['page'] == 1) {
			//$blog_list = cache_get('popular_contest_blogs', 1);
		}
                .....
		
		return $blog_list;
	}


Советую заглянуть в код — достаточно забавная функция.

Похожим методом действует событие «Blog_ListTitleRequested_contest». Кому интересно может по этой строке поискать в коде блог контроллера.

Добавление странички в верхнее меню

Сразу код из inc/templates/top_menu.php:

/**
	* NB! Оюработчик Event Top_MenuRender изменяет структуру меню!!!
	*/

	// смотри framework/plugins
	// Вызывает обработчики события, если они были установлены
	if (class_exists('Event') && Event::exists('Top_MenuRender')) {
		$variables_to_overwrite = array(
			'menu_rel',
			'menu_1_selected',
			'menu_2_level',
			'menu_3_level_rel',
			'menu_3_level',
			'WALK',
		);
		$ret = Event::call('Top_MenuRender', compact($variables_to_overwrite));
		extract($ret);
	}


Тут ничего магического не происходит. Переменные передаются в обработчик, там меняются (вставляется новый пункт меню) и возвращаются обратно.

Вот сам обработчик события:

/**
	 * Добавляет новый пункт в главное менб сайта
	 */
	function event_Top_MenuRender(Array $vars) 
	{
		$blog_contest_link     = '/blog/contest';
		$blog_contest_page_id = 'PluginBlogContest1';
		
		if (substr($_SERVER["REQUEST_URI"],0,strlen($blog_contest_link)) == $blog_contest_link) {
			$vars['WALK']['child_pageid'] = $blog_contest_page_id;
		}
		
		$menu_item_id = count($vars['menu_2_level']);
		
		$vars['menu_2_level'][$menu_item_id] = array(
			'name' => $this->contest_data['contest_name'],
			'page' => $blog_contest_page_id,
			'icon' => '29',
			'file' => 'blog/contest/'
		);
		
		$vars['menu_rel'][5][] = $menu_item_id; 
		
		return $vars;
	}


Кому интересно ковыряйте следующие файлы:

inc/framework/framework.lib.php
inc/framework/libraries/Event.php
inc/framework/plugins/*

Там найдете подробности работы всего этого добра. При необходимости можно вносить изменения.

Да прибудет с вами сила Крона!!!
  • 0
  • 3 декабря 2009, 13:45
  • RiboZz

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

RSS свернуть / развернуть

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.