===== Пошаговое осваивание фреймворка ABillS =====
** Писалось для версии 0.77.14 **\\
В данном руководстве, Вы пошагово ознакомитесь с основными частями системы.\\
Фреймворк состоит из нескольких частей:\\
* Ядро вебинтерфейса (движок) (''index.cgi'')
* Работа с БД (''dbcore.pm'')
* Работа с визуализацией
* Базовые библиотеки
* Файл конфигурации
Сначала рассмотрим составные части модуля, чтобы понимать, что нужно для написания логически интегрированного функционала.\\
В ABillS основная часть кода написана в функциональном или процедурном стиле, что влияет на работу с системой. Кроме того, поскольку ООП не используется для полиморфизма или расширения функционала классов через наследование, многие функции принимают аргумент $attr, в котором записаны дополнительные условия выполнения (которые могут кардинально изменять как результат так и логику выполнения), поэтому нужно всегда учитывать полную сигнатуру вызова при чтении кода. \\
==== Структура модуля ABillS ====
Модуль ABillS (для примера ''Example'') состоит из:
* Класса менеджера сущностей в БД. (''Abills/mysql/Example.pm'')
* Файл с логикой (функциями) ''Abills/modules/Example/webinterface''
* Описание меню ''Abills/modules/Example/config''
* Дополнительные файлы словарных переменных (необязательно) (''Abills/modules/lng_english.pl'')
* Файла описания схемы БД (''db/Example.sql'')
Теперь, когда мы рассмотрели какие части должны быть в самом модуле, детально рассмотрим взаимодействие модуля с частями фреймворка.\\
==== Регистрация модуля в движке ===
После добавления имени модуля в массив ''@MODULES'' (**libexec/config.pl**), при инициализации движка читается файл ''config'' из папки модуля и словарь с переменными текущего языка пользователя вебинтерфейса. Функции из ''config'' добавляются в глобальные реестр функций интерфейса, при этом каждой присваивается особый числовой индекс, который позволяет вызвать эту функцию.\\
Сами функции должны быть доступны в ''webinterface'' (или быть импортированы из других пакетов внутри ''webinterface'')\\
Поскольку все ''webinterface'' выполняются в глобальной области видимости, к имени каждой функции нужно добавлять имя модуля.\\
==== Основная часть модуля - вебинтерфейс ====
В основном случае, логика вебинтерфейса проста и прозрачна - получить данные, обработать и вывести в каком-то виде (шаблон или таблица).\\
Фреймворк неявно (через глобальную область видимости) передаёт в ''webinterface'' следующие переменные:
^ Имя ^ Описание ^
|%LANG |хеш-масив словаря |
|%FORM |хеш-масив значений переданных на страницу (GET или POST запросом) |
|$html |Обьект визуализации (экземпляр класса ''Abills::HTML'') |
|$users |Менеджер работы с пользователями (экземпляр класса ''Users''). **Использовать только в функциях админ. интерфейса.** |
|$db |Соединение с БД |
|$admin |Менеджер работы с администраторами (экземпляр класса ''Admins'')|
|%conf |хеш-масив конфигурационного файла |
Для примера рассмотрим работу с сущностью ''entity'' в модуле ''Example''\\
Получить данные можно несколькими способами:
* Из БД (ссылка на работу с БД)
* Из внешнего источника (здесь ссылка на web_request)
* Из файловой системы
* Из других модулей
Для CRUD операций в ABillS принято использовать одну отдельную функцию в которой происходят следующие операции:
* Добавление новой сущности
* Редактирование сущности
* Удаление сущности
* Отображение списка сущностей
* Отображение сущности (совмещено с редактированием)
Если используется работа с БД, то внутри файла ''webinterface'' инициализируется обьект менеджера работы с сущностями.
use Example; # Загрузить файл /usr/abills/Abills/mysql/Example.pm
my $Example = Example->new($db, $admin, \%conf); # Создать объект менеджера
В коде функция работы с сущностями будет выглядеть так:
#**********************************************************
=head2 entity_example_main()
=cut
#**********************************************************
sub entity_example_main{
# Хеш для переменных шаблона обьявляется в области видимости функции
my %template_args = ();
# Флаг отображения шаблона
my $show_template = $FORM{add_form} || 0;
# Здесь используется глобальный хеш %FORM,
# который доступен в глобальной области видимости
# и включает значения, полученные из GET или POST запроса.
if ($FORM{add}) {
$Example->entity_add({%FORM});
$show_template = !show_result($Example, $lang{ADDED});
}
elsif ($FORM{change}) {
$Example->entity_change({%FORM});
show_result($Example, $lang{CHANGED});
$show_template = 1;
}
elsif ($FORM{chg}) {
my $entity_info = $Example->entity_info($FORM{chg});
if (!_error_show($Entity)) {
%template_args = %{$entity_info};
$show_template = 1;
}
}
elsif ($FORM{del} && $FORM{COMMENTS}) {
$Example->entity_del({ ID => $FORM{del}, COMMENTS => $FORM{COMMENTS} });
show_result($Example, $lang{DELETED});
}
# Использование этой точки выхода
# позволяет использовать эту же функцию
# только для выполнения операции (например AJAX запросом)
return 1 if $FORM{MESSAGE_ONLY};
# Здесь собрана логика обработки данных для отображения шаблона
if ($show_template) {
# Отображение шаблона
$html->tpl_show(
_include('example_entity', 'Example'),
{
%TEMPLATE_ARGS,
%FORM,
SUBMIT_BTN_ACTION => ($FORM{chg}) ? 'change' : 'add',
SUBMIT_BTN_NAME => ($FORM{chg}) ? $lang{CHANGE} : $lang{ADD},
}
);
}
# Использование этой точки выхода
# позволяет использовать эту же функцию
# для отображения шаблона изменения внутри модального окна
return 1 if ($FORM{TEMPLATE_ONLY});
# Использование библиотеки Abills::ResultFormer
# для получения списка из БД (метод $Example->entities_list($attr)) и построения таблицы (Abills::HTML->table($attr))
my Abills::HTML $table; ($table) = result_former(
{
INPUT_DATA => $Example,
FUNCTION => 'entities_list',
BASE_FIELDS => 0,
DEFAULT_FIELDS => 'ID,NAME,VALUE',
FUNCTION_FIELDS => 'change,del',
SKIP_USER_TITLE => 1,
EXT_FIELDS => 0,
EXT_TITLES => {
id => '#',
name => $lang{NAME},
value => $lang{VALUE},
},
TABLE => {
width => '100%',
caption => $lang{ENTITY},
ID => 'ENTITIES_TABLE',
EXPORT => 1,
MENU => "$lang{ADD}:index=$index&add_form=1:add"
},
MAKE_ROWS => 1,
SEARCH_FORMER => 1,
MODULE => 'Example',
}
);
# Таблицу нужно выводить отдельно
print $table->show();
# Сообщаем движку, что функция завершилась нормально
return 1;
}
==== Работа с БД ====
Все классы работы с БД наследуются от ''dbcore''. \\
В таком случае в классе становятся доступны следующие методы:\\
| query($query, $type, $attr) | выполнение запроса к БД ( В основном используется для операции ''SELECT'' )|
| changes($table, $data, $attr) | обёртка над query("UPDATE ..."). Сравнивает данные в таблице и изменяет только поля с обновлёнными значениями. Может добавлять в системный лог записи об изменении|
| query_add($table, $data, $attr) | обёртка над query("INSERT ..."). Добавляет данные в таблицу, инкапсулирует логику обработки значений некоторых типов (''ip'', ''netmask'', ''attachment'', ''reply'', ''text''...)|
| query_del($table, $data, $extended_params, $attr) | обёртка над query("DELETE ..."), В нормальном случае используется для удаления строки с id = $data->{ID}|
| search_former($search_columns, $attr) | специальный метод формирования WHERE части запроса.|
Все эти методы должны вызываться в объекте с заданными полями ''conf'', ''db'', ''admin'' (''$self->{db}'', ''$self->{conf}'', ''$self->{admin}''). \\
Конструктор в общем случае должен реализовать как минимум этот функционал
#**********************************************************
=head2 new($db, $admin, \%conf) - Constructor for Example
=cut
#**********************************************************
sub new{
my ($class, $db, $admin, $CONF) = @_;
my $self = {
db => $db,
admin => $admin,
conf => $CONF
};
bless($self, $class);
return $self;
}
Рассмотрим работу с каждым из унаследованных методов детальнее.
CREATE TABLE `example_entity` (
`id` INT UNSIGNED PRIMARY AUTO_INCREMENT,
`name` VARCHAR(40) NOT NULL,
`value` SMALLINT(6) NOT NULL DEFAULT
);
^ id ^ name ^ value ^
| 1 | name1 | 101 |
| 2 | name2 | 102 |
=== query($query, $type, $attr) ===
Метод query() выполняет запрос к базе и в зависимости от аргумента $type получает результат и в зависимости от значений в $attr применяет к нему некоторые преобразования.\\
Рассмотрим примеры запросов и результат выполнения.
$self->query("SELECT * FROM example_entity");
Результатом выполнения будет запись в ''$self->{list}'' двумерного масива содержимого таблицы ''example_entity''.
$self->query("SELECT * FROM example_entity", undef, { COLS_NAME => 1 })
Здесь в качестве $type мы указываем ''undef'' для получения данных из базы. ''$attr->{COLS_NAME} => 1'' говорит, что мы хотим получить результаты в виде масива хешей. Результатом выполнения будет запись в $self->{list} масива хешей, где ключами хеша будут названия столбцов таблицы, а значениями - соответственно значения.\\
Поскольку структура таблицы (порядок столбцов в таблице) может меняться, использование ''COLS_NAME'' предпочтительнее (Читать как: **Использовать всегда и везде при получении списков базы**)
Следующий пример удобен, когда в коде нам нужно будет сформировать простой список выбора или поисковую таблицу ключ - значение. (Например, по id строки)
$self->query("SELECT id,name FROM example_entity", undef, { LIST2HASH => 'id,name' });
Результатом выполнения запроса будет запись в ''$self->{list_hash}'' хеша, где ключ ''id'' строки, а значение ''name''.\\
Теперь рассмотрим ключ ''COLS_UPPER''.
$self->query("SELECT * FROM example_entity", undef, { COLS_NAME => 1, COLS_UPPER => 1 })
Использование этого ключа связано с системой шаблонов, по утверждённому стандарту, названия столбцом таблицы указываются в __lowercase__, а переменные шаблона указываются в __UPPERCASE__. Таким образом, для передачи данных из БД в шаблон, пришлось бы вручную переназначать переменные при передаче в шаблон. Ключ ''COLS_UPPER'' дублирует ключи в хеше в в виде UPPERCASE, что позволяет передавать строки результата в шаблон без дополнительной логики.
==== Создание базовой страницы ====
Создаем базовую страницу сервиса
''cgi-bin/hello.cgi''
#!/usr/bin/perl
=head1 NAME
Hello world
=cut
use strict;
use warnings;
# Включение нужных путей
BEGIN {
our $libpath = '../';
my $sql_type = 'mysql';
unshift(@INC,
$libpath . "Abills/$sql_type/",
$libpath . "Abills/modules/",
$libpath . '/lib/',
$libpath . '/Abills/',
$libpath
);
}
#Модуль конфигурации
use Conf;
our (
$libpath,
%conf,
%lang,
$base_dir,
);
# конфигурационный файл
do "../libexec/config.pl";
# HTML визуализация
use Abills::HTML;
my $html = Abills::HTML->new(
{
IMG_PATH => 'img/',
NO_PRINT => 1,
CONF => \%conf,
CHARSET => $conf{default_charset},
}
);
# Подключение базы
use Abills::SQL;
my $db = Abills::SQL->connect($conf{dbtype}, $conf{dbhost}, $conf{dbname}, $conf{dbuser}, $conf{dbpasswd}, {
CHARSET => ($conf{dbcharset}) ? $conf{dbcharset} : undef
});
# Включение базовых словарей
if($html->{language} ne 'english') {
do $libpath . "/language/english.pl";
}
if(-f $libpath . "/language/$html->{language}.pl") {
do $libpath."/language/$html->{language}.pl";
}
# Подключение модуля работы с шаблонами
require Abills::Templates;
# Включение конфигурационного файла
Conf->new($db, undef, \%conf);
$html->{METATAGS} = templates('metatags_client');
print $html->header();
# Диалоговое окно приветсвия
print $html->message('info', $lang{INFO}, "Hello world\nSystem name '$conf{WEB_TITLE}'");
1;