Понимание сериализации в PHP
Сериализованное представление данных может быть достигнуто с помощью различных форматов. Очень распространён 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
для целого числа в спецификаторе типа стоит d
для "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
.
Заключение
Спасибо за чтение! Надеюсь, это дало вам некоторое представление о том, как сериализуются различные типы значений в PHP.
В следующей статье я напишу о том, как написать собственную логику десериализации на другом языке программирования, скорее всего, на JavaScript!