Существует технология, позволяющая работать из Веб-приложений напрямую с БД, используя бизнес-объекты в формате XML
Взаимодействие с объектами GB через XML
-
Установка
- Установить GBUDF.dll версии 3.112 или выше
- Приобрести и установить лицензию на использование формата XML
Основные принципы
- Все обращения к базе данных производятся путем вызова хранимых процедур, каждая из которых имеет один входной параметр и один выходной параметр.
- Входной и выходной параметры имеют тип VARCHAR(32640)
- Входные и выходные данные передаются в формате XML, кодировка UTF-8
- Если размер входного XML превышает 32640 байт, необходимо «нарезать» его на части нужного размера, и вызвать несколько раз процедуру prepare_param. Последняя часть передается в качестве параметра нужной процедуре.
- Пример на PHP:
$param = $xml->asXML(); while (strlen($param) > 32640) { $slice = substr($param,0, 32640); $param = substr($param, 32640); $query = ibase_query('select * from prepare_param(?)', $slice); ibase_fetch_row ($query); }
- Размер выходного XML также может превышать 32640, поэтому нужно конкатенировать получаемые из процедуры строки:
-
Пример на PHP:
$queryres = ''; $query = ibase_query('select * from '.$proc.'(?)', $param); while ($row = ibase_fetch_row ($query)) $queryres = $queryres.$row[0];
- Если в процессе работы процедуры возникает ошибка, то возвращается пакет из 2 полей ErrorCode и ErrorParams и одной записи, содержащей код и параметры ошибки
Схема XML
- XML представляют собой пакеты данных табличной структуры. Каждый пакет содержит набор общих параметров, набор полей и набор записей, в каждой из которых содержатся значения полей.
- XML для обмена данных должен иметь кодировку UTF-8. Некоторые имена полей (в частности, хиповых) являются регистрозависимыми, поэтому рекомендуется соблюдать регистр как в именах тэгов, так и в значениях атрибутов.
- XML для обмена данными имеет следующую структуру:
<?xml encoding=”utf-8” ?> <DataPacket> <MetaData> <Params> Общие параметры </Params> <Fields> Поля </Fields> </MetaData> <RowData> Записи </RowData> </DataPacket>
Общие параметры
- Общие параметры – глобальные параметры пакета данных.
- Описание каждого параметра имеет вид:
- <Param Name=”Имя” Value=”Значение” />
-
Используются следующие общие параметры:
- Эти параметры являются обязательными:
- USERID – код пользователя ГБ, возвращается при успешном логине.
- ATTACHMENT – код подключения, возвращается при успешном логине.
-
Эти параметры являются необязательными:
-
CURRPROC – имя вызываемой процедуры. Нужен для корректного отображения в инструментах мониторинга. Крайне желательно, чтобы он был заполнен
-
DEPOT – код удаленного отдела. Его заполнение требуется в особых случаях:
- Когда нужно выполнить какие-либо действия в обход контроля доступа
- Когда нужно создать объект, код которого имеет заданное значение, в обход генераторов.
- Когда нужно, чтобы сделанные изменения не ушли по обмену в другие базы
- Прежде чем заполнять параметр DEPOT, рекомендуется обратиться к разработчикам!
- LOGNAME – имя процедуры, если хочется чтобы информация о запуске и завершении процедуры была показана в GBLOG
- Рекомендуется заполнять этот параметр при вызове отчетов
-
Поля
- Поля описывают структуры таблицы, содержащейся в пакете данных.
- Описание каждого поля имеет вид:
<Field FieldName=”Имя” FieldType=”Тип” > Параметры поля </Field>
- Имя поля должно удовлетворять стандартам xml на именование тэгов и атрибутов, поскольку оно же будет использоваться в теге Row
- Тип поля может иметь одно из значений:
- I4 –целое
- I2 – короткое целое
- Sn – строка длиной n (например, S20). n не может превышать 250
- F8 – число с плавающей точкой
- D8 – дата
- B – блоб (двоичные данные)
- Каждый из параметров поля имеет вид
- <Param Name=”Имя” Value=”Значение” />
-
Параметры используется только в выходных данных, во входном XML их можно не заполнять.
-
Используются следующие параметры полей:
- Nature – внутренний тип поля.
- Возможные значения:
- 1 – в поле хранится количество (например, товара, в штуках)
- 2+256*n – в поле хранится сумма в валюте с кодом n
- 3 – в поле хранятся проценты
- 4 – в поле хранится дата
- 6+256*n – в поле хранится имя, максимальная длина n символов
- 7+256*n – в поле хранится код объекта с типом n
- 2+256*n – в поле хранится цена товара в валюте с кодом n
- 10 – в поле хранится субсет
- 11- в поле хранится хип
- 12 – в поле хранится логическое значение
- 14 – в поле хранится путь к файлу
- 15+256*n – в поле хранится число, которое должно быть отформатировано с n знаками после запятой, и по которому не предполагается вычисление итогов
- 16+256*n – в поле хранится число, которое должно быть отформатировано с n знаками после запятой, и по которому предполагается вычисление итогов
- Attrs – атрибуты поля, битовая маска, используется только в ГБ, можно игнорировать.
- Label – подпись к полю
-
Записи
- Каждая запись имеет вид:
<Row RowState=”Состояние” Поле=”Значение” … > Субсеты </Row>
- Атрибуты тэга Row содержат значения для каждого простого поля. Каждое поле должно быть описано в MetaData/Fields
- Исключение составляет необязательный атрибут RowState, который описывает состояние записи при манипуляциях с объектами – его описывать не нужно.
- Значения должны быть отформатированы следующим образом:
- Числа с плавающей точкой должны быть записаны без разделителя групп разрядов, с десятичным разделителем «точка»
- Даты должны быть записаны в формате DD.MM.YYYY
- В строках переводы строк должны быть заменены на ‘<BR>’
- Субсеты делятся на 2 типа:
- Хип – ровно одна запись из любого числа полей строкового типа, каждое поле может иметь любое имя без ограничений. Поле, содержащее в себе хип, как правило, имеет имя Heap
- Формат представления хипа:
<Поле> <Field FieldName=”Имя” Value=”Значение” /> … </Поле>
- Субсет – вложенная таблица
- Формат представления субсета:
<Поле> <DataPacket> Описание вложенной таблицы </DataPacket> </Поле>
Основные методы работы
Запуск процедуры
-
Последовательность действий:
- Подготовить XML с входными данными
- Установить общие параметры
- При необходимости порезать полученный XML на части и вызвать нужное число раз процедуру prepare_param
- Вызвать нужную процедуру и профетчить из нее результаты, сконкатенировав результирующую строку
- Пропарсить полученный XML. Проверить, не содержит ли он описание ошибки. Если содержит – достать описание ошибки и запустить ее обработку.
- Пример на PHP с использованием SimpleXML :
class PacketError extends Exception { } function execute_procedure($proc, $xml) { global $userid, $attachment; if (!isset($xml->MetaData->Params)) $xml->MetaData->addChild('Params'); if (count($xml->xpath("MetaData/Params/Param[@Name='USERID']")) == 0) { $user = $xml->MetaData->Params->addChild('Param'); $user->addAttribute('Name','USERID'); $user->addAttribute('Value',$userid); } if (count($xml->xpath("MetaData/Params/Param[@Name='ATTACHMENT']")) == 0) { $user = $xml->MetaData->Params->addChild('Param'); $user->addAttribute('Name','ATTACHMENT'); $user->addAttribute('Value',$attachment); } $param = $xml->asXML(); while (strlen($param) > 32640) { $slice = substr($param,0,32640); $param = substr($param,32640); $query = ibase_query('select * from prepare_param(?)', $slice); ibase_fetch_row ($query); } $queryres = ''; $query = ibase_query('select * from '.$proc.'(?)', $param); while ($row = ibase_fetch_row ($query)) $queryres = $queryres.$row[0]; ibase_free_result($query); $res = simplexml_load_string($queryres); if (isset($res->RowData) && isset($res->RowData->Row) && isset($res->RowData->Row['ErrorCode'])) throw new PacketError($res->RowData->Row['ErrorCode'].':'.$res->RowData->Row['ErrorParams']); return $res; }
Вызов процедуры с входным XML, содержащим однострочную таблицу без субсетов
-
Однострочная таблица используется для описания входных параметров всех отчетов, а также процедур вида XXX_GET, возвращающих свойства объекта, поэтому рекомендуется реализовать отдельный метод для вызова таких процедур.
- Пример на PHP с использованием SimpleXML :
function packet_get($proc, $args, $values) { // create params xml $xml = simplexml_load_string('<?xml version="1.0" encoding="utf-8"?><DataPacket><MetaData><Fields></Fields></MetaData><RowData/></DataPacket>'); foreach ($args as $fieldname => $fieldtype) { $field = $xml->MetaData->Fields->addChild('Field'); $field->addAttribute('FieldName', $fieldname); $field->addAttribute('FieldType', $fieldtype); } // add row, fill values $row = $xml->RowData->addChild('Row'); for ($i = 0; $i < count($values); $i++) { $fieldname = $xml->MetaData->Fields->Field[$i]['FieldName']; $row->addAttribute($fieldname, $values[$i]); } return execute_procedure($proc, $xml); }
-
Логин
- Выполнить процедуру security_connect_packet, в качестве входных параметров однострочная таблица, поле username типа строка содержит имя пользователя, поле computername типа строка содержит имя или IP-адрес хоста, с которого производится вход, поле login (целое) содержит 1.
- Проверить, что вход успешен (не произошло ошибки и в выходной таблице LoginResult=1).
- Запомнить возвращенный код пользователя (в выходных данных, поле User) и код подключения (в общих параметрах, параметр ATTACHMENT) для последующих вызовов
- Пример на PHP с использованием SimpleXML :
function gb_login($user) { global $userid, $attachment; try { $xml = packet_get('security_connect_packet',array('username'=>'S200','computername'=>'S200','login'=>'I4'),array($user,'www',1)); } catch(PacketError $e) { return false; } if (isset($xml->RowData) && isset($xml->RowData->Row)) { $res = $xml->RowData->Row['LoginResult']; if ($res == 1) { $userid = $xml->RowData->Row['User']; $attachment_param = $xml->xpath("MetaData/Params/Param[@Name='ATTACHMENT']"); if (count($attachment_param) == 1) $attachment = $attachment_param[0]['Value']; return true; } else return false; } else return false; }
Логофф
- Необходимо выполнять в конце работы
- Выполнить процедуру security_connect_packet, в качестве входных параметров однострочная таблица, поле login (целое) содержит 0.
- Обнулить запомненные коды пользователя и подключения
- Пример на PHP с использованием SimpleXML :
function gb_logoff($user) { global $userid, $attachment; try { $xml = packet_get('security_connect_packet',array('username'=>'S200','computername'=>'S200','login'=>'I4'),array($user,'www',0)); } catch(PacketError $e) { return false; } $userid = 0; $attachment = 0; return true; }
-
Получение свойств объекта
- Получение свойств одного или нескольких объектов заданного типа производится путем вызова процедуры XXX_GET, где XXX – название типа, которое определяется по таблице типов http://wiki.gbsoft.ru/wiki/Типы_объектов_ГБ
-
Способ вызова процедур стандартный – на входе задается XML с 3 полями
- Type, тип I4 – код типа объекта (из таблицы)
- Ids, тип B – список кодов объектов через точку с запятой. Максимальная длина списка 8192 байт, если нужно получить свойтва для большего числа объектов, нужно вызывать процедуру несколько раз
- Props, тип B – список требуемых свойств объектов, через точку с запятой, в начале и в конце списка тоже точка с запятой. Например, если требуется получить название объекта, в поле Props нужно передать ;Name;
- Если нужно получить все свойства объекта, можно передать в Props значение *
-
На выходе процедура вернет XML, содержащий запрошенные поля и по одной записи на каждый запрошенный объект.
-
Перечень стандартных свойств объектов приведен в приложении 1.
- Очевидно, что можно реализовать универсальный метод GB_GET, который по типу объекта, массиву кодов и массиву свойств вернет таблицу с нужными кодами и свойствами.
-
Пример на PHP громоздкий и непрозрачный, поэтому здесь не приведен
-
Работа с отчетами
- Названия и типы полей для параметров отчета можно узнать из описания, а при его отсутствии – из текста хранимой процедуры, либо запустив отчет из gb с ключом –packetlog или –packetlogfile. В последнем случае при запуске отчета в ГБ будет выведено в лог название процедуры, а также имена и типы всех полей, содержащих параметры отчета.
- Как правило, отчеты возвращают только коды объектов, поэтому для получения дополнительных свойств (названия и т.п.) могут потребоваться дополнительные вызовы процедур XXX_GET. В таком случае рекомендуется после получения данных отчета сформировать списки кодов объектов и требуемых свойств, чтобы затем провести минимальное количество вызовов XXX_GET.
- Рекомендуемый способ работы: реализовать метод AddIndirectFields, который получает на входе полученный XML и список вычисляемых полей в формате Articul.Name;Articul.Heap.@Name
- Для каждого поля в списке ищется поле данных (Articul), определяется его внутренний тип (Param с именем Nature), в нашем случае это товары, значит значение параметра будет 7+256=263. по формуле (Nature>>8)&256 определяем код типа – в нашем случае 1. По таблице определяем название типа – Articuls, следовательно нужно вызвать процедуру Articuls_Get. Проанализировав весь список полей, получаем необходимый набор свойств - ;Name;Heap;
- Пробежав по всем записям, определяем список кодов товаров. Затем нужное число раз зовем Articuls_Get
-
В идеале реализация процедуры AddIndirectFields должна поддерживать рекурсию, чтобы допускать задание полей в виде Articul.Node.Name или даже Partner.City.Name – здесь нужно сперва достать регионы из партнеров, а затем названия регионов.
- Кроме того, в ГБ часто используются выражения вида Articul.Heap.@Name для получения значений хиповых полей, и Articul.Heap.@Supplier[fmId+otPartners].Name – для приведения типов хиповых полей на лету
- Пример на PHP громоздкий и непрозрачный, поэтому здесь не приведен
Изменение объектов
- Для создания, изменения и удаления объектов используются процедуры XXX_PUT
- На входе эти процедуры получают данные той же структуры, что возвращает XXX_GET, с информацией о том, что изменилось
- Для создания объекта нужно:
- Вызвать XXX_GET с Ids=-1 и Props=*
- Будет получен XML с одной записью, в которой все поля пусты, а субсеты содержат набор полей.
- Нужно установить RowState записи = 4 и заполнить поля. Если при создании добавляются записи в субсеты, то нужно для каждой записи субсета также установить RowState=4
-
Например, нужно создать документ. Для этого сперва вызываем Documents_Get. Получаем поля документа, а также поля товарных строк документа в субсете Goodies.
- Заполняем поля документа, добавляем записи в субсет Goodies, вызываем Documents_Put
- Для удаления объекта с заданным кодом нужно:
- Вызвать XXX_GET с Ids=код и Props=*
- Будет получен XML с одной записью. Нужно установить в ней RowState=2 и вызвать XXX_PUT
- Для изменения заданных полей объекта с заданным кодом нужно:
- Вызвать XXX_GET с Ids=код и Props=*
- Будет получен XML с одной записью. Нужно установить в этой записи RowState = 1, затем создать новую запись (тэг Row) с RowState = 8 и заполнить в нем только изменяемые поля.
- Если изменяется субсет, то субсет в записи с RowState=8 должен содержать только изменения, по тем же правилам: вставка записи – RowState=4, удаление записи – RowState=2, изменение – пара записей, оригинальная с RowState=1 и измененные поля c RowState=8
- За один вызов процедуры можно вставить, изменить или удалить любое количество объектов.