Child pages
  • Пошаговое осваивание фреймворка ABillS
Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 3 Next »

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

Рассмотрим работу с каждым из унаследованных методов детальнее.

CREATE TABLE `example_entity` (
  `id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  `name` VARCHAR(40) NOT NULL,
  `value` SMALLINT(6) NOT NULL DEFAULT 0
);
idnamevalue
1name1101
2name2102

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;
  • No labels