This function always returned SUCCESS unconditionally; removing the return type
revealed some impossible code for handling FAILURE that could also be removed.
The motivation for this is that types should be considered immutable.
The only times this is not valid is during compilation, optimizations (opcache), or destruction.
Therefore the "normal" type foreach macros are marked to take const arguments and we add mutable version that say so in the name.
Thus add various const qualifiers to communicate intent.
- When appending a single character to the string, use `smart_str_appendc()`
- When appending a C-string without printf use, use `smart_str_appends()`
- When appending just a `zend_string`, use `smart_str_append()`
While internally enums are mostly the same as classes, their output in
`ReflectionClass::__toString()` should show the enum as the developer wrote it,
rather than as the engine stored it. Accordingly
- Say that the enum is an enum, not a final class
- Include the backing type, if any, in the declaration line
- List enum cases separately from constants, and show the underlying values, if
any
GH-15766
Add "final" and "abstract" to the result of `_property_string()` when
outputting the string representation of a `ReflectionClass` or
`ReflectionProperty` instance
Closes GH-17827
When a property default is based on a global constant, show the type of the
default. Previously, `format_default_value()` assumed that non-scalar and
non-array defaults were always going to be `IS_CONSTANT_AST` pointers, and when
the AST expression had been evaluated and produced an object, depending on when
the `ReflectionClass` or `ReflectionProperty` instance had been created, the
default was shown as one of `callable` or `__CLASS__`.
Instead, if the default value is an object (`IS_OBJECT`), show the type of that
object.
Add test cases for both of the `callable` and `__CLASS__` cases to confirm that
they now properly show the type of the constant.
Closes GH-15902.
Closes GH-17781.
Apply the following changes to ReflectionProperty::getValue(), ::getRawValue(), ::isInitialized(), ::setValue(), ::setRawValue():
- Pass a cache slot to the property handler
- Inline the simple case of fetching a declared property
- Use the new parameter parsing API
This results in run time decrease of 12% to 59% in [micro benchmarks](https://gist.github.com/arnaud-lb/2de08142dcd0c7b49caed21398f44656), when executed with `hyperfine -L version base,opt "/tmp/{version}/sapi/cli/php -d opcache.enable_cli=1 $script"`:
```
get-raw-value.php: -58%
get-value-dyn.php: -50%
get-value-hook.php: -47%
get-value.php: -59%
is-initialized.php: -59%
set-value.php: -11%
```
And a 1.8% decrease in this Doctrine benchmark: 505825290d...reflection-property-get-value-opt
When the `zend_class_entry` has a `zend_function` entry for `clone`, the logic
is the same regardless of if the `reflection_object` entry has an object or
not; the determination is based solely on the flags of the `zend_function`.
`new Reflectionproperty($scope, $propName)` keeps a reference to the
zend_property_info of $propName declared in $scope. In getRawValue() and
related methods, we use this reference to check whether the property is hooked.
Calling `new ReflectionProperty($scope, $propName)->getRawValue($object)` is
equivalent to the expression $object->$propName from scope $scope (except that
it bypasses hooks), and thus may access an overridden property (unless the
original is private). This property may have hooks and different flags.
Here I fetch the effective property info before checking for hooks and
property flags.
Fixes GH-17713
Closes GH-17714
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
* 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()`
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]