В PHP 4 и старше предусмотрена функция serialize(mixed $value): string, которая генерирует пригодное для хранения строковое представление любой переменной. Строковое представление используется для хранения или передачи значений PHP между скриптами без потери их типа и структуры. Обратная операция получения переменной из строкового представления реализует функция unserialize() .
Строковое представление сериализации - это особый внутренний формат вида:
1 |
a:3:{i:1;a:0:{}i:2;a:2:{s:5:"title";s:67:"(C) 2019.Реформация Компьютерных Систем";s:8:"nav_menu";i:2;}s:12:"_multiwidget";i:1;} |
Для использования таких данных за пределами среды PHP необходимо выполнять десериализацию таких данных, а для сохранения сериализацию.
В настоящей публикации описан формат сериализации и приведены коды функций для 1С:Предприятие 8.
Формат сериализации
Сериализация - это преобразование данных произвольной структуры в строковую последовательность конечной длины, для которой в последствии может быть выполнено обратное преобразование для получения исходной структуры данных. В PHP подобное преобразование выполняет несколько пар функций, однако основной является пара serialize() и unserialize().
Сериализованные в строки данные имеют вид a:3:{i:1;s:5:"elem1";i:2;s:5:"elem2";i:3;s:5:"elem3";} и удобны для хранения в базе данных.
Скалярные типы:
- Булево сериализуется как: b:<i>; где <i> это целое числовое значения 0 (Ложь) или 1 (Истина).
- Целое сериализуется как: i:<i>; где <i> это числовое значение.
- Число с плавающей точкой сериализуется как (для d подразумевается двойная точность double): d:<f>; где <f> это числовое значение.
- Строка сериализуется как: s:<i>:"<s>"; где <i> это представленная длина строки, и <s> это сама строка.
Специальные типы:
- Значение null просто сериализуется символом N;
Составные типы:
- Массив сериализуется последовательностью a:<i>:{<elements>} где <i> это число элементов в массиве, а <elements> это пустой или непустой набор пар, разделенных ;
- каждая пара образуется как <key>;<value> где <key> представляется скалярным типом , и <value> любым сериализуемым значением.
Следует учесть, что массив в PHP соответствует 1с-типу Соответствие.
- Объект сериализуется как: O:<i>:"<s>":<i>:{<properties>} где первый <i> это целое число представляющее длину <s>, и <s> это имя класса (class name prepended with full namespace). Второй <i> это целое, представляющее число свойств объекта. <properties> это пустой или нет набор пар:
- <name>;<value> где <name> это сериализованная строка представляющая имя свойства и <value> любое сериализуемое значение.
При сериализации объекта имя свойства <name> содержит сведение о его видимости, поскольку <name> представляется как s:<i>:"<s>"; где <i> это целое представляющее длину <s>. Но значение <s> может быть различным в зависимости от видимости свойства:
-
-
- для public свойства <s> это просто имя свойства.
- для protected свойства <s> это также просто имя свойства, начинающееся символом *, заключенным в два NUL-символа (Chr(0)).
- для private свойства <s> это имя свойства, начинающееся символом s, заключенным в два NUL-символа.
-
Другие типы:
Кроме перечисленных существуют и другие случаи как: R:<i>; который представляет php-ссылку. Этот тип автору не встречался и его обработка алгоритмом не предусмотрена, поэтому в настоящей публикации он не рассматривается.
Реализация для 1С
Любые структуры данных 1С могут быть сериализованы в строку в полном соответствии с форматом PHP, а также почти любые данные сериализованные в среде PHP могут быть десерализованны в 1С. Некоторую неоднозначность вызывает десериализация составных типов, поскольку в PHP составные типы представлены типами Массив и Объект, в то время как 1С предусматривает типы Массив, Структура, Соответствие (а кроме того СписокЗначений, ТаблицаЗначений, ДеревоЗначений). Наиболее точно друг другу соответствует типы php-Массив и 1с-Соответствие, при этом php-индекс тождественен 1с-ключу.
Сериализация
Основная функция общего модуля СериализоватьРНР(Значение, Глубина)
- Описание: Рекурсивная функция выполняет сериализацию произвольного значения в строк по формату PHP.
- Параметры:
- Значение - значение произвольного типа, которое будет сериализовано в формат PHP строки
- Глубина - глубина рекурсивности для обхода сложных типов нахоящихся в Значении сложного типа
- Возвращаемое значение: строка сериализации Значения
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
Функция СериализоватьРНР(Значение, Глубина=1) Экспорт Если Значение = NULL ИЛИ Значение = Неопределено Тогда // N; Возврат "N;"; ИначеЕсли ТипЗнч(Значение) = Тип("Строка") Тогда // s:<i>:"<s>"; Возврат "s:" + СтрДлинаРНР(Значение) + ":""" + Значение + """;"; ИначеЕсли ТипЗнч(Значение) = Тип("Число") Тогда Если Значение = Цел(Значение) Тогда // i:<i>; Возврат "i:" + СтрокаЧисла(Значение) + ";"; Иначе // d:<f>; Возврат "d:" + СтрокаЧисла(Значение) + ";"; КонецЕсли ИначеЕсли ТипЗнч(Значение) = Тип("Булево") Тогда // b:<i>; Возврат ?(Значение, "b:1;", "b:0;"); ИначеЕсли ТипЗнч(Значение) = Тип("Массив") Тогда // a:<i>:{<elements>} СтрокаМассива = "a:" + СтрокаЧисла(Значение.Количество()) + ":{"; Для Индекс = 0 По Значение.ВГраница() Цикл СтрокаМассива = СтрокаМассива + СериализоватьРНР(Индекс) + СериализоватьРНР(Значение[Индекс]); КонецЦикла; Возврат СтрокаМассива + "}"; ИначеЕсли ТипЗнч(Значение) = Тип("Соответствие") Тогда ИмяОбъекта = Значение.Получить(NULL); Если ИмяОбъекта = Неопределено Тогда // a:<i>:{<elements>} СтрокаСоответствия = "a:" + СтрокаЧисла(Значение.Количество()) + ":{"; Для Каждого Пара Из Значение Цикл СтрокаСоответствия = СтрокаСоответствия + СериализоватьРНР(Пара.Ключ) + СериализоватьРНР(Пара.Значение); КонецЦикла; Возврат СтрокаСоответствия + "}"; Иначе // O:<i>:"<s>":<i>:{<properties>} //Значение.Удалить(NULL); СтрокаОбъекта = "O:" + СтрДлинаРНР(ИмяОбъекта) + ":""" + ИмяОбъекта + """:" + СтрокаЧисла(Значение.Количество()) + ":{"; Для Каждого Пара Из Значение Цикл Если Пара.Ключ <> NULL Тогда СтрокаОбъекта = СтрокаОбъекта + СериализоватьРНР(Пара.Ключ) + СериализоватьРНР(Пара.Значение); КонецЕсли; КонецЦикла; Возврат СтрокаОбъекта + "}"; КонецЕсли; ИначеЕсли ТипЗнч(Значение) = Тип("Структура") Тогда // a:<i>:{<elements>} СтрокаМассива = "a:" + СтрокаЧисла(Значение.Количество()) + ":{"; Для Каждого Элемент Из Значение Цикл СтрокаМассива = СтрокаМассива + СериализоватьРНР(КлючРНР(Элемент.Ключ)) + СериализоватьРНР(Элемент.Значение); КонецЦикла; Возврат СтрокаМассива + "}"; Иначе Возврат ""; КонецЕсли; КонецФункции |
В коде функции применен не рекомендованный 1С способ директивы условной компиляции, который исключают компиляцию фрагмента сериализации объектов на Клиенте, который обычно там и не может находиться.
Десериализация
Функция общего модуля ДесериализоватьРНР(СтрокаСериализации, КурсорСтроки=1)
- Описание: Рекурсивная функция выполняет десериализацию строки, являющейся сериализацией по формату PHP
- Параметры:
- СтрокаСериализации - строка, которая будет десериализована
- КурсорСтроки - числовой, возвращаемый, устанавливает начало десериализации, по умолчанию =1, возвращаемое значение позволяет проконтролировать индекс завершение сериализации
- Возвращаемое значение: возвращает десериализованное значение скалярного типа Булево, Число, Строка, NULL или Соотвествие
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
Функция ДесериализоватьРНР(СтрокаСериализации, КурсорСтроки=1) Экспорт Если КурсорСтроки < СтрДлина(СтрокаСериализации) Тогда СимволТипа = Сред(СтрокаСериализации, КурсорСтроки, 1); Если СимволТипа = "b" Тогда // b:<i>; Логическое = ?(Сред(СтрокаСериализации, КурсорСтроки + 2, 1) = "1", Истина, Ложь); КурсорСтроки = КурсорСтроки + 4; Возврат Логическое; ИначеЕсли СимволТипа = "i" Тогда // i:<i>; КурсорСтроки = КурсорСтроки + 2; КонецЗначения = СтрНайти(СтрокаСериализации, ";", НаправлениеПоиска.СНачала, КурсорСтроки, 1); Целое = Число(Сред(СтрокаСериализации, КурсорСтроки, КонецЗначения - КурсорСтроки)); КурсорСтроки = КонецЗначения + 1; Возврат Целое; ИначеЕсли СимволТипа = "d" Тогда // d:<f>; КурсорСтроки = КурсорСтроки + 2; КонецЗначения = СтрНайти(СтрокаСериализации, ";", НаправлениеПоиска.СНачала, КурсорСтроки, 1); Десятичное = Число(Сред(СтрокаСериализации, КурсорСтроки, КонецЗначения - КурсорСтроки)); КурсорСтроки = КонецЗначения + 1; Возврат Десятичное; ИначеЕсли СимволТипа = "s" Тогда // s:<i>:"<s>"; КурсорСтроки = КурсорСтроки + 2; КонецЗначения = СтрНайти(СтрокаСериализации, ":", НаправлениеПоиска.СНачала, КурсорСтроки, 1); ДлинаСтрокового = Число(Сред(СтрокаСериализации, КурсорСтроки, КонецЗначения - КурсорСтроки)); КурсорСтроки = КонецЗначения + 1; Строковое = СредPHP(СтрокаСериализации, КурсорСтроки + 1, ДлинаСтрокового); КурсорСтроки = КурсорСтроки + ДлинаСтрокового + 3; Возврат Строковое; ИначеЕсли СимволТипа = "N" Тогда // N; КурсорСтроки = КурсорСтроки + 2; Возврат NULL; ИначеЕсли СимволТипа = "a" Тогда // a:<i>:{<elements>} КурсорСтроки = КурсорСтроки + 2; КонецЗначения = СтрНайти(СтрокаСериализации, ":", НаправлениеПоиска.СНачала, КурсорСтроки, 1); РазмерМассива = Число(Сред(СтрокаСериализации, КурсорСтроки, КонецЗначения - КурсорСтроки)); КурсорСтроки = КонецЗначения + 2; Составной = Новый Соответствие; Для ИндексМассива = 1 По РазмерМассива Цикл КлючМассива = ДесериализоватьРНР(СтрокаСериализации, КурсорСтроки); ЗначениеМассива = ДесериализоватьРНР(СтрокаСериализации, КурсорСтроки); Составной.Вставить(КлючМассива, ЗначениеМассива); КонецЦикла; КурсорСтроки = КурсорСтроки + 1; Возврат Составной; ИначеЕсли СимволТипа = "O" Тогда // O:<i>:"<s>":<i>:{<properties>} КурсорСтроки = КурсорСтроки + 2; КонецЗначения = СтрНайти(СтрокаСериализации, ":", НаправлениеПоиска.СНачала, КурсорСтроки, 1); ДлинаСтрокового = Число(Сред(СтрокаСериализации, КурсорСтроки, КонецЗначения - КурсорСтроки)); КурсорСтроки = КонецЗначения + 1; ИмяОбъекта = Сред(СтрокаСериализации, КурсорСтроки + 1, ДлинаСтрокового); КурсорСтроки = КурсорСтроки + ДлинаСтрокового + 3; // ...<i> КонецЗначения = СтрНайти(СтрокаСериализации, ":", НаправлениеПоиска.СНачала, КурсорСтроки, 1); РазмерМассива = Число(Сред(СтрокаСериализации, КурсорСтроки, КонецЗначения - КурсорСтроки)); КурсорСтроки = КонецЗначения + 2; Составной = Новый Соответствие; Составной.Вставить(NULL, ИмяОбъекта); Для ИндексМассива = 1 По РазмерМассива Цикл КлючМассива = ДесериализоватьРНР(СтрокаСериализации, КурсорСтроки); ЗначениеМассива = ДесериализоватьРНР(СтрокаСериализации, КурсорСтроки); Составной.Вставить(КлючМассива, ЗначениеМассива); КонецЦикла; КурсорСтроки = КурсорСтроки + 1; Возврат Составной; Иначе Возврат Неопределено; КонецЕсли; Иначе Возврат Неопределено; КонецЕсли; КонецФункции |
Приведенный код рассчитан на обработку корректной php-сериализации и не обрабатывает возможные ошибки, поэтому в некоторых случаях десериализация может возвратить абракадабру или даже безнадежно зациклиться.
Улучшение перечисленных недостатков я пока отложу до тех пор, пока они не начнут создавать для меня слишком существенные неудобства. Если кто-либо испытает подобное неудобство раньше, то устранить их не представит большого труда.
Дополнительные функции
Функция общего модуля СредРНР(Строка, НачальныйНомер, ЧислоСимволов) Экспорт
- Описание: Функция работает аналогично стандартной Сред(), но корректирует значение ЧислоСимволов. Поскольку в PHP используется такая кодировка символов, при которой символы ASCII используют один символ, а символы национальных раскладок используют два, три или четыре символа (длина UTF-8 кода), то указываемая для s:<i>:"<s>"; длина строки php-символов оказывается больше, чем длина строки 1с-символов, если в строке присутствуют национальные символы.
- Например: s:5:"А+Б"; при указанной php-длине 5, имеет 1с-длину 3, поэтому функция СредРНР(), получив параметр ЧислоСимволов=5, скорректирует его до ЧислоСимволов=3 и возвратит его по ссылке, и возвращаемую подстроку также уменьшит до 3 символов.
- Параметры:
- Строка - строка, из который извлекается подстрока
- НачальныйНомер - числовой, номер первого символа подстроки в Строке
- ЧислоСимволов - числовой, возвращаемый, первоначальная длина подстроки в php-символах, значение уменьшается на 1 для каждого найденного в подстроке национального символа
- Возвращаемое значение: Строка
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Функция СредPHP(Строка, НачальныйНомер, ЧислоСимволов) Экспорт КурсорСтроки = НачальныйНомер; Пока КурсорСтроки < НачальныйНомер + ЧислоСимволов Цикл КодСимволаРНР = КодСимвола(Строка, КурсорСтроки); Если КодСимволаРНР < 128 Тогда ИначеЕсли КодСимволаРНР < 2048 Тогда ЧислоСимволов = ЧислоСимволов - 1; ИначеЕсли КодСимволаРНР < 65536 Тогда ЧислоСимволов = ЧислоСимволов - 2; Иначе ЧислоСимволов = ЧислоСимволов - 3; КонецЕсли; КурсорСтроки = КурсорСтроки + 1; КонецЦикла; Возврат Сред(Строка, НачальныйНомер, ЧислоСимволов); КонецФункции |
Функция общего модуля СтрДлинаРНР(СтрокаРНР) Экспорт
- Описание: Функция работает аналогично стандартной СтрДлина(), но измеряет длину строки в байтах кодировки UTF-8, как требует PHP для указания в строке сериализации
- Параметры: СтрокаРНР - измеряемая строка
- Возвращаемое значение: количество байтов в строке PHP в кодировке UTF-8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Функция СтрДлинаРНР(СтрокаРНР) Экспорт МультибайтовыхКодов = 0; ДлинаСтрокиРНР = СтрДлина(СтрокаРНР); Для КурсорСтроки = 1 По ДлинаСтрокиРНР Цикл КодСимволаРНР = КодСимвола(СтрокаРНР, КурсорСтроки); Если КодСимволаРНР < 128 Тогда ИначеЕсли КодСимволаРНР < 2048 Тогда МультибайтовыхКодов = МультибайтовыхКодов + 1; ИначеЕсли КодСимволаРНР < 65536 Тогда МультибайтовыхКодов = МультибайтовыхКодов + 2; Иначе МультибайтовыхКодов = МультибайтовыхКодов + 3; КонецЕсли; КонецЦикла; Возврат ДлинаСтрокиРНР + МультибайтовыхКодов; КонецФункции |
Источники:
- Structure of a Serialized PHP string на //stackoverflow.com
- Формат сериализации PHP - PHP serialization format //ru.qwe.wiki
© «РеКС» и DrLightman, 2019
Авторское право принадлежит автору.
При использовании ссылка на источник обязательна.
Исходные тексты предоставлены в свободное использования "как есть" без ответственности за последствия.