Skip to content

Commit 2c9bfbb

Browse files
Fix GH-14402: Add support for serialization in SplPriorityQueue, SplMinHeap and SplMaxHeap
1 parent c3bee21 commit 2c9bfbb

13 files changed

+829
-0
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ PHP NEWS
8484
opened directory has been deprecated. (Girgias)
8585
. Fixed bug GH-19153 (#[\Attribute] validation should error on
8686
trait/interface/enum/abstract class). (DanielEScherzer)
87+
. Fixed GH-14402 (SplPriorityQueue, SplMinHeap, and SplMaxHeap lost their
88+
data on serialize()). (alexandre-daubois)
8789

8890
- XML:
8991
. The xml_parser_free() function has been deprecated. (DanielEScherzer)

ext/spl/spl_heap.c

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,206 @@ PHP_METHOD(SplPriorityQueue, __debugInfo)
10751075
RETURN_ARR(spl_heap_object_get_debug_info(spl_ce_SplPriorityQueue, Z_OBJ_P(ZEND_THIS)));
10761076
} /* }}} */
10771077

1078+
static void spl_heap_serialize_properties(zval *return_value, spl_heap_object *intern)
1079+
{
1080+
HashTable *props = zend_std_get_properties(&intern->std);
1081+
1082+
ZVAL_ARR(return_value, props ? zend_array_dup(props) : zend_new_array(0));
1083+
}
1084+
1085+
static void spl_heap_serialize_internal_state(zval *return_value, spl_heap_object *intern, bool is_pqueue)
1086+
{
1087+
zval heap_elements;
1088+
int heap_count = intern->heap->count;
1089+
1090+
array_init(return_value);
1091+
add_assoc_long(return_value, "flags", intern->flags);
1092+
1093+
if (heap_count == 0) {
1094+
return;
1095+
}
1096+
1097+
array_init_size(&heap_elements, heap_count);
1098+
1099+
for (int heap_idx = 0; heap_idx < heap_count; ++heap_idx) {
1100+
if (is_pqueue) {
1101+
spl_pqueue_elem *elem = spl_heap_elem(intern->heap, heap_idx);
1102+
zval entry;
1103+
array_init(&entry);
1104+
add_assoc_zval_ex(&entry, "data", sizeof("data") - 1, &elem->data);
1105+
Z_TRY_ADDREF(elem->data);
1106+
add_assoc_zval_ex(&entry, "priority", sizeof("priority") - 1, &elem->priority);
1107+
Z_TRY_ADDREF(elem->priority);
1108+
zend_hash_next_index_insert(Z_ARRVAL(heap_elements), &entry);
1109+
} else {
1110+
zval *elem = spl_heap_elem(intern->heap, heap_idx);
1111+
zend_hash_next_index_insert(Z_ARRVAL(heap_elements), elem);
1112+
Z_TRY_ADDREF_P(elem);
1113+
}
1114+
}
1115+
1116+
add_assoc_zval(return_value, "heap_elements", &heap_elements);
1117+
}
1118+
1119+
static void spl_heap_unserialize_properties(HashTable *props_ht, spl_heap_object *intern)
1120+
{
1121+
object_properties_load(&intern->std, props_ht);
1122+
if (!EG(exception)) {
1123+
return;
1124+
}
1125+
1126+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
1127+
}
1128+
1129+
static void spl_heap_unserialize_internal_state(HashTable *state_ht, spl_heap_object *intern, zval *this_ptr, bool is_pqueue)
1130+
{
1131+
zval *flags_val = zend_hash_str_find(state_ht, "flags", sizeof("flags") - 1);
1132+
if (!flags_val || Z_TYPE_P(flags_val) != IS_LONG) {
1133+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object: missing or invalid flags", ZSTR_VAL(intern->std.ce->name));
1134+
return;
1135+
}
1136+
1137+
intern->flags = (int) Z_LVAL_P(flags_val);
1138+
1139+
zval *heap_elements = zend_hash_str_find(state_ht, "heap_elements", sizeof("heap_elements") - 1);
1140+
if (!heap_elements) {
1141+
return;
1142+
}
1143+
1144+
if (Z_TYPE_P(heap_elements) != IS_ARRAY) {
1145+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object: heap_elements must be an array", ZSTR_VAL(intern->std.ce->name));
1146+
return;
1147+
}
1148+
1149+
zval *val;
1150+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(heap_elements), val) {
1151+
if (is_pqueue) {
1152+
if (Z_TYPE_P(val) != IS_ARRAY) {
1153+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object: priority queue elements must be arrays", ZSTR_VAL(intern->std.ce->name));
1154+
return;
1155+
}
1156+
1157+
zval *data_val = zend_hash_str_find(Z_ARRVAL_P(val), "data", sizeof("data") - 1);
1158+
zval *priority_val = zend_hash_str_find(Z_ARRVAL_P(val), "priority", sizeof("priority") - 1);
1159+
1160+
if (!data_val || !priority_val) {
1161+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object: priority queue elements must have data and priority", ZSTR_VAL(intern->std.ce->name));
1162+
return;
1163+
}
1164+
1165+
spl_pqueue_elem elem;
1166+
ZVAL_COPY(&elem.data, data_val);
1167+
ZVAL_COPY(&elem.priority, priority_val);
1168+
spl_ptr_heap_insert(intern->heap, &elem, this_ptr);
1169+
} else {
1170+
Z_TRY_ADDREF_P(val);
1171+
spl_ptr_heap_insert(intern->heap, val, this_ptr);
1172+
}
1173+
} ZEND_HASH_FOREACH_END();
1174+
}
1175+
1176+
PHP_METHOD(SplPriorityQueue, __serialize)
1177+
{
1178+
spl_heap_object *intern = Z_SPLHEAP_P(ZEND_THIS);
1179+
zval props, state;
1180+
1181+
ZEND_PARSE_PARAMETERS_NONE();
1182+
1183+
array_init(return_value);
1184+
1185+
spl_heap_serialize_properties(&props, intern);
1186+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &props);
1187+
1188+
spl_heap_serialize_internal_state(&state, intern, true);
1189+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &state);
1190+
}
1191+
1192+
PHP_METHOD(SplPriorityQueue, __unserialize)
1193+
{
1194+
HashTable *data;
1195+
spl_heap_object *intern = Z_SPLHEAP_P(ZEND_THIS);
1196+
zval *props, *state;
1197+
1198+
ZEND_PARSE_PARAMETERS_START(1, 1)
1199+
Z_PARAM_ARRAY_HT(data)
1200+
ZEND_PARSE_PARAMETERS_END();
1201+
1202+
if (zend_hash_num_elements(data) != 2) {
1203+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
1204+
RETURN_THROWS();
1205+
}
1206+
1207+
props = zend_hash_index_find(data, 0);
1208+
if (!props || Z_TYPE_P(props) != IS_ARRAY) {
1209+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
1210+
RETURN_THROWS();
1211+
}
1212+
1213+
spl_heap_unserialize_properties(Z_ARRVAL_P(props), intern);
1214+
if (EG(exception)) {
1215+
RETURN_THROWS();
1216+
}
1217+
1218+
state = zend_hash_index_find(data, 1);
1219+
if (!state || Z_TYPE_P(state) != IS_ARRAY) {
1220+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
1221+
RETURN_THROWS();
1222+
}
1223+
1224+
spl_heap_unserialize_internal_state(Z_ARRVAL_P(state), intern, ZEND_THIS, true);
1225+
}
1226+
1227+
PHP_METHOD(SplHeap, __serialize)
1228+
{
1229+
spl_heap_object *intern = Z_SPLHEAP_P(ZEND_THIS);
1230+
zval props, state;
1231+
1232+
ZEND_PARSE_PARAMETERS_NONE();
1233+
1234+
array_init(return_value);
1235+
1236+
spl_heap_serialize_properties(&props, intern);
1237+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &props);
1238+
1239+
spl_heap_serialize_internal_state(&state, intern, false);
1240+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &state);
1241+
}
1242+
1243+
PHP_METHOD(SplHeap, __unserialize)
1244+
{
1245+
HashTable *data;
1246+
spl_heap_object *intern = Z_SPLHEAP_P(ZEND_THIS);
1247+
zval *props, *state;
1248+
1249+
ZEND_PARSE_PARAMETERS_START(1, 1)
1250+
Z_PARAM_ARRAY_HT(data)
1251+
ZEND_PARSE_PARAMETERS_END();
1252+
1253+
if (zend_hash_num_elements(data) != 2) {
1254+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
1255+
RETURN_THROWS();
1256+
}
1257+
1258+
props = zend_hash_index_find(data, 0);
1259+
if (!props || Z_TYPE_P(props) != IS_ARRAY) {
1260+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
1261+
RETURN_THROWS();
1262+
}
1263+
1264+
spl_heap_unserialize_properties(Z_ARRVAL_P(props), intern);
1265+
if (EG(exception)) {
1266+
RETURN_THROWS();
1267+
}
1268+
1269+
state = zend_hash_index_find(data, 1);
1270+
if (!state || Z_TYPE_P(state) != IS_ARRAY) {
1271+
zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(intern->std.ce->name));
1272+
RETURN_THROWS();
1273+
}
1274+
1275+
spl_heap_unserialize_internal_state(Z_ARRVAL_P(state), intern, ZEND_THIS, false);
1276+
}
1277+
10781278
/* iterator handler table */
10791279
static const zend_object_iterator_funcs spl_heap_it_funcs = {
10801280
spl_heap_it_dtor,

ext/spl/spl_heap.stub.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ public function getExtractFlags(): int {}
8282

8383
/** @tentative-return-type */
8484
public function __debugInfo(): array {}
85+
86+
/** @tentative-return-type */
87+
public function __serialize(): array {}
88+
89+
/** @tentative-return-type */
90+
public function __unserialize(array $data): void {}
8591
}
8692

8793
abstract class SplHeap implements Iterator, Countable
@@ -127,6 +133,12 @@ public function isCorrupted(): bool {}
127133

128134
/** @tentative-return-type */
129135
public function __debugInfo(): array {}
136+
137+
/** @tentative-return-type */
138+
public function __serialize(): array {}
139+
140+
/** @tentative-return-type */
141+
public function __unserialize(array $data): void {}
130142
}
131143

132144
class SplMinHeap extends SplHeap
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
--TEST--
2+
SplHeap serialization format - indexed array format prevents conflicts
3+
--FILE--
4+
<?php
5+
6+
$heap = new SplMaxHeap();
7+
$heap->insert(100);
8+
$heap->insert(50);
9+
$heap->insert(200);
10+
11+
$serialized_data = $heap->__serialize();
12+
13+
echo "Serialization data structure:\n";
14+
echo "Is indexed array: " . (isset($serialized_data[0], $serialized_data[1]) && count($serialized_data) === 2 ? 'YES' : 'NO') . "\n";
15+
echo "Index 0 (properties) is array: " . (is_array($serialized_data[0]) ? 'YES' : 'NO') . "\n";
16+
echo "Index 1 (internal state) is array: " . (is_array($serialized_data[1]) ? 'YES' : 'NO') . "\n";
17+
18+
$internal_state = $serialized_data[1];
19+
echo "Internal state contains 'flags': " . (array_key_exists('flags', $internal_state) ? 'YES' : 'NO') . "\n";
20+
echo "Internal state contains 'heap_elements': " . (array_key_exists('heap_elements', $internal_state) ? 'YES' : 'NO') . "\n";
21+
echo "Flags value is numeric: " . (is_numeric($internal_state['flags']) ? 'YES' : 'NO') . "\n";
22+
echo "Heap elements is array: " . (is_array($internal_state['heap_elements']) ? 'YES' : 'NO') . "\n";
23+
echo "Heap elements count: " . count($internal_state['heap_elements']) . "\n";
24+
25+
$min_heap = new SplMinHeap();
26+
$min_heap->insert(100);
27+
$min_heap->insert(50);
28+
29+
$min_data = $min_heap->__serialize();
30+
31+
$pq = new SplPriorityQueue();
32+
$pq->insert('data', 10);
33+
$pq->setExtractFlags(SplPriorityQueue::EXTR_DATA);
34+
35+
$pq_data = $pq->__serialize();
36+
37+
$pq_internal = $pq_data[1];
38+
if (isset($pq_internal['heap_elements']) && is_array($pq_internal['heap_elements'])) {
39+
$first_element = $pq_internal['heap_elements'][0];
40+
echo "PQ elements have 'data' key: " . (array_key_exists('data', $first_element) ? 'YES' : 'NO') . "\n";
41+
echo "PQ elements have 'priority' key: " . (array_key_exists('priority', $first_element) ? 'YES' : 'NO') . "\n";
42+
}
43+
44+
?>
45+
--EXPECT--
46+
Serialization data structure:
47+
Is indexed array: YES
48+
Index 0 (properties) is array: YES
49+
Index 1 (internal state) is array: YES
50+
Internal state contains 'flags': YES
51+
Internal state contains 'heap_elements': YES
52+
Flags value is numeric: YES
53+
Heap elements is array: YES
54+
Heap elements count: 3
55+
PQ elements have 'data' key: YES
56+
PQ elements have 'priority' key: YES

0 commit comments

Comments
 (0)