Livestreet: дао разработчика - 2

Первая часть цикла

Дао — это действие, процесс, поток. Чем чаще вы смотрите на код других плагинов, тем больше вы понимаете, как делать свой.
Накачайте плагинов под 0.4.2 и расматривайте, что у них внутри. Без этого вы не сможете начать писать свои плагины.

Я сделал типовой шаблон, из которого удобно делать свои собственные плагины. Располагается он тут:
http://github.com/medar/livestreetplugin
Чтобы скачать его, надо нажать в правом верхнем углу кнопку «Download Source». Желающие могут форкать и вносить изменения.

В файле находится «рыба» плагина с названием abcplugin.

Cкопируйте папку abcplugin (так называется этот плагин) к себе в /plugins. Выберите имя своего плагина. Не делайте типовых названий — типа tags или user — подумайте, вдруг этот плагин пригодится кому-то другому. Чтобы не было конфликтов в названиях придумайте себе какой-нибудь префикс.
Переименуйте папку abcplugins, файл PluginAbcplugins.class.php и все упоминания Abcplugins на ваше имя плагина внутри этого файла.
Отредактируйте в файле plugin.xml название и описание вашего плагина.

Центральный файл плагина — abcplugin/PluginAbcplugin.class.php
В нем
— декларируются переопределения существующих методов стандартных классов (топиков, блогов и т.п.)
— декларируется делегирование, т.е. замена существующих методов старым способом. Сейчас для этого лучше использовать первый способ, а единственное, для чего сейчас нужен механизм делегирования — это для полной замены tpl шаблона на свой. Кстати, другим способом, нежели через делегирование, это сделать нельзя (поправьте, если я ошибаюсь).
class PluginAbcplugin extends Plugin {

    // Объявление делегирований (нужны для того, чтобы назначить свои экшны и шаблоны)
	public $aDelegates = array(
            /*
             * 'action' => array('ActionIndex'=>'_ActionSomepage'),
             * Замена экшна ActionIndex на ActionSomepage из папки плагина
             *
             * 'template' => array('index.tpl'=>'_my_plugin_index.tpl'), //
             * Замена index.tpl из корня скина файлом /plugins/abcplugin/templates/skin/default/my_plugin_index.tpl
             *
             * 'template'=>array('actions/ActionIndex/index.tpl'=>'_actions/ActionTest/index.tpl'), //
             * Замена index.tpl из скина из папки actions/ActionIndex/ файлом /plugins/abcplugin/templates/skin/default/actions/ActionTest/index.tpl
             */


    );

	// Объявление переопределений (модули, мапперы и сущности)
	protected $aInherits=array(
       /*
	    * Переопределение модулей (функционал):
	    *
	    * 'module'  =>array('ModuleTopic'=>'_ModuleTopic'),
	    *
	    * К классу ModuleTopic (/classes/modules/Topic.class.php) добавляются методы из
	    * PluginAbcplugin_ModuleTopic (/plugins/abcplugin/classes/modules/Topic.class.php) - новые или замена существующих
	    *
	    *
	    * Переопределение мапперов (запись/чтение объектов в/из БД):
	    *
	    * 'mapper'  =>array('ModuleTopic_MapperTopic' => '_ModuleTopic_MapperTopic'),
	    *
	    * К классу ModuleTopic_MapperTopic (/classes/modules/mapper/Topic.mapper.class.php) добавляются методы из
	    * PluginAbcplugin_ModuleTopic_EntityTopic (/plugins/abcplugin/classes/modules/mapper/Topic.mapper.class.php) - новые или замена существующих
	    *
	    *
	    * Переопределение сущностей (интерфейс между объектом и записью/записями в БД):
	    *
	    * 'entity'  =>array('ModuleTopic_EntityTopic' => '_ModuleTopic_EntityTopic'),
	    *
	    * К классу ModuleTopic_EntityTopic (/classes/modules/entity/Topic.entity.class.php) добавляются методы из
	    * PluginAbcplugin_ModuleTopic_EntityTopic (/plugins/abcplugin/classes/modules/entity/Topic.entity.class.php) - новые или замена существующих
	    *
	    */
		

    );

	// Активация плагина
	public function Activate() { 
		/*
        if (!$this->isTableExists('prefix_tablename')) { 
			$this->ExportSQL(dirname(__FILE__).'/install.sql'); // Если нам надо изменить БД, делаем это здесь.
		}
		 */
		return true;
	}
    
	// Деактивация плагина
	public function Deactivate(){
        /*
		$this->ExportSQL(dirname(__FILE__).'/deinstall.sql'); // Выполнить деактивационный sql, если надо.
		 */
    }


	// Инициализация плагина
	public function Init() {
		// $this->Viewer_AddMenu('blog',Plugin::GetTemplatePath(__CLASS__).'/menu.blog.tpl'); // например, задаем свой вид меню
	}
}


В папке config находится конфиг плагина. Там переопределяются уже существующие переменные и добавляются свои. Там вы, в частности, можете переопределить роутинг, добавив свои урлы, если в плагине будете делать свои экшны.
/**
 * Конфиг
 */

// Переопределить имеющуюся переменную в конфиге:
// Config::Set('router.page.somepage', 'PluginAbcplugin_ActionSomepage'); // Переопределение роутера на наш новый Action - добавляем свой урл  http://domain.com/somepage

// Добавить новую переменную:
// $config['per_page'] = 15;
// Эта переменная будет доступна в плагине как Config::Get('plugin.abcplugin.per_page')

return $config;


В папке classes/hook лежит пример объявления хуков — в моделях, в заданной точке движка, а также делегирующие хуки (которые сейчас нужны только для темплейтов, см. выше).
class PluginAbcplugin_HookAbcplugin extends Hook { 
	
	
	public function RegisterHook() {
		
		// Регистрация событий на хуки
		
		/*
		 * Хук в начало функции AddTopic() в модуле Topic (файл /classes/modules/topic/Topic.class.php , если этот модуль не переопределен в других плагинах):
		 *
		 * $this->AddHook('module_topic_addtopic_before','func_topic_addtopic_before');
		 *
		 * Будет вызвана функция func_topic_addtopic_before($aVars) , где $aVars - НЕассоциативный массив аргументов, переданных этой функции.
		 * Передача результата в функцию AddTopic() делается путем изменения аргументов по ссылке - например, &$aVars[0]
		 */

		/*
		 * Хук в конец функции AddTopic() в модуле Topic (файл /classes/modules/topic/Topic.class.php , если этот модуль не переопределен в других плагинах):
		 *
		 * $this->AddHook('module_topic_addtopic_after','func_topic_addtopic_after');
		 *
		 * Будет вызвана функция func_topic_addtopic_after($Var) , где $Var - это то, что возвращает AddTopic() (т.е. или false или объект топика $oTopic)
		 * Функция должна завершаться при помощи return $Var
		 */

		/*
		 * Хук в конкреное место движка
		 *
		 * $this->AddHook('init_action','func_init_action', __CLASS__, -5);
		 *
		 * Приоритет для вызова хука = -5. Этот приоритет так же можно указывать и в хуках на модели.
		 * Будет вызвана функция func_init_action($Var) в том месте движка, где стоит данный хук
		 */

		/*
		 * Хук с делегированием
		 *
		 * $this->AddDelegateHook('module_topic_addtopic_before','func_topic_addtopic_new',__CLASS__);
		 *
		 * Полная подмена функции AddTopic() модуля Topic на свою.
		 * Будет вызвана функция func_topic_addtopic_new($Var), где $aVars - НЕассоциативный массив аргументов.
		 * Делегирование существует в движке только для обеспечения совместимости со старыми плагинами, рекомендуется вместо него использовать переопределение.
		 */

	}

}

В папке classes/mоdules лежат модули, которые вы используете в плагине — полностью свои, или частично или полностью переопределенные системные. Внутри находятся папки entity и mapper, если таковые нужны. Обратите внимание на формат названия классов внутри — это важно, его надо придерживаться:
Если мы изменяем модуль Topic в плагине Abcplugin:
class PluginAbcplugin_ModuleTopic extends PluginAbcplugin_Inherit_ModuleTopic — в центральном файле модели
class PluginAbcplugin_ModuleTopic_EntityTopic extends PluginAbcplugin_Inherit_ModuleTopic_EntityTopic — в файле сущности
class PluginAbcplugin_ModuleTopic_MapperTopic extends PluginAbcplugin_Inherit_ModuleTopic_MapperTopic — a файле маппера
В этой папке уже лежит две «рыбы» моделей — для совсем новой (somepage) b существующей (topic). Приводить их тут не буду.

В папке classes/actions находятся экшны, т.е. классы, отрабатывающие различный функционал по заданному урлу, например, domain.com/somepage. Роутинг (связь урлов с экшнами) задается в config.php плагина. У каждого экшна есть евенты, которые вешаются на «внутренние» урлы — domain.com/somepage/add, domain.com/somepage/edit и т.д. Связь эвентов с этими урлами задается в функции RegisterEvent() соответствующего экшна.
В папке лежит пример экшна somepage.
class PluginAbcplugin_ActionSomepage extends ActionPlugin {

	/**
	 * Инизиализация экшена
	 *
	 */
	public function Init() {
	}

	/**
	 * Регистрируем евенты, по сути определяем УРЛы вида /somepage/.../
	 *
	 */
	protected function RegisterEvent() {
		//$this->AddEvent('add','EventAdd'); // урл /somepage/add
	    //$this->AddEvent('edit','EventEdit'); // урл /somepage/edit
	}

}


В папке templates/language лежат файлы локализаций. Изменения накладываются поверх существующих файлов — можно изменять существующие дефолтные сообщения. Файлы подгружаются автоматом.

В папке templates/skin/default находятся файлы шаблона, применяемые в плагине.

На этом пока все. Кстати, в прошлом посте я забыл упомянуть очень полезную статью по написанию плагинов, я сам её нашел только что:
livestreet.ru/blog/dev_documentation/3710.html.

Попробуйте сделать какой-нибудь свой мелкий плагин (или видоизменить чужой) уже сегодня.

16 комментариев

avatar
B хотел бы сразу начать дискуссию вот по какому вопросу — для чего на данный момент в движке нужно делегирование? Я упомянул в посте, что оно нужно только для конструкция вида
public $aDelegates = array(
            'template' => array('index.tpl'=>'my_plugin_index.tpl')
    );
в главном файле плагина, т.е. для замены файла шаблона. Это так, или оно еще для чего-то нужно? Или про него можно совсем забыть?
avatar
template, modules, entity, mapper, action.
Например сейчас пишу плагин, в котором используется такая конструкция
protected $aDelegates=array(
            'action' => array('ActionIndex'=> '_ActionTest'),
            'template'=>array('actions/ActionIndex/index.tpl'=>'_actions/ActionTest/index.tpl')

    );

попробуйте ответить на вопрос, что в этом куске кода я делаю, и вам станет понятно, для чего необходимо делегирование
avatar
Ну да, действительно, про экшны забыл. Получается, экшны и шаблоны — для этого нужно делегирование. А остальное (модели-мапперы-сущности) лучше делать переопределением ( protected $aInherits=array() ). Так?

А в делегировании template точно такой синтаксис, с путями и подчеркиванием?
avatar
Delegate заменяет, а Inherit наследует. также можно заменить послностью и класс (допустим вам полностью нужно заменить GetTopicById). но я с таким еще не сталкивался.
avatar
Кстати, переназначать экшны можно
avatar
переназначение и есть делегирование. или вы про inherits?
avatar
Да, хотел написать про наследование экшнов, но понял, что с этим в движке пока непонятно. Лучше во избежание проблем для экшнов пока юзать делегирование.
avatar
Наследование Экшена в принципе простое. как сказал Максим, livestreet.ru/blog/dev_documentation/4499.html#comment71298, наследование работает при воссоздании структуры экшена.
avatar
сорри за мусор, нажал какой-то хоткей, текст из формы ответа случайно запостился. Нужна фича редактирования комментов!
avatar
А кто как подключает свои стили и js файлы в плагины?
avatar
+1 за прототип!

мне кажется что в движке не хватает дефолтного «создателя» пустых плагинов, что-то типа scaffold, думаю что можно легко организовать на уровне шелл скрипта
avatar
Кодогенератор типа yiic/gii в Yii? Да, можно для прикола, но и без него пока можно жить, в принципе.
avatar
жить можно, конечно, но это тупо для экономии времени
avatar
надо нажать в левом правом углу
  • Wave
  • 0
avatar
:))
Поправил.
avatar
в инициализацию плагина можно добавить

$this->Viewer_AppendStyle(Plugin::GetTemplatePath('PluginAbcplugin')."/css/style.css"); // Добавление своего CSS
$this->Viewer_AppendScript(Plugin::GetTemplatePath('PluginAbcplugin')."/js/spoiler.js"); // Добавление своего JS
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.