Писалось для версии 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/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($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;