Normally, accesses to properties marked as lazy trigger the object's
initialization, or forward to a real instance if the object is an initialized
proxy.
The purpose of ReflectionProperty::setRawValueWithoutLazyInitialization() and
ReflectionProperty::skipLazyInitialization() is to bypass auto-initialization,
so that some properties can be initialized without triggering initialization.
However, when the object is an initialized proxy, these methods would
unexpectedly update the proxy.
Here I make sure that these methods have an effect on the real instance, when
the object is an initialized proxy.
Fixes GH-16344
Since the return value is never used, the only difference between using this
method and using `object_init_ex()` directly is the flipped order of
parameters, and the added level of indirection - remove that level of
indirection by replacing its uses.
Additionally fixes wrong behaviour in ReflectionParameter when you first
have a construction that uses an object and the subsequent doesn't.
Closes GH-16672.
Allow determining the name of the file that defined a constant, when the
constant was defined in userland code via const or define(). For constants
defined by PHP core or extensions, false is returned, matching the existing
getFileName() methods on other reflection classes.
Fixes GH-15723
Closes GH-15847
During the Doctrine Core Team Meetup 2024 the Doctrine team investigated the
performance overhead of using `setRawValueWithoutLazyInitialization()` instead
of `setValue()` and came to the surprising conclusion that
`setRawValueWithoutLazyInitialization()` outperformed `setValue()`, despite
doing more work.
These two scripts are used as the benchmark:
<?php
class Foo
{
public $id;
public $foo1;
public $foo2;
public $foo3;
public $foo4;
}
$reflection = new ReflectionClass(Foo::class);
$properties = $reflection->getProperties();
for ($i = 0; $i < 1000000; $i++) {
$foo = new Foo();
foreach ($properties as $property) {
$property->setValue($foo, 1);
}
}
and
<?php
class Foo
{
public $id;
public $foo1;
public $foo2;
public $foo3;
public $foo4;
}
$reflection = new ReflectionClass(Foo::class);
$properties = $reflection->getProperties();
for ($i = 0; $i < 1000000; $i++) {
$foo = new Foo();
foreach ($properties as $property) {
$property->setRawValueWithoutLazyInitialization($foo, 1);
}
}
Benchmarking these with a current git master shows that `setValue()` is 50%
slower:
$ hyperfine -L script setValue,setRawValueWithoutLazyInitialization '/tmp/php-before /tmp/test/{script}.php'
Benchmark 1: /tmp/php-before /tmp/test/setValue.php
Time (mean ± σ): 216.0 ms ± 5.8 ms [User: 212.0 ms, System: 3.7 ms]
Range (min … max): 208.2 ms … 225.3 ms 13 runs
Benchmark 2: /tmp/php-before /tmp/test/setRawValueWithoutLazyInitialization.php
Time (mean ± σ): 145.6 ms ± 3.6 ms [User: 141.6 ms, System: 3.8 ms]
Range (min … max): 140.4 ms … 152.8 ms 20 runs
Summary
/tmp/php-before /tmp/test/setRawValueWithoutLazyInitialization.php ran
1.48 ± 0.05 times faster than /tmp/php-before /tmp/test/setValue.php
Looking into the “why” revealed that the `setValue()` script spent quite some
time in `zend_parse_parameters()`.
A 50% overhead can be significant, given that `setValue()` is commonly called
several thousand times in a single request when using Doctrine.
This commit changes the non-static property case of `setValue()` to make use of
the fast parameter parsing API and adjusts `getValue()` for consistency.
The resulting comparison shows that both `setValue()` and
`setRawValueWithoutLazyInitialization()` are now (almost) equal:
$ hyperfine -L script setValue,setRawValueWithoutLazyInitialization 'sapi/cli/php /tmp/test/{script}.php'
Benchmark 1: sapi/cli/php /tmp/test/setValue.php
Time (mean ± σ): 143.0 ms ± 6.4 ms [User: 139.4 ms, System: 3.4 ms]
Range (min … max): 134.8 ms … 157.7 ms 18 runs
Benchmark 2: sapi/cli/php /tmp/test/setRawValueWithoutLazyInitialization.php
Time (mean ± σ): 147.0 ms ± 5.5 ms [User: 143.0 ms, System: 3.6 ms]
Range (min … max): 139.9 ms … 159.8 ms 19 runs
Summary
sapi/cli/php /tmp/test/setValue.php ran
1.03 ± 0.06 times faster than sapi/cli/php /tmp/test/setRawValueWithoutLazyInitialization.php
A tentative return type is used to allow userland code that overrides a method
to not include a typehint without a fatal error; this is inapplicable to final
methods (including all methods of final classes), which cannot be overridden.
Remove the tentative return declarations, and update the build script to
complain about future additions.
* reflection: Fix the return value of ReflectionFunction::{getNamespaceName,inNamespace}() for closures
Fixes GH-16122
* reflection: Clean up implementation of `ReflectionFunctionAbstract::inNamespace()`
* reflection: Clean up implementation of `ReflectionFunctionAbstract::getNamespaceName()`
Instead of allocating, using, and then releasing a zend_string for every
property name unconditionally, only do so when the minimum supported version of
PHP does not have that string in its known strings (ZEND_KNOWN_STRINGS). If the
string is already known, just use the known version directly. This is already
done for some non-generated class registrations, e.g. in
`zend_enum_register_props()`.
For the `Exception`, `ReflectionClass`, and `ReflectionAttribute` classes, the
`__clone()` method is declared to be private, and the implementation has a
comment that it should never be executed. However, the implementation can be
executed by using a `ReflectionMethod`. Fix the comments to instead explain why
the implementation is needed.
[skip ci]
Dynamic properties are generally referred to as "dynamic" properties, while
non-dynamic properties are not commonly referred to as "default" properties.
Thus, the existing method `ReflectionProperty::isDefault()` has a non obvious
name; while an alias could be added for `isNotDynamic()`, a new `isDynamic()`
method seems cleaner. The new method returns the opposite of `isDefault()`;
dynamic properties are not present on the class by default, and properties
present by default are not added dynamically.
Closes GH-15754
To match other capitalized strings like `ZEND_STR_UNKNOWN_CAPITALIZED` and
`ZEND_STR_ARRAY_CAPITALIZED`. Since this known string was only added in PHP
8.4, no backwards compatibility alias is needed.
In zend_std_has_property with ZEND_PROPERTY_EXISTS, we'd just return true when
no get hook was present. However, this function is supposed to return false for
uninitialized properties. PROPERTY_EXISTS is somewhat of a misnomer. Virtual
properties continue to always return true, given there's no backing value to
check.
Fixes GH-15694
Closes GH-15822
When a class (or enum) has no methods, rather than using an array that only
contains `ZEND_FE_END`, use `NULL` for the functions. The implementation of
class registration for internal classes, `do_register_internal_class()` in
zend_API.c, already skips classes where the functions are `NULL`. By removing
these unneeded arrays, we can reduce the size of the header files, while also
removing an unneeded call to zend_register_functions() for each internal class
with no extra methods.
For dynamic properties, instead of crashing with a segmentation fault, just say
that there are no hooks. Also includes a test to prevent regression.
Fixes GH-15718
Closes GH-15721
This is useful to reduce the memory usage of objects that don't actually
use the backing storage. Examples are XMLReader and DOM. When the
properties were added to the stubs, these objects became much much
bigger, which is a waste of memory.
Closes GH-11644.
Work towards GH-13988.
Currently, internal classes are registered with the following code:
INIT_CLASS_ENTRY(ce, "InternalClass", class_InternalClass_methods);
class_entry = zend_register_internal_class_ex(&ce, NULL);
class_entry->ce_flags |= ...;
This has worked well so far, except if InternalClass is readonly. It is because some inheritance checks are run by zend_register_internal_class_ex before ZEND_ACC_READONLY_CLASS is added to ce_flags.
The issue is fixed by adding a zend_register_internal_class_with_flags() zend API function that stubs can use from now on. This function makes sure to add the flags before running any checks. Since the new API is not available in lower PHP versions, gen_stub.php has to keep support for the existing API for PHP 8.3 and below.
YIELD and YIELD_FROM increment opline before returning, but in most places
we need the opline to point to the YIELD and YIELD_FROM.
Here I change YIELD / YIELD_FROM to not increment opline. This simplifies the
code and fixes GH-15275 in a better way.
Closes GH-15328
* Include from build dir first
This fixes out of tree builds by ensuring that configure artifacts are included
from the build dir.
Before, out of tree builds would preferably include files from the src dir, as
the include path was defined as follows (ignoring includes from ext/ and sapi/) :
-I$(top_builddir)/main
-I$(top_srcdir)
-I$(top_builddir)/TSRM
-I$(top_builddir)/Zend
-I$(top_srcdir)/main
-I$(top_srcdir)/Zend
-I$(top_srcdir)/TSRM
-I$(top_builddir)/
As a result, an out of tree build would include configure artifacts such as
`main/php_config.h` from the src dir.
After this change, the include path is defined as follows:
-I$(top_builddir)/main
-I$(top_builddir)
-I$(top_srcdir)/main
-I$(top_srcdir)
-I$(top_builddir)/TSRM
-I$(top_builddir)/Zend
-I$(top_srcdir)/Zend
-I$(top_srcdir)/TSRM
* Fix extension include path for out of tree builds
* Include config.h with the brackets form
`#include "config.h"` searches in the directory containing the including-file
before any other include path. This can include the wrong config.h when building
out of tree and a config.h exists in the source tree.
Using `#include <config.h>` uses exclusively the include path, and gives
priority to the build dir.