===== Пошаговое осваивание фреймворка 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;