Статьи

Как правильно работать с сериализацией PHP

PHP может автоматически сериализовать большинство своих переменных в строки — что позволяет вам сохранять их в хранилище, например, $ _SESSION. Однако есть некоторые хитрости, которые вы должны знать, чтобы избежать взлома скриптов .php и проблем с производительностью.

Примитивы

Примитивная функция serialize () принимает переменную PHP в качестве аргумента и возвращает строку, из которой эта переменная может быть восстановлена:

$ php -r 'var_dump(serialize(42));'
string(5) "i:42;"

unserialize ($ string) выполняет противоположную работу:

$ php -r 'var_dump(unserialize("i:42;"));'
int(42)

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

Однако, это следует за полевыми ссылками:

$ php -r '$object = new stdClass; $object->field = new stdClass; var_dump(serialize($object));'
string(50) "O:8:"stdClass":1:{s:5:"field";O:8:"stdClass":0:{}}"

поэтому, если вы ссылаетесь на объекты ORM или фреймворка из ваших собственных сериализованных объектов, вы, вероятно, получите всю библиотеку вместе с ней. Простой способ избежать этой зависимости сериализации — передать коллаборатора в стек, а не вставлять его в поле объекта:

class MyObject
{
    public function doSomeWork(Zend_View $view) {
        // refer to $view instead of $this->view
    }
}

Запрещенные типы

Некоторые типы переменных не могут быть легко перенесены из одного процесса ОС в другой, и поэтому не подходят для сериализации:

  • переменные типа resource (открытые файлы, потоки, соединения старого стиля)
  • объекты, которые хранят ресурсы внутри них (экземпляр PDO, представляющий соединение с базой данных)
  • замыкания (по какой-то причине, вероятно, связанные с их ссылками, установленными с помощью оператора use )

Каждый объект, который составляет одну из этих переменных, также сталкивается с одной и той же проблемой: исключение, генерируемое во время сериализации. Только когда это происходит, вам приходится прибегать к нестандартным решениям, таким как два следующих.

__sleep () и __wakeup ()

Эта пара необязательных магических методов может сказать PHP сериализовать только часть объекта, что приводит к реализации шаблона Memento.

__sleep () должен возвращать список строк, которые соответствуют именам полей, представляющих состояние объекта, который мы хотим сохранить. Из руководства по PHP:

class Connection
{
    protected $connection;
    private $server, $username, $password, $db;

    public function __sleep()
    {
        return array('server', 'username', 'password', 'db');
    }

    ...

Этот метод будет вызываться во время сериализации, чтобы определить, что вставить в представление (исключая, например, замыкания, которые вы определили и сохранили в $ this.)

__wakeup () вызывается вместо конструктора после десериализации, чтобы позволить объекту восстановить ссылку на текущий процесс. У него нет аргументов, поэтому, если вы хотите определить его, вам придется извлечь соавторов из какого-то глобального состояния (синглтон, статическая, глобальная переменная) или воссоздать их самостоятельно.

Альтернативой методу __wakeup () является объект Repository, который передается в соавторы во время восстановления перед возвратом действительного объекта.

Сериализуемый интерфейс

Этот интерфейс является альтернативой __sleep (). Это позволяет вам указать, что сериализовать программно, вызывая serialize () для подмножества полей объекта. Опять же из руководства по PHP:

class obj implements Serializable {
    private $data;
    public function __construct() {
        $this->data = "My private data";
    }
    public function serialize() {
        return serialize($this->data);
    }
    public function unserialize($data) {
        $this->data = unserialize($data);
    }
}

Интерфейс является альтернативой __sleep () в том смысле, что вы можете использовать только один или другой метод, но не оба.

Под капотом

Причина, по которой вы должны знать, что нужно для правильной сериализации объекта, заключается в том, что это можно сделать без вашего ведома. Например, все, что сохраняется в $ _SESSION, сериализуется для переноса между различными процессами.
Это делается в конце сценария .php и вне обычного потока выполнения; если ошибка возникает из-за несериализуемого поля (например, замыкания) в вашем объекте $ _SESSION [‘state’], вы увидите такую ​​ошибку, как:

Fatal error: Exception thrown without a stack frame in Unknown on line 0

в ваши журналы. Это очень сложно отладить, если вы не знаете, что хранится в сеансе (или что вообще происходит). Если у вас есть рабочая версия кода (например, предыдущий коммит), запустите ручной тест в Apache и посмотрите на / var / lib / php5 (или ваше значение для session.save_path ), чтобы увидеть существующие сессии на сервере. и сериализованные объекты. Это поможет вам почувствовать, что находится внутри, и выяснить, какой объект является виновником; поскольку все ссылки на поля следуют, это может быть очень далеко от графика, который вы помещаете в $ _SESSION.