Полный цикл в digital

Понимание сериализации

Сериализация — это процесс, в котором принимается часть данных и создаётся хранимое или переносимое представление этих данных.

Сериализованное представление данных может быть достигнуто с помощью различных форматов. Очень распространён JSON, поскольку большинство языков в той или иной форме кодируют и декодируют JSON. Можно также использовать XML, YAML или даже просто строку байтов.

Вы, вероятно, знакомы с функцией PHP serialize(). Эта функция принимает одно значение и возвращает его сериализованную версию в виде строки.

Уникальность сериализации в PHP заключается в том, что она фактически использует специальный формат сериализации для представления различных типов данных, включая массивы и объекты.

Каждый тип данных, который PHP может сериализовать, имеет своё собственное представление и обозначение при сериализации. Давайте разберём их и посмотрим, что они собой представляют.

Логические значения

Логические значения очень просты:

b:0;
b:1;

Спецификатором типа для логических значений является b. Затем следует двоеточие и целочисленное представление самого логического значения, так что false становится 0, а true — 1.

Null

Сериализация null приводит к следующему:

N;

Null не содержит никаких дополнительных данных, поэтому он представляется одним символом N.

Целые числа и числа с плавающей запятой

Сериализация значения 100 с помощью функции serialize() возвращает следующую строку:

i:100;

Сериализованные целые числа представляются символом i, за которым следует двоеточие и значение целого числа.

Значение целого числа всегда сериализуется в «десятичном» виде (основание 10). Сериализованная версия не содержит информации об исходном формате значения (шестнадцатеричный, восьмеричный, двоичный и т.д.).

Сериализация значения 100.5 имеет очень похожий формат:

d:100.5;

Единственное отличие заключается в том, что вместо i для целого числа в спецификаторе типа стоит ##bc_38## для «double».

Строки

Сериализованные строки несут в себе некоторую дополнительную информацию. Приведённый ниже код является результатом сериализации Hello, world:

s:12:"Hello, world";

В качестве спецификатора типа используется s. Затем следует двоеточие и результат strlen($value). Затем следует ещё одно двоеточие и исходное значение, заключённое в двойные кавычки:

s:[strlen(value)]:"[value]"

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

Это также помогает в языках более низкого уровня, где строки могут храниться в виде массива байтов. Если вы знаете, сколько байт занимает строка, вы можете выделить нужный объем и избежать возможных проблем с памятью.

Массивы

Сериализация массивов немного сложнее, поскольку массив PHP имеет ключи и значения. Сначала сериализуем пустой массив []:

a:0:{}

Спецификатором типа массива является a. Вторым компонентом в сериализованных данных является длина массива. Третий компонент — место размещения ключа и значений массива:

a:[count(value)]:{...values}

Чтобы увидеть, как происходит сериализация значений, мы можем сериализовать простой массив с тремя значениями [1, 2, 3]:

a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}

То, что находится внутри фигурных скобок, содержит информацию о ключах и значениях внутри массива.

Помните, что хотя мы не указали в массиве никаких ключей, PHP автоматически проиндексирует значения, начиная с 0.

Общее формирование значений выглядит следующим образом:

{key;value;key;value;key;value}

Развёртывание исходного массива в более явный эквивалент с ключом:

[
    0 => 1,
    1 => 2,
    2 => 3,
]

Мы можем посмотреть на каждую пару ключ-значение и увидеть, что их сериализованные значения расположены внутри фигурных скобок по порядку:

{i:0;i:1;i:1;i:2;i:2;i:3;}
 ^0  ^1  ^1  ^2  ^2  ^3

Если немного изменить массив и вместо ключей использовать строки:

[
    'a' => 1,
    'b' => 2,
    'c' => 3,
]
a:3:{s:1:"a";i:1;s:1:"b";i:2;s:1:"c";i:3;}
     ^a      ^1  ^b      ^2  ^c      ^3

Также можно сериализовать вложенные массивы, что полезно для более сложных структур:

a:1:{i:0;a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}}

Объекты

Наиболее сложный формат сериализации можно наблюдать при сериализации объектов.

Возьмём простой класс Nothing, создадим его и сериализуем:

class Nothing
{
    // ...
}

serialise(new Nothing);
O:7:"Nothing":0:{}

Спецификатором типа для сериализуемых объектов является O. Далее следует длина имени класса и само имя класса, заключённое в двойные кавычки.

Число, следующее за именем класса, — это количество свойств, которыми обладает объект. Далее в фигурных скобках располагаются сериализованные свойства в формате, аналогичном массивам.

На примере нового класса User, имеющего 2 свойства — $name и $age, мы можем увидеть, как происходит сериализация значений свойств:

class User
{
    public function __construct(
        public $name,
        public $age,
    ) {}
}

serialize(new User(
    name: "Ryan",
    age: 23
));
O:6:"User":2:{s:4:"name";s:4:"Ryan";s:3:"age";i:23;}

Объект User имеет 2 свойства, поэтому имя каждого свойства сериализуется в виде строки, а после него идёт сериализованное значение.

Преобразование этого формата в упрощённую грамматику может выглядеть следующим образом.

O:[strlen(value::class)]:"[value::class]":[count(properties)]:{...property}

property ::= [name];[value]

Непубличные свойства

Класс User имеет только публичные свойства, но функция serialize() может также сериализовать защищённые и приватные свойства.

Начиная с защищённых свойств, напишем новый класс SensitiveStuff, имеющий одно защищённое свойство $password:

class SensitiveStuff
{
    public function __construct(
        protected $password,
    ) {}
}

serialize(new SensitiveStuff(
    password: 'password123'
));
O:14:"SensitiveStuff":1:{s:11:"\0*\0password";s:11:"password123";}

Все выглядит очень похоже, но можно заметить, что в месте имени свойства есть несколько дополнительных символов. Вместо того чтобы имя свойства сериализовалось в s:8: "password", здесь присутствуют три дополнительных символа: \0*\0.

Символ \0 известен как нулевой терминатор или нулевой байт. Символ * (звёздочка/астериск) обозначает это свойство как защищённое.

Если мы изменим видимость свойства на private и выполним повторную сериализацию, то получим несколько иное значение:

O:14:"SensitiveStuff":1:{s:24:"\0SensitiveStuff\0password";s:11:"password123";}

На этот раз имя свойства снабжено префиксом из нулевого байта, имени класса объекта и ещё одного нулевого байта. Данный шаблон представляет собой приватное свойство.

Ресурсы

Сериализация значений resource в PHP невозможна. При попытке сериализации объекта с ресурсом, хранящимся в свойстве, он будет приведён к целому числу и сериализован как 0.

Заполните форму уже сегодня!
Для начала сотрудничества необходимо заполнить заявку или заказать обратный звонок. В ответ получите коммерческое предложение, которое будет содержать индивидуальную стратегию с учетом требований и поставленных задач
Работаем по будням с 9:00 до 18:00. Заявки, отправленные в выходные, обрабатываем в первый рабочий день до 12:00.
Спасибо, ваш запрос принят и будет обработан!