Skip to content

Commit d9749d0

Browse files
Add a ReflectionClass::newInstanceFromData() method that behaves the same as PDOStatement::fetchObject()
1 parent 17826d3 commit d9749d0

File tree

5 files changed

+167
-2
lines changed

5 files changed

+167
-2
lines changed

ext/reflection/php_reflection.c

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5127,6 +5127,62 @@ ZEND_METHOD(ReflectionClass, newInstanceArgs)
51275127
}
51285128
/* }}} */
51295129

5130+
/* {{{ Returns an instance of this class whose properties are filled with the given data before the constructor is called */
5131+
ZEND_METHOD(ReflectionClass, newInstanceFromData)
5132+
{
5133+
reflection_object *intern;
5134+
zend_class_entry *ce;
5135+
int argc = 0;
5136+
HashTable *data, *args = NULL;
5137+
zend_function *constructor;
5138+
zend_string *key;
5139+
zval *val;
5140+
5141+
GET_REFLECTION_OBJECT_PTR(ce);
5142+
5143+
ZEND_PARSE_PARAMETERS_START(1, 2)
5144+
Z_PARAM_ARRAY_HT(data)
5145+
Z_PARAM_OPTIONAL
5146+
Z_PARAM_ARRAY_HT(args)
5147+
ZEND_PARSE_PARAMETERS_END();
5148+
5149+
if (args) {
5150+
argc = zend_hash_num_elements(args);
5151+
}
5152+
5153+
if (UNEXPECTED(object_init_ex(return_value, ce) != SUCCESS)) {
5154+
return;
5155+
}
5156+
5157+
ZEND_HASH_FOREACH_STR_KEY_VAL(data, key, val) {
5158+
zend_update_property_ex(ce, Z_OBJ_P(return_value), key, val);
5159+
} ZEND_HASH_FOREACH_END();
5160+
5161+
const zend_class_entry *old_scope = EG(fake_scope);
5162+
EG(fake_scope) = ce;
5163+
constructor = Z_OBJ_HT_P(return_value)->get_constructor(Z_OBJ_P(return_value));
5164+
EG(fake_scope) = old_scope;
5165+
5166+
/* Run the constructor if there is one */
5167+
if (constructor) {
5168+
if (!(constructor->common.fn_flags & ZEND_ACC_PUBLIC)) {
5169+
zend_throw_exception_ex(reflection_exception_ptr, 0, "Access to non-public constructor of class %s", ZSTR_VAL(ce->name));
5170+
zval_ptr_dtor(return_value);
5171+
RETURN_NULL();
5172+
}
5173+
5174+
zend_call_known_function(
5175+
constructor, Z_OBJ_P(return_value), Z_OBJCE_P(return_value), NULL, 0, NULL, args);
5176+
5177+
if (EG(exception)) {
5178+
zend_object_store_ctor_failed(Z_OBJ_P(return_value));
5179+
}
5180+
} else if (argc) {
5181+
zend_throw_exception_ex(reflection_exception_ptr, 0, "Class %s does not have a constructor, so you cannot pass any constructor arguments", ZSTR_VAL(ce->name));
5182+
}
5183+
}
5184+
/* }}} */
5185+
51305186
void reflection_class_new_lazy(INTERNAL_FUNCTION_PARAMETERS,
51315187
int strategy, bool is_reset)
51325188
{

ext/reflection/php_reflection.stub.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,9 @@ public function newInstanceWithoutConstructor(): object {}
370370
/** @tentative-return-type */
371371
public function newInstanceArgs(array $args = []): ?object {}
372372

373+
/** @tentative-return-type */
374+
public function newInstanceFromData(array $data, array $args = []): ?object {}
375+
373376
public function newLazyGhost(callable $initializer, int $options = 0): object {}
374377

375378
public function newLazyProxy(callable $factory, int $options = 0): object {}

ext/reflection/php_reflection_arginfo.h

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
--TEST--
2+
ReflectionClass::newInstanceFromData
3+
--FILE--
4+
<?php
5+
6+
class A
7+
{
8+
public int $a;
9+
public string $b;
10+
11+
public function __construct($c, $d)
12+
{
13+
echo "In constructor of class A\n";
14+
}
15+
}
16+
17+
class B
18+
{
19+
public int $a;
20+
public string $b;
21+
}
22+
23+
class C
24+
{
25+
}
26+
27+
#[\AllowDynamicProperties]
28+
class D
29+
{
30+
}
31+
32+
33+
$rcA = new ReflectionClass('A');
34+
$rcB = new ReflectionClass('B');
35+
$rcC = new ReflectionClass('C');
36+
$rcD = new ReflectionClass('D');
37+
38+
try
39+
{
40+
$rcA->newInstanceFromData(['a' => 'bad', 'b' => 123], ['foo', 1337]);
41+
}
42+
catch(Throwable $e)
43+
{
44+
echo "Exception: " . $e->getMessage() . "\n";
45+
}
46+
47+
var_dump($rcA->newInstanceFromData(['a' => 123, 'b' => 'good'], ['foo', 1337]));
48+
49+
var_dump($rcB->newInstanceFromData(['a' => 123, 'b' => 'good']));
50+
51+
var_dump($rcC->newInstanceFromData(['a' => 123, 'b' => 'good']));
52+
53+
var_dump($rcC->newInstanceFromData([]));
54+
55+
var_dump($rcD->newInstanceFromData(['a' => 123, 'b' => 'good']));
56+
57+
?>
58+
--EXPECTF--
59+
Exception: Cannot assign string to property A::$a of type int
60+
In constructor of class A
61+
object(A)#5 (2) {
62+
["a"]=>
63+
int(123)
64+
["b"]=>
65+
string(4) "good"
66+
}
67+
object(B)#5 (2) {
68+
["a"]=>
69+
int(123)
70+
["b"]=>
71+
string(4) "good"
72+
}
73+
74+
Deprecated: Creation of dynamic property C::$a is deprecated in /Users/matt/Projects/php-src/ext/reflection/tests/ReflectionClass_newInstanceFromData_001.php on line 48
75+
76+
Deprecated: Creation of dynamic property C::$b is deprecated in /Users/matt/Projects/php-src/ext/reflection/tests/ReflectionClass_newInstanceFromData_001.php on line 48
77+
object(C)#5 (2) {
78+
["a"]=>
79+
int(123)
80+
["b"]=>
81+
string(4) "good"
82+
}
83+
object(C)#5 (0) {
84+
}
85+
object(D)#5 (2) {
86+
["a"]=>
87+
int(123)
88+
["b"]=>
89+
string(4) "good"
90+
}

ext/reflection/tests/ReflectionClass_toString_001.phpt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Class [ <internal:Reflection> class ReflectionClass implements Stringable, Refle
3030
Property [ public string $name ]
3131
}
3232

33-
- Methods [64] {
33+
- Methods [65] {
3434
Method [ <internal:Reflection> private method __clone ] {
3535

3636
- Parameters [0] {
@@ -332,6 +332,15 @@ Class [ <internal:Reflection> class ReflectionClass implements Stringable, Refle
332332
- Tentative return [ ?object ]
333333
}
334334

335+
Method [ <internal:Reflection> public method newInstanceFromData ] {
336+
337+
- Parameters [2] {
338+
Parameter #0 [ <required> array $data ]
339+
Parameter #1 [ <optional> array $args = [] ]
340+
}
341+
- Tentative return [ ?object ]
342+
}
343+
335344
Method [ <internal:Reflection> public method newLazyGhost ] {
336345

337346
- Parameters [2] {

0 commit comments

Comments
 (0)