PHP OData драйвер

Удобный доступ к OData данным через SQL синтаксис

Стояла задача создать удобный PHP драйвер к OData интерфейсу.

Представляю вашему вниманию свое творение.

Composer: https://packagist.org/packages/falseclock/dbd-php

GitHub: https://github.com/Falseclock/dbd-php

Много рассказывать и описывать не буду, опишу в виде кода

1. Инсталляция. 

composer require falseclock/dbd-php

2. Конструктор

// Если у вас есть MemCached сервер или несколько, то укажите в первом параметре массив
// состоящий из хоста и порта.
// второй параметр отвечает за компрессию данных
// третий параметр указывает время кеширования по умолчанию

$cache = DBDCacheMemCache::me()->create(array(['host' => '127.0.0.1', 'port' => 11211]),false,"15 min")->open();

// Нужно создать массив из настроек

$odata_options = array(
'RaiseError'  => true, // останавливать выполнение при ошибке
'PrintError'  => true, // выводить ошибка на экран
'HTMLError'   => true, // печатать ошибки в виде HTML
'CacheDriver'  => $cache // указатель на экземпляр кэш драйвера. если нет, то null, либо вообше убрать
);

// создаем экземпляр класса
// 1-ый параметер - адрес ODATA
// 2-ой - имя пользователи
// 3-ий - его пароль
$od = (new DBDYellowERP())->create('http://crm.beta.virtex.kz/odata/', "user", "password", $odata_options);

// reuseSessions - если у вас стоит reuseSessions="use", второй параметр количество попыток подключения
// setDataKey('value') - ставить обязательно. Это ключ, в который 1С заталкивает данные в JSON формате

$od = $odata->reuseSessions(true,3)->
setDataKey('value');

3. Выборка данных

// Подготовка запроса без параметров, просто выборка
$sth = $od->prepare("
SELECT
Ref_Key, Number, Date, СуммаДокумента, КодНазначенияПлатежа, Комментарий, СтруктурнаяЕдиница, СтруктурнаяЕдиница_Type,
Товары/ЕдиницаИзмерения_Key, Товары/Цена, Товары/Сумма, Товары/СуммаНДС, Товары/Количество, Товары/Номенклатура_Key,
ДоговорКонтрагента/Ref_Key, ДоговорКонтрагента/Description,
Контрагент/Ref_Key, Контрагент/НаименованиеПолное, Контрагент/ИдентификационныйКодЛичности,
Организация/Ref_Key, Организация/НаименованиеПолное, Организация/ИдентификационныйНомер, Организация/КБЕ,
Ответственный/Ref_Key, Ответственный/Description
FROM
Document_СчетНаОплатуПокупателю
EXPAND
Контрагент,
Организация,
ДоговорКонтрагента,
Ответственный
ORDER BY
Date аsc
LIMIT 10
");
// хотим положить данные в кэш на 24 часа. Если время не укажем, то сохранится на то время,
// которое указавали стандартным при инициализации драйвера кэша. Если кэш не нужен - просто не пишем $sth->cache()
$sth->cache('CacheKey','24h');

// Выполнить запрос. Если данные будут найдены в кэше, то возьмутся от туда, если нет
// то запросятся по HTTP через OData.
$sth->execute();

// fetchrow - поочередное извлечение данных из массива
while ($row = $sth->fetchrow()) {
print_r($row);
}

// Если нужно извлечь все и сразу, то вот так вот в нумерованный массив
$sth->fetchrowset();

// Если нужен ассоциативный массив, то можно указать ключ
$sth->fetchrowset('Ref_Key');

4. Выборка с параметрами

$vatinvoice = "6453a564-1324-11e7-ef88-26a4bef88324";

$sth = $od->prepare("
SELECT
Ref_Key, Number, Date, СуммаДокумента
FROM
Document_ЭСФ
WHERE
СчетФактура eq cast(guid?,?)
ORDER BY
Date asc
");

// Вместо вопросов будут поставлены эти значения, то есть конвертнется
// в СчетФактура eq cast(guid'6453a564-1324-11e7-ef88-26a4bef88324','Document_СчетФактураВыданный')

$sth->execute($vatinvoice,'Document_СчетФактураВыданный');


5. Вставка новой записи

$data['СтруктурнаяЕдиница_Type'] = "StandardODATA.Catalog_БанковскиеСчета";
$data['ВалютаДокумента_Key'] = $currency['ВалютаДенежныхСредств']['Ref_Key'];
$data['ТипЦен_Key'] = "00000000-0000-0000-0000-000000000000";
$data['СтруктурноеПодразделение_Key'] = "00000000-0000-0000-0000-000000000000";
$data['Date'] = date("c");
$data['Комментарий'] = $data['НомерЗаказа']."
".$data['Комментарий'];
unset($data['НомерЗаказа']);
$data['УчитыватьНДС'] = true;
$data['УчитыватьАкциз'] = false;
$data['СуммаВключаетАкциз'] = false;
$data['СуммаВключаетНДС'] = true;
$data['DeletionMark'] = false;
$data['Posted'] = false;
$data['Ответственный_Key'] = $data['Автор_Key'];
$data['КратностьВзаиморасчетов'] = 1;

// Указываем документ и массив данных, где ключ - название поля
$entry = $od->insert('Document_СчетНаОплатуПокупателю',$data);

// Результатом будет возвращен массив либо выведена ошибка
print_r($entry);

6. Изменение данных

$od->update(
'Document_СчетНаОплатуПокупателю', // Название документа
array('Date' => $data['Date']),  // Массив тех полей которые нужно изменить и новые значения
"(guid?)", // по какому полю и как искать элемент
$data['Ref_Key'] // подстановка значения вместо знака вопроса
);

// либо можно так
$od->update(
'Document_СчетНаОплатуПокупателю', // Название документа
array('Date' => $data['Date'], 'Number'=>'00000012'),  // Массив тех полей которые нужно изменить и новые значения
"(guid'6453a564-1324-11e7-ef88-26a4bef88324')" // по какому полю и как искать элемент
);

7. Metadata

$od->metedata(); // выгрузка всех описаний стандартного интерфейса

$od->metedata('Catalog_Пользователи'); // выгрузка конкретного описания стандартного интерфейса

8. Живой пример

 public function InvoiceData($key)
{
////////////////////////////////////////////////////////////////////////Номенклатура_Key
$sth = $this->od->prepare("
SELECT
Ref_Key, Number, Date, СуммаДокумента, КодНазначенияПлатежа, Комментарий, СтруктурнаяЕдиница, СтруктурнаяЕдиница_Type,
Товары/ЕдиницаИзмерения_Key, Товары/Цена, Товары/Сумма, Товары/СуммаНДС, Товары/Количество, Товары/Номенклатура_Key,
ДоговорКонтрагента/Ref_Key, ДоговорКонтрагента/Description,
Контрагент/Ref_Key, Контрагент/НаименованиеПолное, Контрагент/ИдентификационныйКодЛичности,
Организация/Ref_Key, Организация/НаименованиеПолное, Организация/ИдентификационныйНомер, Организация/КБЕ,
Ответственный/Ref_Key, Ответственный/Description
FROM
Document_СчетНаОплатуПокупателю
EXPAND
Контрагент,
Организация,
ДоговорКонтрагента,
Ответственный
WHERE
Ref_Key eq guid?
");
//$sth->cache("Document_СчетНаОплатуПокупателю:{$key}","10m");
$sth->execute($key);

$invoice = $sth->fetchrow();
////////////////////////////////////////////////////////////////////////

$goods = $this->Goods();
$TYPES = $this->GetContactInfoTypes();
////////////////////////////////////////////////////////////////////////
$sth = $this->od->prepare("
SELECT
Ref_Key AS id,
Description AS desc,
НаименованиеПолное AS name
FROM
Catalog_КлассификаторЕдиницИзмерения
WHERE
НаименованиеПолное ne ''
");
$sth->cache("Catalog_КлассификаторЕдиницИзмерения","24h");
$sth->execute();
$units = $sth->fetchrowset();

array_walk($units, function(&$val){
$val['name'] = $val['name'] . " ({$val['desc']})";
return $val;
});
////////////////////////////////////////////////////////////////////////

$bank = $this->BankAccount($invoice['СтруктурнаяЕдиница']);

$invoice['Банк'] = $bank['Банк']['Description'];
$invoice['БИК'] = $bank['Банк']['БИК'];
$invoice['НомерСчета'] = $bank['НомерСчета'];

foreach ($invoice['Товары'] as &$item) {

$item['ЕдиницаИзмерения'] = $this->_getElementById($units, $item['ЕдиницаИзмерения_Key'])['desc'];
unset($item['ЕдиницаИзмерения_Key']);
$item['Наименование'] = $this->_getElementById($goods, $item['Номенклатура_Key'])['full'];
}

$invoice['Договор'] = $invoice['ДоговорКонтрагента']['Description'];

$GCCD = $this->GetCustomerContactData($invoice['Контрагент']['Ref_Key']);
$GOCD = $this->GetOrganizationContactData($invoice['Организация']['Ref_Key']);

foreach ($GCCD as $row) {
if ($TYPES[$row['Вид']]['PredefinedDataName'] == "ЮрАдресКонтрагента") {
$invoice['Контрагент']['Адрес'] = $row['Представление'];
}
if ($TYPES[$row['Вид']]['PredefinedDataName'] == "ТелефонКонтрагента") {
$invoice['Контрагент']['Телефон'] = $row['Представление'];
}
}
foreach ($GOCD as $row) {
if ($TYPES[$row['Вид']]['PredefinedDataName'] == "ЮрАдресОрганизации") {
$invoice['Организация']['Адрес'] = $row['Представление'];
}
if ($TYPES[$row['Вид']]['PredefinedDataName'] == "ТелефонОрганизации") {
$invoice['Организация']['Телефон'] = $row['Представление'];
}
}
$invoice['Ответственный'] = $invoice['Ответственный']['Description'];

unset ($invoice['ДоговорКонтрагента']);
unset ($invoice['Организация']['Ref_Key']);
unset ($invoice['СтруктурнаяЕдиница']);
unset ($invoice['СтруктурнаяЕдиница_Type']);

return($invoice);
}

print_r(Data1c::me()->InvoiceData('d5086168-00d8-11e7-ef88-26a4bef88324'));

/*

Array
(
[Ref_Key] => d5086168-00d8-11e7-ef88-26a4bef88324
[Number] => 00000000076
[Date] => 2024-03-04T18:44:56
[СуммаДокумента] => 538330
[КодНазначенияПлатежа] => 710
[Комментарий] => Заказ №00005115
dsfsfsfsdf
[Товары] => Array
(
[0] => Array
(
[Номенклатура_Key] => bde6a7ce-32c2-11e2-92a8-c692850d4a80
[Цена] => 41410
[Сумма] => 165640
[СуммаНДС] => 17747,14
[Количество] => 4
[ЕдиницаИзмерения] => шт
[Наименование] => 503297-B21/511777-001 HP 460W HE 12V HOTPLG AC PWR SUPPLY KIT
)

[1] => Array
(
[Номенклатура_Key] => 76e80df8-5802-11e4-6686-0e459e882122
[Цена] => 41410
[Сумма] => 372690
[СуммаНДС] => 39931,07
[Количество] => 9
[ЕдиницаИзмерения] => шт
[Наименование] => 501536-001 HP 8GB (1x8GB) Dual Rank x4 PC3-10600 (DDR3-1333) Registered CAS-9 Memory Kit
)

)

[Контрагент] => Array
(
[Ref_Key] => bd63f5ba-55f1-11e5-5d98-0e459e882122
[НаименованиеПолное] => Товарищество с ограниченной ответственностью ------
[ИдентификационныйКодЛичности] => 140000000634
[Адрес] => 010000, Республика Казахстан, Астана, р-н.Сарыарка, ул. -----
)

[Организация] => Array
(
[НаименованиеПолное] => Товарищество с ограниченной ответственностью "-----"
[ИдентификационныйНомер] => 100000000032
[КБЕ] => 17
[Телефон] => +7, 727 0000 000 87010000385
[Адрес] => A15P5A8, Республика Казахстан, г. Алматы, ул. Сатпаева, -------------
)

[Ответственный] => Муханов Нурлан
[Банк] => АО ДБ "Альфа-Банк"
[БИК] => ALFAKZKA
[НомерСчета] => KZ2790000000000407863
[Договор] => Без договора
)
*/

9. Поддерживаемый синтаксис

SELECT — выборка элементов для выгрузки, можно дополнять через AS, например SELECT Ref_Key AS id, Number AS number, Date FROM document

FROM — указание от куда брать данные

EXPAND — если нужно получать значения связанных сущностей

WHERE — установка условий отбора

ORDER BY — сортировка вывода

LIMIT — количество записей для отбора

10. Итоги

Для кого это нужно вы спросите? Ну в первую очередь если вам нужна выборка данных без установки Bitrix, то это самое оно. Плюс автоматическая поддержка сессий. 

Проект создавался для себя. Open source под лицензией MIT. Документация будет создаваться позже с более детальными случаями и всеми возможными параметрами. Комментарии приветствуются, участие в проекте тем более. 

4 Comments

  1. Идальго

    Привет! Не совсем понимаю, что в этом особенно интересного и перспективного, но хочу разобраться. Поясните пожалуйста следующее:

    1. Причём тут Битрикс? Вроде же не только Битрикс можно научить обмениваться информацией с 1С. Правда я с Битриксом работал только в качестве Интернет магазина, где обмен был либо через файлики (ftp), либо через http запросы и последующей передачей этих фаликов внутри него.

    2. На 1С можно развернуть Веб сервис и обращаться к нему со стороны сайта или другой 1С (см. демо конфигурацию по Веб сервисам от 1С). Чем OData лучше? Прочитал немного про OData в 1С. Оно позволяет работать с объектами в 1С напрямую. Это действительно может быть полезно, но только не для интернет магазинов, а например, для веб интерфейсов, из которых что-то делают в 1С.

    3. Что такое автоматическая поддержка сессий и чем она лучше других случаев? Ну т.е., как я понял, в простом варианте — удаленный сайт, приложение или что-то ещё инициирует http запросом соединение и передает что-то. Чем такой вариант плох и чем автоматическая поддержка сессий лучше? Накладные расходы и в том и в другом случае есть.

    Reply
  2. virtex3

    (1)

    Ничего особенного нет, впрочем, возможно и перспективного. Я просто опубликовал свою работу.

    1. Битрикс позволяет делать интеграцию с 1С Предприятием. Но для этого надо покупать сам Битрикс. Обмениваться информацией можно как угодно и с кем угодно, но разбирательство в интерфейсе и работа с ним напрямую сопряжена с нудным и иногда дурацком кодом. Чтобы взять данные по OData — нужно формировать и форматировать строку запроса. Драйвер позволяет упростить это до SQL синтаксиса. При этом не надо передавать никуда никакие файлы и не надо их парсить. Все происходит налету и без всяких сторонних средства. Эдакая универсальность.

    2. Вы немного не понимаете суть. В моем драйвере есть возможность обращения к HTTP сервисам самого 1С. Я их не описывал в статье, но возможность существует. Я через сервисный URL запрашиваю остатки на складе и себестоимость продукции в режиме реального времени. Через OData могу резервировать товары и выставлять автоматически счета на оплату через сайт. Как раз таки для магазинов это большое спасение — актуальность данных и их быстрое получение.

    3. Если обратимся к документации, то с выходом версии 8.3.9 стало возможно открывать одно постоянное соединение и время от времени обращаться к созданной сессии. Что это значит? Если делать без сессии, то придется постоянно открывать сессию на стороне 1С, а это ресурсы и скорость выполнения. А если 10 человек одновременно запросят данные, то будет открыто 10 сессий. Для этого нужны лицензии. С одной автоматической сессией можно обращаться постоянно и неограниченное количество раз и получать актуальные данные.

    Например, у вас в офисе 200 человек, у которых открыто клиентское приложение и пользователи только лишь читают данные, ничего не вносят, просто смотрят актуальность. Для всех 200 нужна пользовательская лицензия. Можно отказаться от 200 лицензий и оставить одну и давать данные через веб интерфейс.

    Reply
  3. azubar

    Респектую, интересное решение. Кеширование поддерживает только мемкеш?

    Reply
  4. virtex3

    (3) вообще планировал внедрить NOSQL базы типа Redis, Mongo, но руки так и не дошли.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *