PHP5: Объект теряет структуру при сериализации/десериализации

camka

не самка
PHP5: Объект теряет структуру при сериализации/десериализации

Имею объект "дерево" достаточно комплексной структуры: свойства объекта являются ссылками на объекты того же класса. Так же имеется такое свойство как "индекс" для быстрого доступа к узлу по его имени (id). В индексе содержатся массив ссылок на все узлы дерева. Все индексы, кроме индекса корневого узла ссылаются на индекс корневого узла. То есть структура примерно такая:

PHP:
class node{
public $parent; // type node
public $first_sibling; // type node
public $last_sibling; // type node
public $first_child; // type node
public $index = array();
}
При сохранении объекта между запросами в сессию, структура сохраняется идеально и работа с объектом проходит без каких-либо экстцессов. Однако при сохранении объекта в фаил в сериализованном виде и последующей его десериализации и присвоения некой переменной начинаются аномалии. Некотрые ссылки теряются. Например при обращении к некоторому узлу через свойство "индекс" не происходит надлежащего изменения самого узла.

я начинаю грешить на сериализацию/десереализацию, поскольку проведенные испытаня открыли для меня интересный факт. А именно:

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

Так же код вида:
PHP:
echo "\n\r",'<br>H: ', serialize ( $_SESSION['node'] ) ;
echo '<br>C: ', serialize ( clone $_SESSION['node'] ) ;
echo '<br>X: ', serialize ( unserialize( serialize ( $_SESSION['node'] ) ) ) ;
echo '<br>X: ', serialize ( unserialize( serialize ( unserialize( serialize ( $_SESSION['node'] ) ) ) ) ) ;
выдает достаточно подозрительные результаты.

Код:
H: O:4:"node":7:{s:5:"props";a:4:{s:2:"id";i:0;s:4:"name";s:4:"root";s:6:"hidden";b:1;s:5:"perms";i:0;}s:6:"parent";N;s:13:"first_sibling";R:1;s:12:"last_sibling";R:1;s:11:"first_child";O:4:"node":7:{s:5:"props";a:4:{s:2:"id";i:1;s:4:"name";s:1:"1";s:6:"hidden";b:0;s:5:"perms";i:0;}s:6:"parent";R:1;s:13:"first_sibling";R:8;s:12:"last_sibling";R:8;s:11:"first_child";N;s:5:"index";a:2:{i:0;R:1;i:1;R:8;}s:7:"counter";i:2;}s:5:"index";R:15;s:7:"counter";R:16;}
C: O:4:"node":7:{s:5:"props";a:4:{s:2:"id";i:0;s:4:"name";s:4:"root";s:6:"hidden";b:1;s:5:"perms";i:0;}s:6:"parent";N;s:13:"first_sibling";O:4:"node":7:{s:5:"props";a:4:{s:2:"id";i:0;s:4:"name";s:4:"root";s:6:"hidden";b:1;s:5:"perms";i:0;}s:6:"parent";N;s:13:"first_sibling";R:8;s:12:"last_sibling";R:8;s:11:"first_child";O:4:"node":7:{s:5:"props";a:4:{s:2:"id";i:1;s:4:"name";s:1:"1";s:6:"hidden";b:0;s:5:"perms";i:0;}s:6:"parent";R:8;s:13:"first_sibling";R:15;s:12:"last_sibling";R:15;s:11:"first_child";N;s:5:"index";a:2:{i:0;R:8;i:1;R:15;}s:7:"counter";i:2;}s:5:"index";R:22;s:7:"counter";R:23;}s:12:"last_sibling";R:8;s:11:"first_child";R:15;s:5:"index";R:22;s:7:"counter";R:23;}
X: O:4:"node":7:{s:5:"props";a:4:{s:2:"id";i:0;s:4:"name";s:4:"root";s:6:"hidden";b:1;s:5:"perms";i:0;}s:6:"parent";N;s:13:"first_sibling";r:1;s:12:"last_sibling";r:1;s:11:"first_child";O:4:"node":7:{s:5:"props";a:4:{s:2:"id";i:1;s:4:"name";s:1:"1";s:6:"hidden";b:0;s:5:"perms";i:0;}s:6:"parent";r:1;s:13:"first_sibling";R:10;s:12:"last_sibling";R:10;s:11:"first_child";N;s:5:"index";a:2:{i:0;r:1;i:1;R:10;}s:7:"counter";i:2;}s:5:"index";R:18;s:7:"counter";R:20;}
X: O:4:"node":7:{s:5:"props";a:4:{s:2:"id";i:0;s:4:"name";s:4:"root";s:6:"hidden";b:1;s:5:"perms";i:0;}s:6:"parent";N;s:13:"first_sibling";r:1;s:12:"last_sibling";r:1;s:11:"first_child";O:4:"node":7:{s:5:"props";a:4:{s:2:"id";i:1;s:4:"name";s:1:"1";s:6:"hidden";b:0;s:5:"perms";i:0;}s:6:"parent";r:1;s:13:"first_sibling";R:10;s:12:"last_sibling";R:10;s:11:"first_child";N;s:5:"index";a:2:{i:0;r:1;i:1;R:10;}s:7:"counter";i:2;}s:5:"index";R:18;s:7:"counter";R:20;}
Собственно говоря, я и хотел проконсультироваться, нормальное ли это поведение пхп, если один и тот же объект при двойной сериализации/десериализации изменяется. Копал в коментариях мануала. Нашел там подобные проблемы, но ни одно решение не помогло. Посему обращаюсь сюда.
 

Screjet

Новичок
Вообще удивительно, что подобные структуры можно сериализовать..
Даже представить немогу как сериализатор будет избавлятся от рекурсии типа $node->parent->child->parent->...

имхо, рекурсивные объекты лучше не сереализовать вообще. пхп "знает" про ссылки на объекты, но сериализатор ничего о них не знает и воспринимает как объекты.
 

camka

не самка
а как же тогда данные хранятся в сессии? они же там, насколько я знаю, в том же сериализованном виде (или похожем), однако как-то же восстанавливаются, и без проблем?
 

Screjet

Новичок
Восстанавливаются, но уже не как ссылки на объекты, а как объекты.
А сий факт может очень навредить, например когда в родетеле вызываем метод, который _должен_ влиять на вызывающий объект, в итоге вызывающий не меняется (меняется его копия).
 

camka

не самка
Меня бы вполне устроил такой вариант, поскольку он работоспособен в моей ситуации. И, как мне все-таки кажется, и есть тому подтверждение в мануале, ссылки восстанавливаются именно как ссылки, а не как объекты. Интуитивно подозреваю, что "R" в сериализованной строке как раз-таки и значит - reference. Поправте меня, если я заблуждаюсь.

[manual]
Код:
To make the serialized string into a PHP value again, use 
unserialize(). serialize() handles all types, except the resource-
type. You can even serialize() arrays that contain references to 
itself. References inside the array/object you are serialize()ing will 
also be stored.
[/manual]
 

jer

...
да, в 4-ке работает. ссылки сериализуются нормально, сам это использую. сомневаюсь что 5-ке что-то изменилось, может просто баг... ?
 

camka

не самка
Однако, загадка. Ничего не делал, а баги и след простыл. Пытаюсь ее выцепить, а не удается. Вот вам и мистика. Будем ждать, когда всплывет.

А вот еще один казус на закуску:

при присваивании одной и той же переменной какого-то ее свойства того же типа, что и сама переменная, вылазит сегфолт.

Переменная и является вышеописаным объктом "Дерево"

PHP:
$MENU = $MENU->index[ $someid ];
обошел пока клонированием, а не присвоением:
PHP:
$MENU = clone $MENU->index[ $someid ];
 

Miky

Guest
Незнаю в тему ли, но у меня тоже не получилось ни сохранить в сессию, ни серелизовать штатными методоми объект DOMDocument, видимо по той же причине (это тоже дерево). Там был здоровенный XML, а в серелизованном виде это занимало 20 байт от силы.
 

Profic

just Profic (PHP5 BetaTeam)
Miky
ни один объект встроенного класса (как например DOMDocument) не сериализуется вообще. По крайней мере пока. В php 5.1 они, возможно, станут сериализоваться.
 

camka

не самка
resurrection

Поднимаю вопрос сново, потому как проблемы вернулись. Сразу извиняюся за длинный код, но надеюсь он будет понятен.

PHP:
<?php
class node
{
	public $props		= array();	// array of node properties
	public $parent		= null;		// reference to parent node
	public $first_sibling	= null;		// reference to right sibling node 
	public $last_sibling	= null;		// reference to left sibling node
	public $first_child	= null;		// reference to first child node
	public $index		= array();	// reference to root index array
	public $counter		= 0;		// first available id
}
/*
structure
---------

0. root
	1. home
	2. login
		4. register
		5. forgot_pass
	3. menu_editor

*/

$str = 	'O:4:"node":7:{s:5:"props";a:8:{s:2:"id";i:0;s:4:"name";'.
	's:4:"root";s:6:"hidden";b:1;s:5:"perms";i:0;s:6:"active"'.
	';b:0;s:5:"cache";i:0;s:5:"alias";s:4:"root";s:10:"standa'.
	'lone";b:0;}s:6:"parent";N;s:13:"first_sibling";R:1;s:12:'.
	'"last_sibling";R:1;s:11:"first_child";O:4:"node":7:{s:5:'.
	'"props";a:8:{s:2:"id";i:1;s:4:"name";s:4:"home";s:6:"hid'.
	'den";b:0;s:5:"perms";i:0;s:6:"active";b:1;s:5:"alias";s:'.
	'4:"home";s:5:"cache";i:-1;s:10:"standalone";b:0;}s:6:"pa'.
	'rent";R:1;s:13:"first_sibling";O:4:"node":7:{s:5:"props"'.
	';a:8:{s:2:"id";i:2;s:4:"name";s:5:"login";s:6:"hidden";b'.
	':1;s:5:"perms";i:0;s:6:"active";b:1;s:5:"alias";s:5:"log'.
	'in";s:5:"cache";i:0;s:10:"standalone";b:0;}s:6:"parent";'.
	'R:1;s:13:"first_sibling";O:4:"node":7:{s:5:"props";a:8:{'.
	's:2:"id";i:3;s:4:"name";s:11:"menu_editor";s:6:"hidden";'.
	'b:0;s:5:"perms";i:128;s:6:"active";b:1;s:5:"alias";s:11:'.
	'"menu_editor";s:5:"cache";i:0;s:10:"standalone";b:0;}s:6'.
	':"parent";R:1;s:13:"first_sibling";R:12;s:12:"last_sibli'.
	'ng";R:22;s:11:"first_child";N;s:5:"index";a:6:{i:0;R:1;i'.
	':1;R:12;i:2;R:22;i:3;R:32;i:4;O:4:"node":7:{s:5:"props";'.
	'a:8:{s:2:"id";i:4;s:4:"name";s:8:"register";s:6:"hidden"'.
	';b:1;s:5:"perms";i:0;s:6:"active";b:1;s:5:"alias";s:8:"r'.
	'egister";s:5:"cache";i:0;s:10:"standalone";b:0;}s:6:"par'.
	'ent";R:22;s:13:"first_sibling";O:4:"node":7:{s:5:"props"'.
	';a:8:{s:2:"id";i:5;s:4:"name";s:11:"forgot_pass";s:6:"hi'.
	'dden";b:1;s:5:"perms";i:0;s:6:"active";b:1;s:5:"alias";s'.
	':11:"forgot_pass";s:5:"cache";i:0;s:10:"standalone";b:0;'.
	'}s:6:"parent";R:22;s:13:"first_sibling";R:44;s:12:"last_'.
	'sibling";R:44;s:11:"first_child";N;s:5:"index";R:43;s:7:'.
	'"counter";i:6;}s:12:"last_sibling";R:54;s:11:"first_chil'.
	'd";N;s:5:"index";R:43;s:7:"counter";R:65;}i:5;R:54;}s:7:'.
	'"counter";R:65;}s:12:"last_sibling";R:12;s:11:"first_chi'.
	'ld";R:44;s:5:"index";R:43;s:7:"counter";R:65;}s:12:"last'.
	'_sibling";R:32;s:11:"first_child";N;s:5:"index";R:43;s:7'.
	':"counter";R:65;}s:5:"index";R:43;s:7:"counter";R:65;}';

$node = unserialize($str);

echo '<pre>';
/*
foreach( $node->index as $k => $v )
	echo $k, ' - ', $v->props['name'], "\n";
*/

/* 1. */ #$node = &$node->index[4];
/*
0 - 
Notice: Trying to get property of non-object in clone.php on line 38
1 - home
2 - login
3 - menu_editor
4 - register
5 - forgot_pass
*/


/* 2. */ #$node = $node->index[4];
/*
0 - register
1 - home
2 - login
3 - menu_editor
4 - register
5 - forgot_pass
*/


/* 3. */ #$node = clone $node->index[4];
/*
0 - register
1 - home
2 - login
3 - menu_editor
4 - register
5 - forgot_pass
*/


/* 4. */ #$node = unserialize(serialize($node->index[4]));
/*
0 - root
1 - home
2 - login
3 - menu_editor
4 - register
5 - forgot_pass
*/

foreach( $node->index as $k => $v )
	echo $k, ' - ', $v->props['name'], "\n";

?>
Если раскомментировать отдельно каждую из 4-х пронумерованых строк, каждый раз получаем совершенно разные результаты. То есть при присвоении структура объекта странным образом изменяется.

В итоге требуемый (корректный) результат получается только при сериализации/десериализации.

Кроме того, если запустить первый закоментированный цикл до присвоения, то при любом из 4-х вариантов второй цикл аккуратно вешает апач.

Если кто-то понял о чем речь, буду очень рад услышать комментарии.
 

Screjet

Новичок
Попробовал. Отваливается. Далее переименовал $node > $node1, прекратил отваливаться. На первый взгляд гдето в движке неправильно освобождается память.

зы. Тут без 100гр не разобраться.
 

camka

не самка
При переименовании ссылка остается все-таки на старую переменную, поскольку если сделать unset($node); , то история повторяется.

а 100гр - это 100 гринов?
 

Screjet

Новичок
Нет, 100грамм :)

Старая история с (не)освобождением памяти и/или сохранения целостности ссылок. Похоже так и не пофиксили.
 

camka

не самка
Сколько не пытался, не удалось составить примитивного примера для повторения ошибки. Поэтому я пока склоняюсь к мысли, что может быть мой класс какой-то нездоровый. Однако, хотелось бы надеятся, что это не так.
 

Screjet

Новичок
Не заморачивайся на багах ПХП, просто пытайся обойти их. Ради этого приходится жертвовать всякими наворотами в коде, но, это лучше чем терять драгоценное время.
 

camka

не самка
То есть ты предлагаешь забить на форум и сочинять разного рода воркараунды для каждой проблемы. Не согласен. Я думал может кто сведущий подсобит. Ок. Буду рубить на отмаш.
 

Screjet

Новичок
Ну есть же вариант (в твоем примере) переименовать (закомментированные) $node в другое название и все будет работать.
 

camka

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

Screjet

Новичок
Вобщем, тогда запускай пхп с --enable-debug изучай С (если надо) и смотри что не так с памятью делает ПХП. Вступай в группу саппорта пхп :)

Кстати пробовал портировать под пхп4? Может там лучше дела обстоят?
 
Сверху