Я работал с PHP уже несколько лет, но до сих пор мне не приходилось иметь дело с сериализацией явно, используя только $_SESSION
. Теперь у меня есть проект, который требует от меня вручную реализовать механизм сериализации для определенных данных – и я понимаю, что проблема применима и к $_SESSION
.
У меня есть класс, который содержит ряд свойств. Большинство из этих свойств малы (как в потреблении памяти): числа, относительно короткие строки и т. Д. Однако класс также содержит некоторые свойства, которые могут содержать массивы HUGE (например, весь дамп таблицы базы данных: 100 000 строк по 100 полей каждый ). Как это бывает, это один из классов, который должен быть сериализован / десериализован – и, к счастью, свойства, содержащие большие массивы, не нужно сериализовать, поскольку они по существу являются временными работами и при необходимости восстанавливаются.
В таких обстоятельствах в Java я просто объявлял бы свойство transient
– и он был бы исключен из serialisaion. К сожалению, PHP не поддерживает такие квалификаторы.
Один из способов иметь дело с этим:
class A implements Serializable { private $var_small = 1234; private $var_big = array( ... ); //huge array, of course, not init in this way public function serialize() { $vars = get_object_vars($this); unset($vars['var_big']); return serialize($vars); } public function unserialize($data) { $vars = unserialize($data); foreach ($vars as $var => $value) { $this->$var = $value; } } }
неclass A implements Serializable { private $var_small = 1234; private $var_big = array( ... ); //huge array, of course, not init in this way public function serialize() { $vars = get_object_vars($this); unset($vars['var_big']); return serialize($vars); } public function unserialize($data) { $vars = unserialize($data); foreach ($vars as $var => $value) { $this->$var = $value; } } }
Однако это довольно громоздко, так как мне нужно будет обновлять метод serialize
каждый раз, когда я добавляю другое свойство переходного процесса. Кроме того, как только наследование вступит в игру, это становится еще более сложным – иметь дело, поскольку переходные свойства могут быть как в подклассе, так и в родительском. Я знаю, это все еще выполнимо, однако я бы предпочел делегировать как можно больше языку, а не изобретать колесо.
Итак, каков наилучший способ справиться с переходными свойствами? Или я что-то упускаю, и PHP поддерживает это из коробки?
Php предоставляет метод __sleep magic, который позволяет вам выбирать, какие атрибуты должны быть сериализованы.
EDIT Я тестировал, как работает __sleep()
когда наследование находится в игре:
<?php class A { private $a = 'String a'; private $b = 'String b'; public function __sleep() { echo "Sleep A\n"; return array( 'a'); } } class B extends A { private $c = 'String c'; private $d = 'String d'; public function __sleep() { echo "Sleep B\n"; return array( 'c'); } } class C extends A { private $e = 'String e'; private $f = 'String f'; public function __sleep() { echo "Sleep C\n"; return array_merge( parent::__sleep(), array( 'e')); } } $a = new A(); $b = new B(); $c = new C(); echo serialize( $a) ."\n"; // Result: O:1:"A":1:{s:4:"Aa";s:8:"String a";} // called "Sleep A" (correct) echo serialize( $b) ."\n"; // Result: O:1:"B":1:{s:4:"Bc";s:8:"String c";} // called just "Sleep B" (incorrect) echo serialize( $c) ."\n"; // Caused: PHP Notice: serialize(): "a" returned as member variable from __sleep() but does not exist ... // When you declare `private $a` as `protected $a` that class C returns: // O:1:"C":2:{s:4:"*a";s:8:"String a";s:4:"Ce";s:8:"String e";} // which is correct and called are both: "Sleep C" and "Sleep A"
Таким образом, кажется, что вы можете сериализовать родительские данные только в том случае, если они объявлены как защищенные: – /
EDIT 2 Я пробовал его с интерфейсом Serializable
со следующим кодом:
<?php class A implements Serializable { private $a = ''; private $b = ''; // Just initialize strings outside default values public function __construct(){ $this->a = 'String a'; $this->b = 'String b'; } public function serialize() { return serialize( array( 'a' => $this->a)); } public function unserialize( $data){ $array = unserialize( $data); $this->a = $array['a']; } } class B extends A { private $c = ''; private $d = ''; // Just initialize strings outside default values public function __construct(){ $this->c = 'String c'; $this->d = 'String d'; parent::__construct(); } public function serialize() { return serialize( array( 'c' => $this->c, '__parent' => parent::serialize())); } public function unserialize( $data){ $array = unserialize( $data); $this->c = $array['c']; parent::unserialize( $array['__parent']); } } $a = new A(); $b = new B(); echo serialize( $a) ."\n"; echo serialize( $b) ."\n"; $a = unserialize( serialize( $a)); // C:1:"A":29:{a:1:{s:1:"a";s:8:"String a";}} $b = unserialize( serialize( $b)); // C:1:"B":81:{a:2:{s:1:"c";s:8:"String c";s:8:"__parent";s:29:"a:1:{s:1:"a";s:8:"String a";}";}} print_r( $a); print_r( $b); /** Results: A Object ( [a:A:private] => String a [b:A:private] => ) B Object ( [c:B:private] => String c [d:B:private] => [a:A:private] => String a [b:A:private] => ) */
Итак, чтобы подвести итог : вы можете сериализовать классы через __sleep()
только если у них нет закрытых членов в суперклассе (которые также должны быть сериализованы). Вы можете сериализовать сложный объект с помощью интерфейса Serializable
, но это приносит вам некоторые накладные расходы на программирование.
Вы можете использовать __sleep
и __wakeup
. Для первого вы предоставляете массив имен свойств объектов, которые вы хотите сериализовать. Опустите «переходные» члены из этого списка.
__wakeup
вызывается немедленно, когда экземпляр неэтериализован. Вы можете использовать это, например, для пополнения непереходных свойств при некоторых условиях.