Стояла задача создать удобный 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, то это самое оно. Плюс автоматическая поддержка сессий.
Привет! Не совсем понимаю, что в этом особенно интересного и перспективного, но хочу разобраться. Поясните пожалуйста следующее:
1. Причём тут Битрикс? Вроде же не только Битрикс можно научить обмениваться информацией с 1С. Правда я с Битриксом работал только в качестве Интернет магазина, где обмен был либо через файлики (ftp), либо через http запросы и последующей передачей этих фаликов внутри него.
2. На 1С можно развернуть Веб сервис и обращаться к нему со стороны сайта или другой 1С (см. демо конфигурацию по Веб сервисам от 1С). Чем OData лучше? Прочитал немного про OData в 1С. Оно позволяет работать с объектами в 1С напрямую. Это действительно может быть полезно, но только не для интернет магазинов, а например, для веб интерфейсов, из которых что-то делают в 1С.
3. Что такое автоматическая поддержка сессий и чем она лучше других случаев? Ну т.е., как я понял, в простом варианте — удаленный сайт, приложение или что-то ещё инициирует http запросом соединение и передает что-то. Чем такой вариант плох и чем автоматическая поддержка сессий лучше? Накладные расходы и в том и в другом случае есть.
(1)
Ничего особенного нет, впрочем, возможно и перспективного. Я просто опубликовал свою работу.
1. Битрикс позволяет делать интеграцию с 1С Предприятием. Но для этого надо покупать сам Битрикс. Обмениваться информацией можно как угодно и с кем угодно, но разбирательство в интерфейсе и работа с ним напрямую сопряжена с нудным и иногда дурацком кодом. Чтобы взять данные по OData — нужно формировать и форматировать строку запроса. Драйвер позволяет упростить это до SQL синтаксиса. При этом не надо передавать никуда никакие файлы и не надо их парсить. Все происходит налету и без всяких сторонних средства. Эдакая универсальность.
2. Вы немного не понимаете суть. В моем драйвере есть возможность обращения к HTTP сервисам самого 1С. Я их не описывал в статье, но возможность существует. Я через сервисный URL запрашиваю остатки на складе и себестоимость продукции в режиме реального времени. Через OData могу резервировать товары и выставлять автоматически счета на оплату через сайт. Как раз таки для магазинов это большое спасение — актуальность данных и их быстрое получение.
3. Если обратимся к документации, то с выходом версии 8.3.9 стало возможно открывать одно постоянное соединение и время от времени обращаться к созданной сессии. Что это значит? Если делать без сессии, то придется постоянно открывать сессию на стороне 1С, а это ресурсы и скорость выполнения. А если 10 человек одновременно запросят данные, то будет открыто 10 сессий. Для этого нужны лицензии. С одной автоматической сессией можно обращаться постоянно и неограниченное количество раз и получать актуальные данные.
Например, у вас в офисе 200 человек, у которых открыто клиентское приложение и пользователи только лишь читают данные, ничего не вносят, просто смотрят актуальность. Для всех 200 нужна пользовательская лицензия. Можно отказаться от 200 лицензий и оставить одну и давать данные через веб интерфейс.
Респектую, интересное решение. Кеширование поддерживает только мемкеш?
(3) вообще планировал внедрить NOSQL базы типа Redis, Mongo, но руки так и не дошли.