Писалось для версии 0.77.14
В данном руководстве, Вы пошагово ознакомитесь с основными частями системы.
Фреймворк состоит из нескольких частей:
Ядро вебинтерфейса (движок) (index.cgi)
Работа с БД (dbcore.pm)
Работа с визуализацией
Базовые библиотеки
Файл конфигурации
Сначала рассмотрим составные части модуля, чтобы понимать, что нужно для написания логически интегрированного функционала.
В ABillS основная часть кода написана в функциональном или процедурном стиле, что влияет на работу с системой. Кроме того, поскольку ООП не используется для полиморфизма или расширения функционала классов через наследование, многие функции принимают аргумент $attr, в котором записаны дополнительные условия выполнения (которые могут кардинально изменять как результат, так и логику выполнения), поэтому нужно всегда учитывать полную сигнатуру вызова при чтении кода.
Модуль ABillS (для примера Example) состоит из:
Класса менеджера сущностей в БД (Abills/mysql/Example.pm)
Файл с логикой (функциями) (Abills/modules/Example/webinterface)
Описание меню (Abills/modules/Example/config)
Дополнительные файлы словарных переменных (необязательно) (Abills/modules/Example/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 | Хеш-масcив конфигурационного файла |
Для примера рассмотрим работу с сущностью 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;
} |
Рассмотрим работу с каждым из унаследованных методов детальнее.
|
Метод 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; |