Skip to content

Commit bec66ee

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

14 files changed

+848
-1
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ PHP NEWS
149149
. Fix RCN violations in array functions. (nielsdos)
150150
. Fixed GH-18976 pack() overflow with h/H format and INT_MAX repeater value.
151151
(David Carlier)
152+
. Fixed GH-14402 (SplPriorityQueue, SplMinHeap, and SplMaxHeap lost their
153+
data on serialize()). (alexandre-daubois)
152154

153155
- Streams:
154156
. Fixed GH-13264 (fgets() and stream_get_line() do not return false on filter

ext/spl/spl_heap.c

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

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

ext/spl/spl_heap.stub.php

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

9292
/** @tentative-return-type */
9393
public function __debugInfo(): array {}
94+
95+
/** @tentative-return-type */
96+
public function __serialize(): array {}
97+
98+
/** @tentative-return-type */
99+
public function __unserialize(array $data): void {}
94100
}
95101

96102
abstract class SplHeap implements Iterator, Countable
@@ -136,6 +142,12 @@ public function isCorrupted(): bool {}
136142

137143
/** @tentative-return-type */
138144
public function __debugInfo(): array {}
145+
146+
/** @tentative-return-type */
147+
public function __serialize(): array {}
148+
149+
/** @tentative-return-type */
150+
public function __unserialize(array $data): void {}
139151
}
140152

141153
class SplMinHeap extends SplHeap

ext/spl/spl_heap_arginfo.h

Lines changed: 19 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
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)