Создание простого плагина. Пошаговая инструкция для новичков. Часть 1

Данный материал предназначен для новичков в мире Livestreet, желающих разобраться с базовыми особенностями работы с системой плагинов.

Создадим простой плагин, который будет создавать объект «Book» из введенных пользователем данных и сохранять в базу данных. А на отдельной страничке — выводить все созданные объекты Book списком.

Начальные условия:
Название плагина — test
Страница для ввода данных книги: ваш_сайт_ls.tld/test
Страница для вывода списка книг: ваш_сайт_ls.tld/test/books
Поля объекта Book:
id
name — название книги;
author — автор книги;
description — описание книги.

1. Создаем каркас плагина
Переходим в папку
livestreet/engine/console/

cd livestreet/engine/console/

Запускаем скрипт для создания каркаса плагина:
php ls plugin new test

В результате выполнения создастся каркас нового плагина test и разместится в папке
livestreet/plugins


2. Конфигурируем плагин
Переходим в папку
livestreet/plugins/test
и редактируем файл
plugin.xml
в соответствии со своими потребностями (там все понятно).

3. Конфигурируем конфиг плагина
Переходим в папку
livestreet/plugins/test/config
, редактируем конфиг плагина
config.php


a)Конфигурируем роутер
После
$config = array();
добавляем следующий код(или раскоментируйте соотв. строку в комментарии):
Config::Set('router.page.test', 'PluginTest_ActionTest');

Таким образом, все запросы на domain.com/test будут попадать в обработчик PluginTest_ActionTest

b)Конфигурируем переменную таблицы
Перед
return $config;
добавляем:
// Таблицы плагина
$config['table']['test_table'] = '___db.table.prefix___test_table';

Таким образом мы сохранили в конфиге полное имя таблицы нашего плагина с учетом префикса. Можно обратиться к нему из любого места плагина через
Config::Get('plugin.test.table.test_table')


4. Создаем нашу таблицу test_table
Открываем из корня плагина скрипт
install.sql

После комментария вставляем следующий код:
CREATE TABLE IF NOT EXISTS `prefix_test_table` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(500) NOT NULL,
  `author` varchar(100) NOT NULL,
  `description` varchar(1000) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


Файл install.sql выполнится при активации вашего плагина из админки. Создастся ваша таблица test_table с префиксом, соотв. инсталляции LS.

5. Конфигурируем главный класс плагина
Данный класс отвечает за переопределения и наследования методов, прикрепление различных скриптов и стилей к плагину, а также, за функции активации и деактивации плагина из админки плагинов. Нас интересует последнее.

Открываем
livestreet/plugins/test/PluginTest.class.php

Переходим к части, ответственной за активацию плагина, метод Activate
Раскомментируем находящийся в нем код, или заменим своми. После редактирования метод должен выглядеть следующим образом:
// Активация плагина
    public function Activate() {
        if (!$this->isTableExists('prefix_tablename')) {
            $this->ExportSQL(dirname(__FILE__).'/install.sql');
        }
        return true;
    }

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

6. Конфигурируем Экшн
Экшин — это то, что будет выполнено при переходе по соотв. ссылке. За зависимость экшина от переданной ссылки отвечает роутер.
Упрощенно — роутер получает данные о запросе (строку запроса), разбивает ее на составляющие, и вызывает соответствующий экшин (функцию/метод).
Экшин принимает переданные данные, проверяет их, и, при корректности данных, выполняет какое-то действие, например, с помощью маппера делает запись в БД.
После выполненного действия, экшин должен вернуть результат выполненного действия. Это может быть ошибка, сообщение об успешном выполнении, и сам шаблон экшина. Результат выполнения Экшина передается клиенту в браузер.

Открываем файл экшинов плагина
livestreet/plugins/test/classes/actions/ActionTest.class.php


a) Регистрируем события(ивенты)экшина
Переходим в метод регистрации ивентов (событий экшина) RegisterEvent
Если вы ничего не изменяли, то там должен быть следующий код:
protected function RegisterEvent() {
        $this->AddEvent('index','EventIndex');
}


Этот код означает, что при переходе по ссылке livestreet.tld/test будет выполнен метод(функция) EventIndex

Если вы хотите добавить обработку иных uri, то просто зарегистрируйте свой экшин. Например, вы хотите обрабатывать uri вида
livestreet.tld/test/books
то просто дополните код:
protected function RegisterEvent() {
        $this->AddEvent('index','EventIndex');
        $this->AddEvent('books','EventBooks');
}

и создайте обработчик (метод/функцию) соответствующего ивента.

b) Создаем обработчик события(ивента)экшина
Обработчик — это то, что будет выполнено при наступлении(вызове) соответствующего события(ивента)экшина.
Представим, что нам необходимо обработать какой-то POST — запрос, например, с данными о книге.
Находим метод/функцию EventIndex класса ActionTest.class.php
Если вы ничего не изменяли, то там будет просто пустая функция. Дополним ее. И создадим обработчик для события EventBooks.

Содержание файла
livestreet/plugins/test/classes/actions/ActionTest.class.php

<?php


class PluginTest_ActionTest extends ActionPlugin {


    /**
     * Инициализация экшена
     */

    public function Init() {

        $this->SetDefaultEvent('index');

    }


    /**
     * Регистрируем евенты
     */

    protected function RegisterEvent() {

        $this->AddEvent('index','EventIndex');

        $this->AddEvent('books','EventBooks');

    }


    protected function EventIndex() {

        // Если форма с данными была отправлена:

        if (isPost('submit_book_save')) {
<br /><br />
            //Проверяем наличие и валидность секретного ключа
            $this->Security_ValidateSendForm();
            // Принимаем данные POST-запроса

            $post = $GLOBALS['_POST'];

            // Проверяем данные POST-запроса на корректность

            if(
                        !isset($post['book_name'],$post['book_author'],$post['book_description']) ||
                empty($post['book_name'])||empty($post['book_author'])||empty($post['book_description'])

            ) {

                //Добавляем сообщение об ошибке

                $this->Message_AddError('Ошибка!','Не все данные формы заполнены!');

                return;
            
              }

            // Передем данные методу модуля, где

            //создаем объект книги и сохраняем его в БД

            // PluginTest — наш плагин

            // Test - модуль Test плагина Test

            // createBook - метод (функция) модуля Test плагина Test

            $result = $this->PluginTest_ModuleTest_createBook($post);

            //Если при сохранении возникли какие-то ошибки и т.п., создаем сообщение об ошибке.

            if(!$result){

                //Добавляем сообщение об ошибке

                $this->Message_AddError('Ошибка!','Возникли проблемы при сохранении данных!');

                return;

            }

            // Добавляем сообщение об успешном сохранении книги

            $this->Message_AddNotice('Данные книги успешно сохранены!');

        }

        //Получаем данные о всех сохраненных книгах

        $aBooks=$this->PluginTest_Test_getBooks();

        //Передаем эти данные в шаблон. В шаблоне они будут доступны как объект 'books'

        $this->Viewer_Assign('aBooks',$aBooks);

    }



    protected function EventBooks() {

        //Получаем данные о всех сохраненных книгах

        $aBooks=$this->PluginTest_Test_getBooks();

        //Передаем эти данные в шаблон. В шаблоне они будут доступны как объект 'books'

        $this->Viewer_Assign('aBooks',$aBooks);

    }


    /**
     * Завершение работы экшена
     */

    public function EventShutdown() {


    }
}

?>



Задача Экшина — получить данные, проверить все ли необходимые данные получены, и передать управление Модулю. Потом вернуть результат клиенту в виде скомпилированного шаблона (странички) с вставленными данными результата выполнения.

UPD1:
В ивент экшина EventIndex добавлена проверка секретного ключа при получении данных формы. Спасибо PSNet

Часть 2

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

avatar
А видео сделать можете?
avatar
0_o Зачем? Серьезно, не понимаю ) Проще по тексту, чем всматриваться в видеоряд…
Все пошагово. «Копируй-вставляй»
avatar
Я бы посоветовал выложить архив с готовым плагином
avatar
Да, вечерком выложу если успею, или завтра.
avatar
Отлично! Очень не хватало простой инструкции по созданию плагинов! Спасибо.
avatar
Пожалуйста! Надеюсь, что пригодится. Она не претендует на полноту, многие возможности не освещены. Но для получения самых базовых навыков, думаю, пойдет )
avatar
Прочитал обе статьи, я думал как-то проще делается, честно говоря )
Не совсем понял вот этот шаг: «Запускаем скрипт для создания каркаса плагина:»
php ls plugin new test

Как именно производить «запуск»?

И еще, для хорошего мануала для разрабов не хватает глоссария — что есть что, списка понятий.
avatar
Это с консоли.
avatar
На счет скрипта:
Этот скрипт запускается из консоли(командной строки).
1. Открываете консоль (Для Windows: нажимаете сочетания клавишь win+r, затем в всплывшем окошке вводите команду cmd и нажимаете enter).
2. Дальше — по статье:
— переходите в дирректорию с размещенным скриптом по созданию плагина, для этого вводите команду:
cd livestreet/engine/console/

Где livestreet — дирректория, куда вы производили установку вашей копии Livestreet.

После перехода в эту дирректорию, необходимо запустить php в режиме cli (comand line interface, режим командной строки), и натравить его на наш скрипт с параметрами, делается это следующей командой:
php ls plugin new test


Где:
php — запуск php в режиме cli.
ls — имя скрипта.
plugin new — команда скрипта по созданию нового плагина.
test — параметр команды, название создаваемого плагина.


На счет глоссария, думаю, это дело именно самих разработчиков LS. Никто кроме них не сможет лучшим образом сформировать глоссарий :-) Хотя, практически все есть в документации на Livestreet для разработчиков, или в книжках по основам php+ООП.
avatar
Ок, а если сайт уже на вирт. хостинге? Или это инструкция для сайта работающего на локалке?
avatar
Инструкция для любого применения. Но вы же, надеюсь, не пишите код на виртуальном хостинге на «боевом сервере», правда ведь? :-)

Самая простая практика разработки:
Есть локальная машина, на которой проводится разработка
Есть боевой сервер, в вашем случае, ваш виртуальных хостинг.


После каки-то работ на локальной машине производите проверку результата и при успешном прохождении, синхронизируете с боевым сервером.

Вариантов синхронизации — множество, начиная от примитивного ftp, заканчивая различными современными средствами деплоя.

Идеальный вариант — если ваш боевой сервер и локальная машина идентичны. Достигается это, в самом простом варианте, применением виртуальных машин. Просто один и тот же образ виртуальной машины разворачиваете на локальной машине и боевом сервере...
В общем, тема достаточно обширная, думаю, если интересуетесь, почитайте Хабр или литературу по современным принципам и подходам в веб-разработке…
avatar
После:
php ls plugin new test

получил:
«php» не является внутренней или внешней командой, исполняемой программой или пакетным файлом.
Что не так, можете подсказать?

Скрин.
avatar
На какой системе работаете (OSX/WIN/LINUX/...)?

Необходимо настроить пути к исполняемому файлу PHP.
Если у вас OSX:
export PATH=/usr/local/php5/bin:$PATH

Если Win:
Читать тут.
avatar
Win7 + Denwer
avatar
Никогда не работал с Denwer, посмотрите, может данное решение поможет?
avatar
Спасибо, помогло!
avatar
// Принимаем данные POST-запроса
$post = $GLOBALS['_POST'];
Ну это уже слишком, никто адекватный с глобальными массивами не работает. Как минимум есть $_POST, но у лс есть своя обертка — getRequest
avatar
Можно, конечно, начать холивар по этому поводу, но, подскажите, в чем проблема использования глобальных переменных на чтение?
И в чем же сакральный смысл использования обертки getRequest, которая аналогично использует глобальные $_REQUEST, $_GET и $_POST так же на чтение?
Это не сарказм, мне просто действительно интересно. Спасибо за комментарий )
avatar
Потому что трогать глобальные переменные (неважно, чтение это или запись) — моветон если в этом нет крайней нужды (что бывает только если вы пишете свою цмс/фреймворк). Для этого были специально придуманы $_POST, $_GET, а потом и $_REQUEST

И в чем же сакральный смысл использования обертки getRequest, которая аналогично использует глобальные $_REQUEST, $_GET и $_POST так же на чтение?
Например, вместо $mVal = isset($_REQUEST['name']) ? $_REQUEST['name'] : null; или страшненького $mVal = @$_REQUEST['name']; можно писать просто $mVal = getRequest('name'); где вторым параметром дополнительно (если нужно) можно указывать тип запроса, а в третьем — значение по-умолчанию. Это + к удобству.

Также лс — это цмс/уже фреймворк, поэтому он предлагает средства, которые следует использовать. Можно ведь не писать модули, а накидать толстых контроллеров с пхп/скл/жс/хтмл внутри и запросами через обычные mysql_* и это будет работать, но какой код, какими словами его будуть называть те, кому будет честь его дорабатывать.

Это сродни тех же вопросов «а зачем шаблонизатор, если можно шаблоны на пхп+хтмл+жс все в кучу как в вордпрессе», «зачем орм, есть мапперы» и т.п.

Ответ один — это создает удобства при дальнейших доработках кода.

Есть негласные стандарты, наблюдаемые по коду.

А ещё есть getRequestStr в которой допущена ошибка, которую она своим существованием пыталась обойти для пхп 5.4 (string) array(), исправлена в лс 2.0…
avatar
Ок, спасибо за подробный ответ.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.