Пример создания модуля

Попробуем на примере создать очень простой модуль/дополнение для LiveStreet. Сразу стоит оговориться, что под модулем в LiveStreet подразумевается некая библиотека дополнительного функционала, а не какой то законченный блок функционала. Модуль это только его часть. Надеюсь понятно смог объяснить, а теперь приступим!

Создадим функционал сложения двух чисел, введённых пользователем. Причем если пользователь авторизован, то выводим удвоенную сумму.
Разделим этап разработки такого функционала на несколько этапов:
  • создание модуля, где собственно сложение и будет происходить
  • создание шаблона странички, где пользователь будет вводить данные и видеть результат
  • создание экшена сложения

Замечание: на самом деле для такого простого функционала, как суммирование двух чисел, модуль можно и не создавать, а производить сложение прямо в экшене. Но мы пойдем правильным путем с точки зрения архитектуры движка и создадим простой модуль.

Все модули находяться в каталоге /classes/modules/, каждый в своей директории. Если модуль системный, т.е. принадлежит ядру движка(даже не движа, а фреймворку), то он имеет префикс sys_. Все пользовательские модули, т.е. модули которые реализуют уже конкретный функционал сайта, не должны иметь этот префикс.
Итак, создаем в каталоге /classes/modules/ каталог mytest, а в нем файл Mytest.class.php следующего содержания(classes/modules/mytest/Mytest.class.php):
<?
class LsMytest extends Module {
	
	public function Init() {		
		
	}  
	
	public function Summ($iFirst,$iSecond) {
		$iResult=$iFirst+$iSecond;
		// если юзер авторизован то удваиваем сумму
		if ($this->User_IsAuthorization()) {
				$iResult=$iResult*2;
			}
		return $iResult;
	}
}
?>

Все модули должны быть унаследованы от базового абстрактного класса Module и должны иметь обязательный метод Init(), в котором происходит какая либо инициализация модуля при его запуске. В нашем случаи ничего инициализировать не нужно, просто оставляем метод пустым. Далее мы определяем метод Summ(), это и будет наш основной метод в модуле, который будет производить сложение чисел.

Теперь создадим экшен, для этого в каталоге /classes/actions/ создаем класс экшена ActionSumm.class.php:
<?
class ActionSumm extends Action {	
	public function Init() {		
		$this->SetDefaultEvent('summ');
	}
		
	protected function RegisterEvent() {		
		$this->AddEvent('summ','EventSumm');		
	}
		
	protected function EventSumm() {		
		//обрабатываем отправку формы
		$this->SubmitSumm();		
	}
	
	protected function SubmitSumm() {
		// если не было отправки формы то завершаем обработку
		if (!getRequest('submit_summ')) {
			return;
		}
		// проверяем корректность ввода чисел
		$bOk=true;
		if (!func_check(getRequest('summ_a'),'id')) {
			$this->Message_AddError('Параметр А не является числом','Ошибка');
			$bOk=false;
		}
		if (!func_check(getRequest('summ_b'),'id')) {
			$this->Message_AddError('Параметр B не является числом','Ошибка');
			$bOk=false;
		}
		if ($bOk) {			
			// вызываем метод модуля для подсчета суммы
			$iResult=$this->Mytest_Summ(getRequest('summ_a'),getRequest('summ_b'));
			$this->Message_AddNotice('Сумма: '.$iResult);
		}
	}	
}
?>

Каждый экшен наследуется от родительского класса Action. Обязательными методами являются Init() и RegisterEvent(), в Init() мы определяем какой евент будет запускаться по умолчанию(он у нас всего один), а в RegisterEvent() регистрируем все используемые евенты, т.е. привязываем к нему метод для обработки.
В SubmitSumm() проверяем на корректность введённые пользователем данные — два числа для сложения. Если числа введены корректно, то суммируем их.

Пришла очередь шаблона. Все шаблоны лежат в каталоге /templates/skin/название_шаблона/, шаблоны привязанные к экшенам лежат в соответствующих каталогах в /templates/skin/название_шаблона/actions/.
В нашем случаи это будет каталог /templates/skin/habra/actions/ActionSumm/, в нём создадим файлик шаблона summ.tpl:
{include file='header.tpl'}

{include file='system_message.tpl'}

<form method="POST">
	A: <input type="text" name="summ_a" value="{$_aRequest.summ_a}"> 
	B: <input type="text" name="summ_b" value="{$_aRequest.summ_b}"> 
	
	<input type="submit" name="submit_summ" value="Посчитать!">	
</form>

{include file='footer.tpl'}

Если название шаблона(Summ) совпадает с название евента в экшене, то этот шаблон автоматически подключится для вывода.

Теперь осталось разрешить наш экшен в файле роутинга config.route.php. Добавим новую строку в секцию «page»:
'summ' => 'ActionSumm',

Этим мы открываем доступ к нашей страничке вида наш_сайт/summ/

Всё, новый функционал движка готов к работе!
Надеюсь данный топик будет полезным любителям-копателям :)

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

avatar
Просто и доходчиво. Спасибо! )
avatar
один момент только — на работоспособность не проверял :)
  • ort
  • 0
avatar
Буду иметь ввиду. Как раз было желание сделать модуль «Компании» как на хабре… уж очень нужно.
avatar
А чем он так хорош? Для каких целей?
avatar
Дело не в том, хорош он или плох… дело в том, что для моего проекта нужен этот модуль… в ливстрите его нет…
avatar
Ну так вот как раз и интересно зачем он нужен? Я просто в нем смысла не особенно много вижу…
avatar
Проект который я делаю привязан к Казахстану. Я хочу чтобы был блок компании где ведется Рейтинг наших Казахстанских компаний. Одно дело когда вы видите названия компании к которым не имеете никакого отношения… другое дело когда большинство этих компаний находятся в вашем городе и вы быть может в них работали.
Опять же в контексте моего проекта это будет еще и гайдлайн по казахстанским компаниям.
комментарий был удален
комментарий был удален
avatar

Попробовал поставить простейший модуль, форма ввода 2-х чисел появляется, кнопка «Посчитать», при нажатии выдает такое:Fatal error: Uncaught exception 'Exception' with message 'Не найден класс модуля — Mytest' in /*******/classes/engine/Engine.class.php:95 Stack trace: #0 /******/classes/engine/Engine.class.php(140): Engine->LoadModule('Mytest', true) #1 /*******/classes/engine/Action.class.php(258): Engine->_CallModule('Mytest_Summ', Array) #2 [internal function]: Action->__call('Mytest_Summ', Array) #3 /******/classes/actions/ActionSumm.class.php(33): ActionSumm->Mytest_Summ('5', '8') #4 /*********/classes/actions/ActionSumm.class.php(13): ActionSumm->SubmitSumm() #5 /*******/classes/engine/Action.class.php(103): eval()'d code(1): ActionSumm->EventSumm() #6 /*******/classes/engine/Action.class.php(103): eval() #7 /******/classes/engine/Router.class.php(140): Action->ExecEv in /*******/classes/engine/Engine.class.php on line 95
avatar
небольшая ошибка: файл класса модуля должен называться не mytest.class.php, а Mytest.class.php
avatar
ну а чтобы пример заработал в версии 0.3, модуль нужно переименовать в LsMytest.php…
avatar
сорри, в LsMytest.class.php… ;)
avatar
Спасибо. Доступно. НО ещё нужно принципы работы с базой. Как правильно работать с этим:

oDb->select($sql) и т.д

Жду продолжения!
avatar
Спасибо, как пример — на отлично. Но побольше бы объяснений
avatar
Спасибо, буду начинать пробовать делать доску объявлений.
avatar
Добрый день. Делаю каталог программ.
Подскажите, пожалуйста, каким образом прописать в роутинг такие страницы:
/programm/
/programm/{prog_name}/
/programm/{prog_name}/{prog_version}/

на выходе урлы имеют такой вид:
/programm/
/programm/acemoney/
/programm/acemoney/1.2.3/

В методе
<code>protected function RegisterEvent() {
        $this->AddEvent('programm', 
           'EventProgrammList');
        $this->AddEventPreg('/^([0-9_A-Za-z-]+)$/i',
           'EventProgrammItem');
        $this->AddEventPreg('/^([0-9_A-Za-z-]+)$/i', 
           '^(([0-9]+)([0-9\.]+))+$/i',
           'EventProgrammItemVersion');
}</code>

делаю заглушки для эти методы, но третий урл не подхватывается, все время отрабатывает второй урл.
Мне не нужно конкретное решение этой проблемы, хочется понимания того, каким образом работает AddEventPreg.
<code>protected function RegisterEvent() {
        $this->AddEvent('programm', 
           'EventProgrammList');
        $this->AddEventPreg('/^регэксп_для_prog_name$/i',
           'EventProgrammItem');
        $this->AddEventPreg('/^регэксп_для_prog_name$/i', 
           '^регэксп_для_prog_version$/i',
           'EventProgrammItemVersion');
}</code>

Я правильно понимаю?
prog_name может быть только латинской буквой, подчеркиванием или тире.
prog_version: число или несколько чисел, разделенных точкой.

Но это не суть важно, главное понять принцип. В коде не смог его «прочесть», каюсь.
avatar
вот так:
<code>$this->AddEvent('_list','EventProgrammList');
$this->AddEventPreg('/^[a-z\-\_]+$/i','/^$/','EventProgrammItem');
$this->AddEventPreg('/^[a-z\-\_]+$/i','/^[a-z\-\_\.]+$/i','EventProgrammItemVersion');</code>

но чтоб не плодить разные евенты можно сделать так:
<code>$this->AddEvent('_list','EventProgrammList');
$this->AddEventPreg('/^[a-z\-\_]+$/i','/^([a-z\-\_\.]+)?$/i','EventProgrammItem');</code>
в таком случаи номер версии можно доставать в EventProgrammItem.

AddEventPreg работает очень просто — первый параметр это шаблон евента, а остальные шаблоны параметров
avatar
в шаблоне для номера версии пропустил числа =)
avatar
Повод добавить редактирование и удаление комментария автором? ;)
avatar
повод быть внимательным и пользоваться предпросмотром
avatar
да, и EventProgrammList нужно установить как дефолтный
avatar
ключевой момент — если для параметра нет шаблона то считается, что он может быть любым
avatar
Большое спасибо за обстоятельный ответ!
>>AddEventPreg работает очень просто — первый параметр это шаблон евента,
>>а остальные шаблоны параметров
Максим, в моем случае event — это будет название программы, а параметр это версия программы? Я правильно понял?
<code>$this->AddEventPreg('/^[a-z\-\_]+$/i','/^([a-z\-\_\.]+)?$/i','EventProgrammItem');</code>

причем судя по короткой записи параметр может как использоваться, так и нет.
Т.е. вышеописанное добавление event сработает
и при таком урл
<code>/programm/acemoney/</code>

и при таком урл
<code>/programm/acemoney/1.2.3/</code>

да?
avatar
да
avatar
Предлагаю идею: роутинг попробовать делать на основе полного урл, т.е.
<code>
$this->AddRoute('/programm/', 
  'ActionName1', 
  'EventMethod1');
$this->AddRoute('/programm/([0-9_A-Za-z-]+)/',
  'ActionName2', 
  'EventMethod2');
$this->AddRoute('/programm/([0-9_A-Za-z-]+)/([0-9][0-9\.]+)', 
  'ActionName3', 
  'EventMethod3');</code>

собирать в роутер все варианты урл, потом на входе смотреть — попадает ли запрашиваемый хотя бы под один шаблон зарегистрированных, далее получать из него название Action и запускаемого в нем EventMethod.
Прозрачность формирования роутинга увеличивается и многое упрощается.
Причем если будет зарегистрирован к примеру такой маршрут:
<code>$this->AddRoute('/([0-9_A-Za-z-]+)/',
  'ActionUser', 
  'EventShowUserCard');
</code>

для показа страницы пользователя, то получается у нас в верхнем уровне две страницы:
'/([0-9_A-Za-z-]+)/' и '/programm/', так вот при запросе урл '/programm/' сперва искать четкое совпадение, а потом по регуляркам сравнивать, и в данном примере выводить страницу со списокм программ, а не персональную карточку участника с ником «programm».
Подумайте на досуге.
avatar
роутер не должен знать ни о каких евентах и разных комбинаций экшенов, его задача должна быть максимально проста — запустить нужный экшен, а дальше вся логика в этом экшене.
делать экшен динамический не есть хорошо, этим мы накладываем сами себе ограничения — например, пользователь имеет право иметь логин «programm» и т.п.
Задача стоит в разделении задач, а не делать из них мишанину
avatar
делаю так
<code>
	protected function RegisterEvent()
	{
		$this -> AddEvent('enciclopedy','CarEnciclopedy');
		$this -> AddEventPreg('/.+/','Maker');
	}
</code>

на выходе имею
Fatal error: Uncaught exception 'Exception' with message 'Не найден шаблон: actions/ActionEnciclopedy/rty.tpl'

А как явно указать какой шаблон я хочу использовать?
avatar
avatar
Спасибо помогло.
*начал запоминать новое слово «поиск»*
avatar
Господа, а почему ошибка по Message_AddError вылезает 2 раза? И это касается не только этого, примерного модуля, а вообще.
Простой
if()$this->Message_AddError('Error','Ошиибка');


Выдает мне 2 предупреждения об ошибке
avatar
возможно system_message.tpl инклудится 2 раза
avatar
Да, действительно, если из header.tpl убрать строки
{if !$noShowSystemMessage}
			{include file='system_message.tpl'}
		{/if}

То вывод будет производится только 1 раз. Благодарю за помощь
avatar
из header.tpl убирать не нужно, лучше убрать дубликаты в шаблонах
avatar
Вы правы. Это логичнее. Кстати, в этом примере из файла Summ.tpl, я думаю, его тоже лучше убрать.
avatar
Мм… хотя в хабре он вроде и не инклудится в хеддере =)
avatar
сделал все действия но вылетела ошибка при нажатии на кнопку
Fatal error: Class 'LsMytest' not found in C:\apache\localhost\www\classes\engine\Engine.class.php on line 98

была похожая проблема выше «небольшая ошибка: файл класса модуля должен называться не mytest.class.php, а Mytest.class.php»

я исправил с большой буквы но это не помогло…
что может быль не так?
спасибо.
avatar
можно было бы посмотреть другие модули, находящиеся в папке
к примеру Topic.class.php, 25 строка
avatar
Подскажите что я сделал не так, вот ошибка:
Fatal error: Call to a member function _CallModule() on a non-object in Z:...\classes\engine\Action.class.php on line 268
avatar
Настройте правильно роуты и вызовы AddEvent(), AddEventPreg(), могу поспорить, что ошибка в них.
avatar
И еще вот что… прописал в роутере экшены, но почемуто когда в шаблоне прописываю — он не прописывается…
avatar
Добрый день. Ктонить может подсказать, как сделать чтобы по ссылке, например my_site/example/ выводилась php страница?
avatar
Прочитал статью. Мне 15 лет, для меня она немного сложновата. Здесь написано как создать модуль на всю страницу, а можете написать статью как написать модуль в виде блока сбоку на сайте? Просто недавно начал изучать пхп…

Написал отдельный пхп файл выводящий количество строк в таблице, теперь мне как-то нужно это вставить в livestreet
  • cvz
  • 0
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
avatar
А у меня такая ошибка —
Fatal error: Class 'ActionSumm' not found in Z:\home\livestreet-0-3-1\www\classes\engine\Router.class.php on line 167
. Что не так.
avatar
Если название шаблона(Summ) совпадает с название евента в экшене, то этот шаблон автоматически подключится для вывода.

А если не совпадёт, как указать какой именно шаблон выводить?
avatar
Например, ActionEvent = «a», а шаблон мне нужно вывести «b». Где установить шаблон «b»? В данный момент вижу вот что:

Fatal error: Uncaught exception 'Exception' with message 'Can not find the template: /var/www/site.ru/plugins/my/templates/skin/new/actions/MyAction/b.tpl' in /var/www/site.ru/engine/modules/viewer/Viewer.class.php:272 Stack trace: #0 /var/www/site.ru/engine/classes/Engine.class.php(334): eval()'d code(1): LsViewer->Display('/var/www/site...') #1 /var/www/site.ru/engine/classes/Engine.class.php(334): eval() #2 /var/www/site.ru/engine/classes/Router.class.php(340): Engine->_CallModule('Viewer_Display', Array) #3 [internal function]: Router->__call('Viewer_Display', Array) #4 /var/www/site.ru/engine/classes/Router.class.php(69): Router->Viewer_Display('/var/www/site...') #5 /var/www/site.ru/index.php(36): Router->Exec() #6 {main} thrown in /var/www/site.ru/engine/modules/viewer/Viewer.class.php on line 272
avatar
какой верный полный путь до шаблона b.tpl?
avatar
Шаблоны:
"/var/www/мой_сайт/plugins/test/templates/skin/new/actions/ActionTest/test.tpl",
"/var/www/мой_сайт/plugins/test/templates/skin/new/actions/ActionTest/test2.tpl",
Экшен "/var/www/мой_сайт/plugins/test/classes/actions/ActionTest.class.php":

class PluginTest_ActionTest extends ActionPlugin {

        public function Init() {        
                /*
                 * Событие по-умолчанию
                 */
                $this->SetDefaultEvent('test');
        }
        
        /*
        * Регистрируем все используемые события
        */
        protected function RegisterEvent() {                
                //$this->AddEvent('test','EventTest');
                $this->AddEventPreg('/^[a-z\-\_]+$/i','/^([a-z\-\_\.]+)?$/i','EventTest');
        }
                
        protected function EventTest() {
                
                $Param1 = Router::GetActionEvent();
                $Param2 = $this->GetParam(0);
                $Param3 = $this->GetParam(1);
                
                if ($Param1  = 'test') {
                        $this->SetTemplateAction('test');

                } else {
                        $this->SetTemplateAction('test2');
                }
        }
        
        /*
         * Завершение работы Action`a
         */
        public function EventShutdown() {
        }
}
avatar
и сообщение об ошибке сюда же
avatar
Вызываю мой_сайт/test/test77/

Fatal error: Uncaught exception 'Exception' with message 'Can not find the template: /var/www/мой_сайт/plugins/geo/templates/skin/new/actions/ActionTest/test77.tpl' in /var/www/мой_сайт/engine/modules/viewer/Viewer.class.php:272 Stack trace: #0 /var/www/мой_сайт/engine/classes/Engine.class.php(334): eval()'d code(1): LsViewer->Display('/var/www/мой_сайт...') #1 /var/www/мой_сайт/engine/classes/Engine.class.php(334): eval() #2 /var/www/мой_сайт/engine/classes/Router.class.php(340): Engine->_CallModule('Viewer_Display', Array) #3 [internal function]: Router->__call('Viewer_Display', Array) #4 /var/www/мой_сайт/engine/classes/Router.class.php(69): Router->Viewer_Display('/var/www/мой_сайт...') #5 /var/www/мой_сайт/index.php(36): Router->Exec() #6 {main} thrown in /var/www/мой_сайт/engine/modules/viewer/Viewer.class.php on line 272
avatar
avatar
Спасибо!
avatar
вот здесь trac.lsdev.ru/livestreet/browser/tags/0.3.1/classes/engine/Action.class.php описаны все методы для работы с экшенами
avatar
SetTemplateAction() не работает. Если указываю через SetTemplate(), то получается. Но это неправильно: SetTemplateAction() ведь предназначен для Action, почему он тогда не переопределяет шаблон в экшене плагина?
avatar
trac.lsdev.ru/livestreet/browser/tags/0.3.1/classes/actions/ActionPeople.class.php

protected function EventGood() {
162	        /**
163	         * Получаем статистику
164	         */
165	        $this->GetStats();       
166	        /**
167	         * Получаем хороших юзеров
168	         */
169	        $this->GetUserRating('good');   
170	        /**
171	         * Устанавливаем шаблон вывода
172	         */       
173	        $this->SetTemplateAction('index');   
174	    }  


я вас правильно понял?
avatar
Да, всё верно. Скачайте последнюю ревизию, создайте новый плагин в папке plugins в корне, активируйте его урл_сайта/admin/plugins/ и попробуйте переопределить шаблон вывода, как вы верно указали выше.
avatar
Оба метода — для установки шаблона. Только файл шаблона в разных местах может лежать — либо в корне скина, либо в подпапке экшена.

SetTemplate() — файл лежит в в общей папке шаблона: templates\skin\название_скина\

SetTemplateAction() — файл лежит в папке с шаблоном экшена: templates\skin\название_скина\actions\MyAction\
avatar
Да. Но в случае с экшеном плагина SetTemplateAction, похоже, игнорируется. Прочитайте мой комментарий выше 2randomtoy и попробуйте тоже проделать это, если не трудно. Хочу понять — у меня руки кривые или это баг…
avatar
Так, секундочку, мадмуазель, речь идет о 0.4? И не просто о модуле, а о плагине? Тогда я пас. Вышесказанное мной относилось к модулям в 0.3.1. Механизм плагинов 0.4 пока в работе и там может быть всякое. Слова Алексея: «Система плагинов еще дорабатывается. Как только закончу — напишу статью.»
avatar
Кто первым встает — того и тапки! =)
avatar
config.route.php

Нет такого
  • Dimas
  • 0
avatar
Актуально ли это для версии 1.0?
  • ARM
  • 0
avatar
Для версии 1,0,1 где роутинг вписать?
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.