Skip to content

Commit 7e46992

Browse files
committed
Allow overriding the behaviour of appending operator via append()
1 parent 20f99d0 commit 7e46992

File tree

5 files changed

+128
-60
lines changed

5 files changed

+128
-60
lines changed

ext/spl/spl_array.c

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,14 @@ typedef struct _spl_array_object {
4949
unsigned char nApplyCount;
5050
bool is_child;
5151
Bucket *bucket;
52+
/* Overridden ArrayAccess methods */
5253
zend_function *fptr_offset_get;
5354
zend_function *fptr_offset_set;
5455
zend_function *fptr_offset_has;
5556
zend_function *fptr_offset_del;
57+
/* Overridden append() method */
58+
zend_function *fptr_append;
59+
/* Overridden count() method */
5660
zend_function *fptr_count;
5761
zend_class_entry* ce_get_iterator;
5862
zend_object std;
@@ -192,6 +196,7 @@ static zend_object *spl_array_object_new_ex(zend_class_entry *class_type, zend_o
192196
ZEND_ASSERT(parent);
193197

194198
if (inherited) {
199+
/* Find potentially overridden ArrayAccess methods */
195200
intern->fptr_offset_get = zend_hash_str_find_ptr(&class_type->function_table, "offsetget", sizeof("offsetget") - 1);
196201
if (intern->fptr_offset_get->common.scope == parent) {
197202
intern->fptr_offset_get = NULL;
@@ -208,7 +213,12 @@ static zend_object *spl_array_object_new_ex(zend_class_entry *class_type, zend_o
208213
if (intern->fptr_offset_del->common.scope == parent) {
209214
intern->fptr_offset_del = NULL;
210215
}
211-
/* Find count() method */
216+
/* Find potentially overridden append() method */
217+
intern->fptr_append = zend_hash_str_find_ptr(&class_type->function_table, "append", sizeof("append") - 1);
218+
if (intern->fptr_append->common.scope == parent) {
219+
intern->fptr_append = NULL;
220+
}
221+
/* Find potentially overridden count() method */
212222
intern->fptr_count = zend_hash_find_ptr(&class_type->function_table, ZSTR_KNOWN(ZEND_STR_COUNT));
213223
if (intern->fptr_count->common.scope == parent) {
214224
intern->fptr_count = NULL;
@@ -460,7 +470,7 @@ static uint32_t spl_array_set_refcount(bool is_child, HashTable *ht, uint32_t re
460470
return old_refcount;
461471
} /* }}} */
462472

463-
static void spl_array_write_dimension_ex(int check_inherited, zend_object *object, zval *offset, zval *value) /* {{{ */
473+
static void spl_array_write_dimension_ex(bool check_inherited, zend_object *object, zval *offset, zval *value) /* {{{ */
464474
{
465475
spl_array_object *intern = spl_array_from_obj(object);
466476
HashTable *ht;
@@ -474,6 +484,20 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec
474484

475485
/* We are appending */
476486
if (!offset) {
487+
/* append() method is overridden, so call it */
488+
if (check_inherited && intern->fptr_append) {
489+
zend_call_known_function(
490+
intern->fptr_append,
491+
object,
492+
object->ce,
493+
/* retval_ptr */NULL,
494+
/* param_count */ 1,
495+
/* params */ value,
496+
/* named_params */ NULL
497+
);
498+
return;
499+
}
500+
477501
/* Cannot append if backing value is an object */
478502
if (spl_array_is_object(intern)) {
479503
zend_throw_error(NULL, "Cannot append properties to objects, use %s::offsetSet() instead", ZSTR_VAL(object->ce->name));
@@ -491,6 +515,7 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec
491515
return;
492516
}
493517

518+
/* offsetSet() method is overridden, so call it */
494519
if (check_inherited && intern->fptr_offset_set) {
495520
zend_call_method_with_2_params(object, object->ce, &intern->fptr_offset_set, "offsetSet", NULL, offset, value);
496521
return;
@@ -542,7 +567,7 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec
542567

543568
static void spl_array_write_dimension(zend_object *object, zval *offset, zval *value) /* {{{ */
544569
{
545-
spl_array_write_dimension_ex(1, object, offset, value);
570+
spl_array_write_dimension_ex(/* check_inherited */ true, object, offset, value);
546571
} /* }}} */
547572

548573
static void spl_array_unset_dimension_ex(int check_inherited, zend_object *object, zval *offset) /* {{{ */
@@ -703,9 +728,10 @@ PHP_METHOD(ArrayObject, offsetSet)
703728
if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &index, &value) == FAILURE) {
704729
RETURN_THROWS();
705730
}
706-
spl_array_write_dimension_ex(0, Z_OBJ_P(ZEND_THIS), index, value);
731+
spl_array_write_dimension_ex(/* check_inherited */ false, Z_OBJ_P(ZEND_THIS), index, value);
707732
} /* }}} */
708733

734+
/* Needed for spl_iterators.c:2938 */
709735
void spl_array_iterator_append(zval *object, zval *append_value) /* {{{ */
710736
{
711737
spl_array_write_dimension(Z_OBJ_P(object), NULL, append_value);
@@ -719,7 +745,7 @@ PHP_METHOD(ArrayObject, append)
719745
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &value) == FAILURE) {
720746
RETURN_THROWS();
721747
}
722-
spl_array_iterator_append(ZEND_THIS, value);
748+
spl_array_write_dimension_ex(/* check_inherited */ false, Z_OBJ_P(ZEND_THIS), /* offset */ NULL, value);
723749
} /* }}} */
724750

725751
/* {{{ Unsets the value at the specified $index. */

ext/spl/tests/bug33136.phpt renamed to ext/spl/tests/ArrayObject/bug33136.phpt

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class Collection extends ArrayObject
99

1010
function __construct()
1111
{
12-
$this->data = array();
12+
$this->data = [];
1313
parent::__construct($this->data);
1414
}
1515

@@ -26,7 +26,7 @@ class Collection extends ArrayObject
2626
}
2727
}
2828

29-
echo "\n\nInitiate Obj\n";
29+
echo "Initiate Obj\n";
3030
$arrayObj = new Collection();
3131

3232
echo "Assign values\n";
@@ -41,7 +41,7 @@ var_dump($arrayObj[1]);
4141
$arrayObj["foo"] = "baz";
4242
var_dump($arrayObj["foo"]);
4343

44-
print_r($arrayObj);
44+
var_dump($arrayObj);
4545

4646
var_dump(count($arrayObj));
4747

@@ -58,18 +58,18 @@ string(3) "bar"
5858
Collection::offsetSet(foo,baz)
5959
Collection::offsetGet(foo)
6060
string(3) "baz"
61-
Collection Object
62-
(
63-
[data:Collection:private] => Array
64-
(
65-
)
66-
67-
[storage:ArrayObject:private] => Array
68-
(
69-
[0] => foo
70-
[1] => bar
71-
[foo] => baz
72-
)
73-
74-
)
61+
object(Collection)#1 (2) {
62+
["data":"Collection":private]=>
63+
array(0) {
64+
}
65+
["storage":"ArrayObject":private]=>
66+
array(3) {
67+
[0]=>
68+
string(3) "foo"
69+
[1]=>
70+
string(3) "bar"
71+
["foo"]=>
72+
string(3) "baz"
73+
}
74+
}
7575
int(3)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
Bug #34548 (Method append() in class extended from ArrayObject crashes PHP)
3+
--FILE--
4+
<?php
5+
6+
class Collection extends ArrayObject
7+
{
8+
public function add($dataArray)
9+
{
10+
foreach ($dataArray as $value) {
11+
$this->append($value);
12+
}
13+
}
14+
}
15+
16+
$data1 = ['one', 'two', 'three'];
17+
$data2 = ['four', 'five'];
18+
19+
$foo = new Collection($data1);
20+
$foo->add($data2);
21+
22+
var_dump($foo->getArrayCopy());
23+
24+
echo "Done\n";
25+
?>
26+
--EXPECT--
27+
array(5) {
28+
[0]=>
29+
string(3) "one"
30+
[1]=>
31+
string(3) "two"
32+
[2]=>
33+
string(5) "three"
34+
[3]=>
35+
string(4) "four"
36+
[4]=>
37+
string(4) "five"
38+
}
39+
Done
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
Subclassing ArrayObject should still call overridden offsetSet method when appending
3+
--EXTENSIONS--
4+
spl
5+
--FILE--
6+
<?php
7+
class A implements ArrayAccess {
8+
public function offsetSet($offset, $value): void {
9+
var_dump(__METHOD__);
10+
var_dump($offset);
11+
var_dump($value);
12+
}
13+
public function offsetGet($offset): mixed {}
14+
public function offsetUnset($offset): void {}
15+
public function offsetExists($offset): bool {}
16+
}
17+
18+
class B extends ArrayObject {
19+
public function offsetSet($offset, $value): void {
20+
var_dump(__METHOD__);
21+
var_dump($offset);
22+
var_dump($value);
23+
}
24+
public function append(mixed $value) : void{
25+
var_dump(__METHOD__);
26+
var_dump($value);
27+
}
28+
}
29+
30+
$a = new A();
31+
$a[] = 1;
32+
33+
$b = new B();
34+
$b[] = 1;
35+
?>
36+
--EXPECT--
37+
string(12) "A::offsetSet"
38+
NULL
39+
int(1)
40+
string(9) "B::append"
41+
int(1)

ext/spl/tests/bug34548.phpt

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)