mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
[RFC] Add #[\DelayedTargetValidation] attribute (#18817)
https://wiki.php.net/rfc/delayedtargetvalidation_attribute
This commit is contained in:
14
UPGRADING
14
UPGRADING
@@ -53,6 +53,8 @@ PHP 8.5 UPGRADE NOTES
|
||||
. Applying #[\Attribute] to an abstract class, enum, interface, or trait triggers
|
||||
an error during compilation. Previously, the attribute could be added, but when
|
||||
ReflectionAttribute::newInstance() was called an error would be thrown.
|
||||
The error can be delayed from compilation to runtime using the new
|
||||
#[\DelayedTargetValidation] attribute.
|
||||
|
||||
- DOM:
|
||||
. Cloning a DOMNamedNodeMap, DOMNodeList, Dom\NamedNodeMap, Dom\NodeList,
|
||||
@@ -184,6 +186,11 @@ PHP 8.5 UPGRADE NOTES
|
||||
RFC: https://wiki.php.net/rfc/final_promotion
|
||||
. #[\Override] can now be applied to properties.
|
||||
RFC: https://wiki.php.net/rfc/override_properties
|
||||
. The #[\DelayedTargetValidation] attribute can be used to suppress
|
||||
compile-time errors from core (or extension) attributes that are used on
|
||||
invalid targets. These errors are instead reported at runtime if and when
|
||||
ReflectionAttribute::newInstance() is called.
|
||||
RFC: https://wiki.php.net/rfc/delayedtargetvalidation_attribute
|
||||
|
||||
- Curl:
|
||||
. Added support for share handles that are persisted across multiple PHP
|
||||
@@ -528,6 +535,11 @@ PHP 8.5 UPGRADE NOTES
|
||||
hooks are final, and whether the property is virtual. This also affects
|
||||
the output of ReflectionClass::__toString() when a class contains hooked
|
||||
properties.
|
||||
. ReflectionAttribute::newInstance() can now throw errors for internal
|
||||
attributes if the attribute was applied on an invalid target and the
|
||||
error was delayed from compile-time to runtime via the
|
||||
#[\DelayedTargetValidation] attribute.
|
||||
RFC: https://wiki.php.net/rfc/delayedtargetvalidation_attribute
|
||||
|
||||
- Session:
|
||||
. session_start is stricter in regard to the option argument.
|
||||
@@ -648,6 +660,8 @@ PHP 8.5 UPGRADE NOTES
|
||||
- Core:
|
||||
. NoDiscard attribute was added.
|
||||
RFC: https://wiki.php.net/rfc/marking_return_value_as_important
|
||||
. DelayedTargetValidation attribute was added.
|
||||
RFC: https://wiki.php.net/rfc/delayedtargetvalidation_attribute
|
||||
|
||||
- Curl:
|
||||
. CurlSharePersistentHandle representing a share handle that is persisted
|
||||
|
||||
@@ -28,6 +28,12 @@ PHP 8.5 INTERNALS UPGRADE NOTES
|
||||
extra layer of indirection can be removed. In other cases a zval can
|
||||
be heap-allocated and stored in the pointer as a minimal change to keep
|
||||
compatibility.
|
||||
. The validator callbacks for internal attribute now return `zend_string *`
|
||||
rather than `void`; instead of emitting an error when an attribute is
|
||||
applied incorrectly, the error message should be returned as a zend_string
|
||||
pointer. If the error will be delayed until runtime, it is stored in the
|
||||
new `validation_error` field of the `zend_attribute` struct.
|
||||
RFC: https://wiki.php.net/rfc/delayedtargetvalidation_attribute
|
||||
|
||||
- Hash
|
||||
. Hash functions now use proper hash_spec_result enum for return values
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] has errors at runtime
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[NoDiscard]
|
||||
class Demo {
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
public const FOO = 'BAR';
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
public string $v1;
|
||||
|
||||
public string $v2 {
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
get => $this->v2;
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
set => $value;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
public function __construct(
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
public string $v3
|
||||
) {
|
||||
$this->v1 = $v3;
|
||||
echo __METHOD__ . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
function demoFn() {
|
||||
echo __FUNCTION__ . "\n";
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
const EXAMPLE = true;
|
||||
|
||||
$cases = [
|
||||
new ReflectionClass('Demo'),
|
||||
new ReflectionClassConstant('Demo', 'FOO'),
|
||||
new ReflectionProperty('Demo', 'v1'),
|
||||
new ReflectionProperty('Demo', 'v2')->getHook(PropertyHookType::Get),
|
||||
new ReflectionProperty('Demo', 'v2')->getHook(PropertyHookType::Set),
|
||||
new ReflectionMethod('Demo', '__construct'),
|
||||
new ReflectionParameter([ 'Demo', '__construct' ], 'v3'),
|
||||
new ReflectionProperty('Demo', 'v3'),
|
||||
new ReflectionFunction('demoFn'),
|
||||
new ReflectionConstant('EXAMPLE'),
|
||||
];
|
||||
foreach ($cases as $r) {
|
||||
echo str_repeat("*", 20) . "\n";
|
||||
echo $r . "\n";
|
||||
$attributes = $r->getAttributes();
|
||||
var_dump($attributes);
|
||||
try {
|
||||
$attributes[1]->newInstance();
|
||||
} catch (Error $e) {
|
||||
echo get_class($e) . ": " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
********************
|
||||
Class [ <user> <iterateable> class Demo ] {
|
||||
@@ %s %d-%d
|
||||
|
||||
- Constants [1] {
|
||||
Constant [ public string FOO ] { BAR }
|
||||
}
|
||||
|
||||
- Static properties [0] {
|
||||
}
|
||||
|
||||
- Static methods [0] {
|
||||
}
|
||||
|
||||
- Properties [3] {
|
||||
Property [ public string $v1 ]
|
||||
Property [ public string $v2 { get; set; } ]
|
||||
Property [ public string $v3 ]
|
||||
}
|
||||
|
||||
- Methods [1] {
|
||||
Method [ <user, ctor> public method __construct ] {
|
||||
@@ %s %d - %d
|
||||
|
||||
- Parameters [1] {
|
||||
Parameter #0 [ <required> string $v3 ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "NoDiscard"
|
||||
}
|
||||
}
|
||||
Error: Attribute "NoDiscard" cannot target class (allowed targets: function, method)
|
||||
********************
|
||||
Constant [ public string FOO ] { BAR }
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "Attribute"
|
||||
}
|
||||
}
|
||||
Error: Attribute "Attribute" cannot target class constant (allowed targets: class)
|
||||
********************
|
||||
Property [ public string $v1 ]
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "Attribute"
|
||||
}
|
||||
}
|
||||
Error: Attribute "Attribute" cannot target property (allowed targets: class)
|
||||
********************
|
||||
Method [ <user> public method $v2::get ] {
|
||||
@@ %s %d - %d
|
||||
|
||||
- Parameters [0] {
|
||||
}
|
||||
- Return [ string ]
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "Attribute"
|
||||
}
|
||||
}
|
||||
Error: Attribute "Attribute" cannot target method (allowed targets: class)
|
||||
********************
|
||||
Method [ <user> public method $v2::set ] {
|
||||
@@ %s %d - %d
|
||||
|
||||
- Parameters [1] {
|
||||
Parameter #0 [ <required> string $value ]
|
||||
}
|
||||
- Return [ void ]
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "Attribute"
|
||||
}
|
||||
}
|
||||
Error: Attribute "Attribute" cannot target method (allowed targets: class)
|
||||
********************
|
||||
Method [ <user, ctor> public method __construct ] {
|
||||
@@ %s %d - %d
|
||||
|
||||
- Parameters [1] {
|
||||
Parameter #0 [ <required> string $v3 ]
|
||||
}
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "Attribute"
|
||||
}
|
||||
}
|
||||
Error: Attribute "Attribute" cannot target method (allowed targets: class)
|
||||
********************
|
||||
Parameter #0 [ <required> string $v3 ]
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "Attribute"
|
||||
}
|
||||
}
|
||||
Error: Attribute "Attribute" cannot target parameter (allowed targets: class)
|
||||
********************
|
||||
Property [ public string $v3 ]
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "Attribute"
|
||||
}
|
||||
}
|
||||
Error: Attribute "Attribute" cannot target property (allowed targets: class)
|
||||
********************
|
||||
Function [ <user> function demoFn ] {
|
||||
@@ %s %d - %d
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "Attribute"
|
||||
}
|
||||
}
|
||||
Error: Attribute "Attribute" cannot target function (allowed targets: class)
|
||||
********************
|
||||
Constant [ bool EXAMPLE ] { 1 }
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "Attribute"
|
||||
}
|
||||
}
|
||||
Error: Attribute "Attribute" cannot target constant (allowed targets: class)
|
||||
@@ -0,0 +1,55 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] prevents target errors at compile time
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[NoDiscard]
|
||||
class Demo {
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
public const FOO = 'BAR';
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
public string $v1;
|
||||
|
||||
public string $v2 {
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
get => $this->v2;
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
set => $value;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
public function __construct(
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
public string $v3
|
||||
) {
|
||||
$this->v1 = $v3;
|
||||
echo __METHOD__ . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
function demoFn() {
|
||||
echo __FUNCTION__ . "\n";
|
||||
}
|
||||
|
||||
$o = new Demo( "foo" );
|
||||
demoFn();
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
const EXAMPLE = true;
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Demo::__construct
|
||||
demoFn
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties]
|
||||
trait DemoTrait {}
|
||||
@@ -0,0 +1,58 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with preloaded attribute with errors
|
||||
--INI--
|
||||
opcache.enable=1
|
||||
opcache.enable_cli=1
|
||||
opcache.preload={PWD}/opcache_validator_errors.inc
|
||||
--EXTENSIONS--
|
||||
opcache
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
|
||||
?>
|
||||
--FILE--
|
||||
<?php
|
||||
$r = new ReflectionClass('DemoTrait');
|
||||
echo $r . "\n";
|
||||
$attributes = $r->getAttributes();
|
||||
var_dump($attributes);
|
||||
try {
|
||||
$attributes[1]->newInstance();
|
||||
} catch (Error $e) {
|
||||
echo get_class($e) . ": " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Trait [ <user> trait DemoTrait ] {
|
||||
@@ %s %d-%d
|
||||
|
||||
- Constants [0] {
|
||||
}
|
||||
|
||||
- Static properties [0] {
|
||||
}
|
||||
|
||||
- Static methods [0] {
|
||||
}
|
||||
|
||||
- Properties [0] {
|
||||
}
|
||||
|
||||
- Methods [0] {
|
||||
}
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(22) "AllowDynamicProperties"
|
||||
}
|
||||
}
|
||||
Error: Cannot apply #[\AllowDynamicProperties] to trait DemoTrait
|
||||
@@ -0,0 +1,13 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] does not prevent repetition errors
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[NoDiscard]
|
||||
#[NoDiscard]
|
||||
class Demo {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Attribute "NoDiscard" must not be repeated in %s on line %d
|
||||
@@ -0,0 +1,180 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with #[\AllowDynamicProperties]: validator errors delayed
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties]
|
||||
trait DemoTrait {}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties]
|
||||
interface DemoInterface {}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties]
|
||||
readonly class DemoReadonly {}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties]
|
||||
enum DemoEnum {}
|
||||
|
||||
$cases = [
|
||||
new ReflectionClass('DemoTrait'),
|
||||
new ReflectionClass('DemoInterface'),
|
||||
new ReflectionClass('DemoReadonly'),
|
||||
new ReflectionClass('DemoEnum'),
|
||||
];
|
||||
foreach ($cases as $r) {
|
||||
echo str_repeat("*", 20) . "\n";
|
||||
echo $r . "\n";
|
||||
$attributes = $r->getAttributes();
|
||||
var_dump($attributes);
|
||||
try {
|
||||
$attributes[1]->newInstance();
|
||||
} catch (Error $e) {
|
||||
echo get_class($e) . ": " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
********************
|
||||
Trait [ <user> trait DemoTrait ] {
|
||||
@@ %s %d-%d
|
||||
|
||||
- Constants [0] {
|
||||
}
|
||||
|
||||
- Static properties [0] {
|
||||
}
|
||||
|
||||
- Static methods [0] {
|
||||
}
|
||||
|
||||
- Properties [0] {
|
||||
}
|
||||
|
||||
- Methods [0] {
|
||||
}
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(22) "AllowDynamicProperties"
|
||||
}
|
||||
}
|
||||
Error: Cannot apply #[\AllowDynamicProperties] to trait DemoTrait
|
||||
********************
|
||||
Interface [ <user> interface DemoInterface ] {
|
||||
@@ %s %d-%d
|
||||
|
||||
- Constants [0] {
|
||||
}
|
||||
|
||||
- Static properties [0] {
|
||||
}
|
||||
|
||||
- Static methods [0] {
|
||||
}
|
||||
|
||||
- Properties [0] {
|
||||
}
|
||||
|
||||
- Methods [0] {
|
||||
}
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(22) "AllowDynamicProperties"
|
||||
}
|
||||
}
|
||||
Error: Cannot apply #[\AllowDynamicProperties] to interface DemoInterface
|
||||
********************
|
||||
Class [ <user> readonly class DemoReadonly ] {
|
||||
@@ %s %d-%d
|
||||
|
||||
- Constants [0] {
|
||||
}
|
||||
|
||||
- Static properties [0] {
|
||||
}
|
||||
|
||||
- Static methods [0] {
|
||||
}
|
||||
|
||||
- Properties [0] {
|
||||
}
|
||||
|
||||
- Methods [0] {
|
||||
}
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(22) "AllowDynamicProperties"
|
||||
}
|
||||
}
|
||||
Error: Cannot apply #[\AllowDynamicProperties] to readonly class DemoReadonly
|
||||
********************
|
||||
Enum [ <user> enum DemoEnum implements UnitEnum ] {
|
||||
@@ %s %d-%d
|
||||
|
||||
- Constants [0] {
|
||||
}
|
||||
|
||||
- Static properties [0] {
|
||||
}
|
||||
|
||||
- Static methods [1] {
|
||||
Method [ <internal, prototype UnitEnum> static public method cases ] {
|
||||
|
||||
- Parameters [0] {
|
||||
}
|
||||
- Return [ array ]
|
||||
}
|
||||
}
|
||||
|
||||
- Properties [1] {
|
||||
Property [ public protected(set) readonly string $name ]
|
||||
}
|
||||
|
||||
- Methods [0] {
|
||||
}
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(22) "AllowDynamicProperties"
|
||||
}
|
||||
}
|
||||
Error: Cannot apply #[\AllowDynamicProperties] to enum DemoEnum
|
||||
@@ -0,0 +1,180 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with #[\Attribute]: validator errors delayed
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
trait DemoTrait {}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
interface DemoInterface {}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
abstract class DemoAbstract {}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute]
|
||||
enum DemoEnum {}
|
||||
|
||||
$cases = [
|
||||
new ReflectionClass('DemoTrait'),
|
||||
new ReflectionClass('DemoInterface'),
|
||||
new ReflectionClass('DemoAbstract'),
|
||||
new ReflectionClass('DemoEnum'),
|
||||
];
|
||||
foreach ($cases as $r) {
|
||||
echo str_repeat("*", 20) . "\n";
|
||||
echo $r . "\n";
|
||||
$attributes = $r->getAttributes();
|
||||
var_dump($attributes);
|
||||
try {
|
||||
$attributes[1]->newInstance();
|
||||
} catch (Error $e) {
|
||||
echo get_class($e) . ": " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
********************
|
||||
Trait [ <user> trait DemoTrait ] {
|
||||
@@ %s %d-%d
|
||||
|
||||
- Constants [0] {
|
||||
}
|
||||
|
||||
- Static properties [0] {
|
||||
}
|
||||
|
||||
- Static methods [0] {
|
||||
}
|
||||
|
||||
- Properties [0] {
|
||||
}
|
||||
|
||||
- Methods [0] {
|
||||
}
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "Attribute"
|
||||
}
|
||||
}
|
||||
Error: Cannot apply #[\Attribute] to trait DemoTrait
|
||||
********************
|
||||
Interface [ <user> interface DemoInterface ] {
|
||||
@@ %s %d-%d
|
||||
|
||||
- Constants [0] {
|
||||
}
|
||||
|
||||
- Static properties [0] {
|
||||
}
|
||||
|
||||
- Static methods [0] {
|
||||
}
|
||||
|
||||
- Properties [0] {
|
||||
}
|
||||
|
||||
- Methods [0] {
|
||||
}
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "Attribute"
|
||||
}
|
||||
}
|
||||
Error: Cannot apply #[\Attribute] to interface DemoInterface
|
||||
********************
|
||||
Class [ <user> abstract class DemoAbstract ] {
|
||||
@@ %s %d-%d
|
||||
|
||||
- Constants [0] {
|
||||
}
|
||||
|
||||
- Static properties [0] {
|
||||
}
|
||||
|
||||
- Static methods [0] {
|
||||
}
|
||||
|
||||
- Properties [0] {
|
||||
}
|
||||
|
||||
- Methods [0] {
|
||||
}
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "Attribute"
|
||||
}
|
||||
}
|
||||
Error: Cannot apply #[\Attribute] to abstract class DemoAbstract
|
||||
********************
|
||||
Enum [ <user> enum DemoEnum implements UnitEnum ] {
|
||||
@@ %s %d-%d
|
||||
|
||||
- Constants [0] {
|
||||
}
|
||||
|
||||
- Static properties [0] {
|
||||
}
|
||||
|
||||
- Static methods [1] {
|
||||
Method [ <internal, prototype UnitEnum> static public method cases ] {
|
||||
|
||||
- Parameters [0] {
|
||||
}
|
||||
- Return [ array ]
|
||||
}
|
||||
}
|
||||
|
||||
- Properties [1] {
|
||||
Property [ public protected(set) readonly string $name ]
|
||||
}
|
||||
|
||||
- Methods [0] {
|
||||
}
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "Attribute"
|
||||
}
|
||||
}
|
||||
Error: Cannot apply #[\Attribute] to enum DemoEnum
|
||||
@@ -0,0 +1,79 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with #[\NoDiscard]: validator errors delayed
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class DemoClass {
|
||||
public string $hooked {
|
||||
#[DelayedTargetValidation]
|
||||
#[NoDiscard] // Does nothing here
|
||||
get => $this->hooked;
|
||||
#[DelayedTargetValidation]
|
||||
#[NoDiscard] // Does nothing here
|
||||
set => $value;
|
||||
}
|
||||
}
|
||||
|
||||
$cases = [
|
||||
new ReflectionProperty('DemoClass', 'hooked')->getHook(PropertyHookType::Get),
|
||||
new ReflectionProperty('DemoClass', 'hooked')->getHook(PropertyHookType::Set),
|
||||
];
|
||||
foreach ($cases as $r) {
|
||||
echo str_repeat("*", 20) . "\n";
|
||||
echo $r . "\n";
|
||||
$attributes = $r->getAttributes();
|
||||
var_dump($attributes);
|
||||
try {
|
||||
$attributes[1]->newInstance();
|
||||
} catch (Error $e) {
|
||||
echo get_class($e) . ": " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
********************
|
||||
Method [ <user> public method $hooked::get ] {
|
||||
@@ %s %d - %d
|
||||
|
||||
- Parameters [0] {
|
||||
}
|
||||
- Return [ string ]
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "NoDiscard"
|
||||
}
|
||||
}
|
||||
Error: #[\NoDiscard] is not supported for property hooks
|
||||
********************
|
||||
Method [ <user> public method $hooked::set ] {
|
||||
@@ %s %d - %d
|
||||
|
||||
- Parameters [1] {
|
||||
Parameter #0 [ <required> string $value ]
|
||||
}
|
||||
- Return [ void ]
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "NoDiscard"
|
||||
}
|
||||
}
|
||||
Error: #[\NoDiscard] is not supported for property hooks
|
||||
@@ -0,0 +1,62 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with a successful validator
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties]
|
||||
class DemoClass {}
|
||||
|
||||
$obj = new DemoClass();
|
||||
var_dump($obj);
|
||||
// No warnings
|
||||
$obj->dynamic = true;
|
||||
var_dump($obj);
|
||||
|
||||
$ref = new ReflectionClass('DemoClass');
|
||||
echo $ref . "\n";
|
||||
$attributes = $ref->getAttributes();
|
||||
var_dump($attributes);
|
||||
var_dump($attributes[1]->newInstance());
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
object(DemoClass)#%d (0) {
|
||||
}
|
||||
object(DemoClass)#%d (1) {
|
||||
["dynamic"]=>
|
||||
bool(true)
|
||||
}
|
||||
Class [ <user> class DemoClass ] {
|
||||
@@ %s %d-%d
|
||||
|
||||
- Constants [0] {
|
||||
}
|
||||
|
||||
- Static properties [0] {
|
||||
}
|
||||
|
||||
- Static methods [0] {
|
||||
}
|
||||
|
||||
- Properties [0] {
|
||||
}
|
||||
|
||||
- Methods [0] {
|
||||
}
|
||||
}
|
||||
|
||||
array(2) {
|
||||
[0]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(23) "DelayedTargetValidation"
|
||||
}
|
||||
[1]=>
|
||||
object(ReflectionAttribute)#%d (1) {
|
||||
["name"]=>
|
||||
string(22) "AllowDynamicProperties"
|
||||
}
|
||||
}
|
||||
object(AllowDynamicProperties)#%d (0) {
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with #[\AllowDynamicProperties]: invalid targets don't error
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties] // Does something here
|
||||
class DemoClass {
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties] // Does nothing here
|
||||
public $val;
|
||||
|
||||
public string $hooked {
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties] // Does nothing here
|
||||
get => $this->hooked;
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties] // Does nothing here
|
||||
set => $value;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties] // Does nothing here
|
||||
public const CLASS_CONST = 'FOO';
|
||||
|
||||
public function __construct(
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties] // Does nothing here
|
||||
$str
|
||||
) {
|
||||
echo "Got: $str\n";
|
||||
$this->val = $str;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties] // Does nothing here
|
||||
public function printVal() {
|
||||
echo 'Value is: ' . $this->val . "\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties] // Does nothing here
|
||||
function demoFn() {
|
||||
echo __FUNCTION__ . "\n";
|
||||
return 456;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[AllowDynamicProperties] // Does nothing here
|
||||
const GLOBAL_CONST = 'BAR';
|
||||
|
||||
$d = new DemoClass('example');
|
||||
$d->printVal();
|
||||
var_dump($d->val);
|
||||
$d->hooked = "foo";
|
||||
var_dump($d->hooked);
|
||||
var_dump(DemoClass::CLASS_CONST);
|
||||
demoFn();
|
||||
var_dump(GLOBAL_CONST);
|
||||
|
||||
$d->missingProp = 'foo';
|
||||
var_dump($d);
|
||||
?>
|
||||
--EXPECTF--
|
||||
Got: example
|
||||
Value is: example
|
||||
string(7) "example"
|
||||
string(3) "foo"
|
||||
string(3) "FOO"
|
||||
demoFn
|
||||
string(3) "BAR"
|
||||
object(DemoClass)#%d (3) {
|
||||
["val"]=>
|
||||
string(7) "example"
|
||||
["hooked"]=>
|
||||
string(3) "foo"
|
||||
["missingProp"]=>
|
||||
string(3) "foo"
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with #[\Attribute]: invalid targets don't error
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class NonAttribute {}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute] // Does something here
|
||||
class DemoClass {
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute] // Does nothing here
|
||||
public $val;
|
||||
|
||||
public string $hooked {
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute] // Does nothing here
|
||||
get => $this->hooked;
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute] // Does nothing here
|
||||
set => $value;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute] // Does nothing here
|
||||
public const CLASS_CONST = 'FOO';
|
||||
|
||||
public function __construct(
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute] // Does nothing here
|
||||
$str
|
||||
) {
|
||||
echo "Got: $str\n";
|
||||
$this->val = $str;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute] // Does nothing here
|
||||
public function printVal() {
|
||||
echo 'Value is: ' . $this->val . "\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute] // Does nothing here
|
||||
function demoFn() {
|
||||
echo __FUNCTION__ . "\n";
|
||||
return 456;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Attribute] // Does nothing here
|
||||
const GLOBAL_CONST = 'BAR';
|
||||
|
||||
$d = new DemoClass('example');
|
||||
$d->printVal();
|
||||
var_dump($d->val);
|
||||
$d->hooked = "foo";
|
||||
var_dump($d->hooked);
|
||||
var_dump(DemoClass::CLASS_CONST);
|
||||
demoFn();
|
||||
var_dump(GLOBAL_CONST);
|
||||
|
||||
#[DemoClass('BAZ')]
|
||||
#[NonAttribute]
|
||||
class WithDemoAttribs {}
|
||||
|
||||
$ref = new ReflectionClass(WithDemoAttribs::class);
|
||||
$attribs = $ref->getAttributes();
|
||||
var_dump($attribs[0]->newInstance());
|
||||
var_dump($attribs[1]->newInstance());
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Got: example
|
||||
Value is: example
|
||||
string(7) "example"
|
||||
string(3) "foo"
|
||||
string(3) "FOO"
|
||||
demoFn
|
||||
string(3) "BAR"
|
||||
Got: BAZ
|
||||
object(DemoClass)#5 (1) {
|
||||
["val"]=>
|
||||
string(3) "BAZ"
|
||||
["hooked"]=>
|
||||
uninitialized(string)
|
||||
}
|
||||
|
||||
Fatal error: Uncaught Error: Attempting to use non-attribute class "NonAttribute" as attribute in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(%d): ReflectionAttribute->newInstance()
|
||||
#1 {main}
|
||||
thrown in %s on line %d
|
||||
@@ -0,0 +1,82 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with #[\Deprecated]: valid targets are deprecated
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Deprecated] // Does nothing here
|
||||
class DemoClass {
|
||||
#[DelayedTargetValidation]
|
||||
#[Deprecated] // Does nothing here
|
||||
public $val;
|
||||
|
||||
public string $hooked {
|
||||
#[DelayedTargetValidation]
|
||||
#[Deprecated] // Does something here
|
||||
get => $this->hooked;
|
||||
#[DelayedTargetValidation]
|
||||
#[Deprecated] // Does something here
|
||||
set => $value;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Deprecated] // Does something here
|
||||
public const CLASS_CONST = 'FOO';
|
||||
|
||||
public function __construct(
|
||||
#[DelayedTargetValidation]
|
||||
#[Deprecated] // Does nothing here
|
||||
$str
|
||||
) {
|
||||
echo "Got: $str\n";
|
||||
$this->val = $str;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Deprecated] // Does something here
|
||||
public function printVal() {
|
||||
echo 'Value is: ' . $this->val . "\n";
|
||||
return 123;
|
||||
}
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Deprecated] // Does something here
|
||||
function demoFn() {
|
||||
echo __FUNCTION__ . "\n";
|
||||
return 456;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Deprecated] // Does something here
|
||||
const GLOBAL_CONST = 'BAR';
|
||||
|
||||
$d = new DemoClass('example');
|
||||
$d->printVal();
|
||||
var_dump($d->val);
|
||||
$d->hooked = "foo";
|
||||
var_dump($d->hooked);
|
||||
var_dump(DemoClass::CLASS_CONST);
|
||||
demoFn();
|
||||
var_dump(GLOBAL_CONST);
|
||||
?>
|
||||
--EXPECTF--
|
||||
Got: example
|
||||
|
||||
Deprecated: Method DemoClass::printVal() is deprecated in %s on line %d
|
||||
Value is: example
|
||||
string(7) "example"
|
||||
|
||||
Deprecated: Method DemoClass::$hooked::set() is deprecated in %s on line %d
|
||||
|
||||
Deprecated: Method DemoClass::$hooked::get() is deprecated in %s on line %d
|
||||
string(3) "foo"
|
||||
|
||||
Deprecated: Constant DemoClass::CLASS_CONST is deprecated in %s on line %d
|
||||
string(3) "FOO"
|
||||
|
||||
Deprecated: Function demoFn() is deprecated in %s on line %d
|
||||
demoFn
|
||||
|
||||
Deprecated: Constant GLOBAL_CONST is deprecated in %s on line %d
|
||||
string(3) "BAR"
|
||||
@@ -0,0 +1,80 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with #[\NoDiscard]: valid targets complain about discarding
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[NoDiscard] // Does nothing here
|
||||
class DemoClass {
|
||||
#[DelayedTargetValidation]
|
||||
#[NoDiscard] // Does nothing here
|
||||
public $val;
|
||||
|
||||
public string $hooked {
|
||||
#[DelayedTargetValidation]
|
||||
#[NoDiscard] // Does nothing here
|
||||
get => $this->hooked;
|
||||
#[DelayedTargetValidation]
|
||||
#[NoDiscard] // Does nothing here
|
||||
set => $value;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[NoDiscard] // Does nothing here
|
||||
public const CLASS_CONST = 'FOO';
|
||||
|
||||
public function __construct(
|
||||
#[DelayedTargetValidation]
|
||||
#[NoDiscard] // Does nothing here
|
||||
$str
|
||||
) {
|
||||
echo "Got: $str\n";
|
||||
$this->val = $str;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[NoDiscard] // Does something here
|
||||
public function printVal() {
|
||||
echo 'Value is: ' . $this->val . "\n";
|
||||
return 123;
|
||||
}
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[NoDiscard] // Does something here
|
||||
function demoFn() {
|
||||
echo __FUNCTION__ . "\n";
|
||||
return 456;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[NoDiscard] // Does nothing here
|
||||
const GLOBAL_CONST = 'BAR';
|
||||
|
||||
$d = new DemoClass('example');
|
||||
$d->printVal();
|
||||
$v = $d->printVal();
|
||||
var_dump($d->val);
|
||||
$d->hooked = "foo";
|
||||
var_dump($d->hooked);
|
||||
// NoDiscard does not support property hooks, this should not complain
|
||||
$d->hooked;
|
||||
var_dump(DemoClass::CLASS_CONST);
|
||||
demoFn();
|
||||
$v = demoFn();
|
||||
var_dump(GLOBAL_CONST);
|
||||
?>
|
||||
--EXPECTF--
|
||||
Got: example
|
||||
|
||||
Warning: The return value of method DemoClass::printVal() should either be used or intentionally ignored by casting it as (void) in %s on line %d
|
||||
Value is: example
|
||||
Value is: example
|
||||
string(7) "example"
|
||||
string(3) "foo"
|
||||
string(3) "FOO"
|
||||
|
||||
Warning: The return value of function demoFn() should either be used or intentionally ignored by casting it as (void) in %s on line %d
|
||||
demoFn
|
||||
demoFn
|
||||
string(3) "BAR"
|
||||
@@ -0,0 +1,18 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with #[\Override]: non-overrides still error (get hook)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class DemoClass {
|
||||
|
||||
public string $hooked {
|
||||
#[DelayedTargetValidation]
|
||||
#[Override] // Does something here
|
||||
get => $this->hooked;
|
||||
set => $value;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: DemoClass::$hooked::get() has #[\Override] attribute, but no matching parent method exists in %s on line %d
|
||||
@@ -0,0 +1,18 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with #[\Override]: non-overrides still error (method)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class DemoClass {
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Override] // Does something here
|
||||
public function printVal() {
|
||||
echo 'Value is: ' . $this->val . "\n";
|
||||
return 123;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: DemoClass::printVal() has #[\Override] attribute, but no matching parent method exists in %s on line %d
|
||||
@@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with #[\Override]: non-overrides still error (normal property)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class DemoClass {
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Override] // Does something here
|
||||
public string $prop;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: DemoClass::$prop has #[\Override] attribute, but no matching parent property exists in %s on line %d
|
||||
@@ -0,0 +1,18 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with #[\Override]: non-overrides still error (set hook)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class DemoClass {
|
||||
|
||||
public string $hooked {
|
||||
get => $this->hooked;
|
||||
#[DelayedTargetValidation]
|
||||
#[Override] // Does something here
|
||||
set => $value;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: DemoClass::$hooked::set() has #[\Override] attribute, but no matching parent method exists in %s on line %d
|
||||
@@ -0,0 +1,84 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with #[\Override]: invalid targets or actual overrides don't do anything
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Base {
|
||||
|
||||
public $val;
|
||||
|
||||
public string $hooked {
|
||||
get => $this->hooked;
|
||||
set => $value;
|
||||
}
|
||||
|
||||
public function printVal() {
|
||||
echo __METHOD__ . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Override] // Does nothing here
|
||||
class DemoClass extends Base {
|
||||
#[DelayedTargetValidation]
|
||||
#[Override] // Does something here
|
||||
public $val;
|
||||
|
||||
public string $hooked {
|
||||
#[DelayedTargetValidation]
|
||||
#[Override] // Does something here
|
||||
get => $this->hooked;
|
||||
#[DelayedTargetValidation]
|
||||
#[Override] // Does something here
|
||||
set => $value;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Override] // Does nothing here
|
||||
public const CLASS_CONST = 'FOO';
|
||||
|
||||
public function __construct(
|
||||
#[DelayedTargetValidation]
|
||||
#[Override] // Does nothing here
|
||||
$str
|
||||
) {
|
||||
echo "Got: $str\n";
|
||||
$this->val = $str;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Override] // Does something here
|
||||
public function printVal() {
|
||||
echo 'Value is: ' . $this->val . "\n";
|
||||
return 123;
|
||||
}
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Override] // Does nothing here
|
||||
function demoFn() {
|
||||
echo __FUNCTION__ . "\n";
|
||||
return 456;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[Override] // Does nothing here
|
||||
const GLOBAL_CONST = 'BAR';
|
||||
|
||||
$d = new DemoClass('example');
|
||||
$d->printVal();
|
||||
var_dump($d->val);
|
||||
$d->hooked = "foo";
|
||||
var_dump($d->hooked);
|
||||
var_dump(DemoClass::CLASS_CONST);
|
||||
demoFn();
|
||||
var_dump(GLOBAL_CONST);
|
||||
?>
|
||||
--EXPECT--
|
||||
Got: example
|
||||
Value is: example
|
||||
string(7) "example"
|
||||
string(3) "foo"
|
||||
string(3) "FOO"
|
||||
demoFn
|
||||
string(3) "BAR"
|
||||
@@ -0,0 +1,82 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with #[\ReturnTypeWillChange]: valid targets suppress return type warnings
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class WithoutAttrib implements Countable {
|
||||
public function count() {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[ReturnTypeWillChange] // Does nothing here
|
||||
class DemoClass implements Countable {
|
||||
#[DelayedTargetValidation]
|
||||
#[ReturnTypeWillChange] // Does nothing here
|
||||
public $val;
|
||||
|
||||
public string $hooked {
|
||||
#[DelayedTargetValidation]
|
||||
#[ReturnTypeWillChange] // Does nothing here
|
||||
get => $this->hooked;
|
||||
#[DelayedTargetValidation]
|
||||
#[ReturnTypeWillChange] // Does nothing here
|
||||
set => $value;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[ReturnTypeWillChange] // Does nothing here
|
||||
public const CLASS_CONST = 'FOO';
|
||||
|
||||
public function __construct(
|
||||
#[DelayedTargetValidation]
|
||||
#[ReturnTypeWillChange] // Does nothing here
|
||||
$str
|
||||
) {
|
||||
echo "Got: $str\n";
|
||||
$this->val = $str;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[ReturnTypeWillChange] // Does something here
|
||||
public function printVal() {
|
||||
echo 'Value is: ' . $this->val . "\n";
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[ReturnTypeWillChange] // Does something here
|
||||
public function count() {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[ReturnTypeWillChange] // Does nothing here
|
||||
function demoFn() {
|
||||
echo __FUNCTION__ . "\n";
|
||||
return 456;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[ReturnTypeWillChange] // Does nothing here
|
||||
const GLOBAL_CONST = 'BAR';
|
||||
|
||||
$d = new DemoClass('example');
|
||||
$d->printVal();
|
||||
var_dump($d->val);
|
||||
$d->hooked = "foo";
|
||||
var_dump($d->hooked);
|
||||
var_dump(DemoClass::CLASS_CONST);
|
||||
demoFn();
|
||||
var_dump(GLOBAL_CONST);
|
||||
?>
|
||||
--EXPECTF--
|
||||
Deprecated: Return type of WithoutAttrib::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in %s on line %d
|
||||
Got: example
|
||||
Value is: example
|
||||
string(7) "example"
|
||||
string(3) "foo"
|
||||
string(3) "FOO"
|
||||
demoFn
|
||||
string(3) "BAR"
|
||||
@@ -0,0 +1,82 @@
|
||||
--TEST--
|
||||
#[\DelayedTargetValidation] with #[\SensitiveParameter]: parameter still redacted
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[SensitiveParameter] // Does nothing here
|
||||
class DemoClass {
|
||||
#[DelayedTargetValidation]
|
||||
#[SensitiveParameter] // Does nothing here
|
||||
public $val;
|
||||
|
||||
public string $hooked {
|
||||
#[DelayedTargetValidation]
|
||||
#[SensitiveParameter] // Does nothing here
|
||||
get => $this->hooked;
|
||||
#[DelayedTargetValidation]
|
||||
#[SensitiveParameter] // Does nothing here
|
||||
set => $value;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[SensitiveParameter] // Does nothing here
|
||||
public const CLASS_CONST = 'FOO';
|
||||
|
||||
public function __construct(
|
||||
#[DelayedTargetValidation]
|
||||
#[SensitiveParameter] // Does something here
|
||||
$str
|
||||
) {
|
||||
echo "Got: $str\n";
|
||||
$this->val = $str;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[SensitiveParameter] // Does nothing here
|
||||
public function printVal(
|
||||
#[DelayedTargetValidation]
|
||||
#[SensitiveParameter]
|
||||
$sensitive // Does something here
|
||||
) {
|
||||
throw new Exception('Testing backtrace');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[SensitiveParameter] // Does nothing here
|
||||
function demoFn() {
|
||||
echo __FUNCTION__ . "\n";
|
||||
return 456;
|
||||
}
|
||||
|
||||
#[DelayedTargetValidation]
|
||||
#[SensitiveParameter] // Does nothing here
|
||||
const GLOBAL_CONST = 'BAR';
|
||||
|
||||
$d = new DemoClass('example');
|
||||
var_dump($d->val);
|
||||
$d->hooked = "foo";
|
||||
var_dump($d->hooked);
|
||||
var_dump(DemoClass::CLASS_CONST);
|
||||
demoFn();
|
||||
var_dump(GLOBAL_CONST);
|
||||
|
||||
$d->printVal('BAZ');
|
||||
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Got: example
|
||||
string(7) "example"
|
||||
string(3) "foo"
|
||||
string(3) "FOO"
|
||||
demoFn
|
||||
string(3) "BAR"
|
||||
|
||||
Fatal error: Uncaught Exception: Testing backtrace in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(%d): DemoClass->printVal(Object(SensitiveParameterValue))
|
||||
#1 {main}
|
||||
thrown in %s on line %d
|
||||
@@ -32,6 +32,7 @@ ZEND_API zend_class_entry *zend_ce_sensitive_parameter_value;
|
||||
ZEND_API zend_class_entry *zend_ce_override;
|
||||
ZEND_API zend_class_entry *zend_ce_deprecated;
|
||||
ZEND_API zend_class_entry *zend_ce_nodiscard;
|
||||
ZEND_API zend_class_entry *zend_ce_delayed_target_validation;
|
||||
|
||||
static zend_object_handlers attributes_object_handlers_sensitive_parameter_value;
|
||||
|
||||
@@ -69,33 +70,28 @@ uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_ent
|
||||
return ZEND_ATTRIBUTE_TARGET_ALL;
|
||||
}
|
||||
|
||||
static void validate_allow_dynamic_properties(
|
||||
static zend_string *validate_allow_dynamic_properties(
|
||||
zend_attribute *attr, uint32_t target, zend_class_entry *scope)
|
||||
{
|
||||
ZEND_ASSERT(scope != NULL);
|
||||
const char *msg = NULL;
|
||||
if (scope->ce_flags & ZEND_ACC_TRAIT) {
|
||||
zend_error_noreturn(E_ERROR, "Cannot apply #[\\AllowDynamicProperties] to trait %s",
|
||||
ZSTR_VAL(scope->name)
|
||||
);
|
||||
msg = "Cannot apply #[\\AllowDynamicProperties] to trait %s";
|
||||
} else if (scope->ce_flags & ZEND_ACC_INTERFACE) {
|
||||
msg = "Cannot apply #[\\AllowDynamicProperties] to interface %s";
|
||||
} else if (scope->ce_flags & ZEND_ACC_READONLY_CLASS) {
|
||||
msg = "Cannot apply #[\\AllowDynamicProperties] to readonly class %s";
|
||||
} else if (scope->ce_flags & ZEND_ACC_ENUM) {
|
||||
msg = "Cannot apply #[\\AllowDynamicProperties] to enum %s";
|
||||
}
|
||||
if (scope->ce_flags & ZEND_ACC_INTERFACE) {
|
||||
zend_error_noreturn(E_ERROR, "Cannot apply #[\\AllowDynamicProperties] to interface %s",
|
||||
ZSTR_VAL(scope->name)
|
||||
);
|
||||
}
|
||||
if (scope->ce_flags & ZEND_ACC_READONLY_CLASS) {
|
||||
zend_error_noreturn(E_ERROR, "Cannot apply #[\\AllowDynamicProperties] to readonly class %s",
|
||||
ZSTR_VAL(scope->name)
|
||||
);
|
||||
}
|
||||
if (scope->ce_flags & ZEND_ACC_ENUM) {
|
||||
zend_error_noreturn(E_ERROR, "Cannot apply #[\\AllowDynamicProperties] to enum %s",
|
||||
ZSTR_VAL(scope->name)
|
||||
);
|
||||
if (msg != NULL) {
|
||||
return zend_strpprintf(0, msg, ZSTR_VAL(scope->name));
|
||||
}
|
||||
scope->ce_flags |= ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void validate_attribute(
|
||||
static zend_string *validate_attribute(
|
||||
zend_attribute *attr, uint32_t target, zend_class_entry *scope)
|
||||
{
|
||||
const char *msg = NULL;
|
||||
@@ -109,8 +105,9 @@ static void validate_attribute(
|
||||
msg = "Cannot apply #[\\Attribute] to abstract class %s";
|
||||
}
|
||||
if (msg != NULL) {
|
||||
zend_error_noreturn(E_ERROR, msg, ZSTR_VAL(scope->name));
|
||||
return zend_strpprintf(0, msg, ZSTR_VAL(scope->name));
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ZEND_METHOD(Attribute, __construct)
|
||||
@@ -212,6 +209,20 @@ ZEND_METHOD(Deprecated, __construct)
|
||||
}
|
||||
}
|
||||
|
||||
static zend_string *validate_nodiscard(
|
||||
zend_attribute *attr, uint32_t target, zend_class_entry *scope)
|
||||
{
|
||||
ZEND_ASSERT(CG(in_compilation));
|
||||
const zend_string *prop_info_name = CG(context).active_property_info_name;
|
||||
if (prop_info_name != NULL) {
|
||||
// Applied to a hook
|
||||
return ZSTR_INIT_LITERAL("#[\\NoDiscard] is not supported for property hooks", 0);
|
||||
}
|
||||
zend_op_array *op_array = CG(active_op_array);
|
||||
op_array->fn_flags |= ZEND_ACC_NODISCARD;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ZEND_METHOD(NoDiscard, __construct)
|
||||
{
|
||||
zend_string *message = NULL;
|
||||
@@ -439,6 +450,9 @@ static void attr_free(zval *v)
|
||||
|
||||
zend_string_release(attr->name);
|
||||
zend_string_release(attr->lcname);
|
||||
if (attr->validation_error != NULL) {
|
||||
zend_string_release(attr->validation_error);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < attr->argc; i++) {
|
||||
if (attr->args[i].name) {
|
||||
@@ -471,6 +485,7 @@ ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_string
|
||||
}
|
||||
|
||||
attr->lcname = zend_string_tolower_ex(attr->name, persistent);
|
||||
attr->validation_error = NULL;
|
||||
attr->flags = flags;
|
||||
attr->lineno = lineno;
|
||||
attr->offset = offset;
|
||||
@@ -567,6 +582,10 @@ void zend_register_attribute_ce(void)
|
||||
|
||||
zend_ce_nodiscard = register_class_NoDiscard();
|
||||
attr = zend_mark_internal_attribute(zend_ce_nodiscard);
|
||||
attr->validator = validate_nodiscard;
|
||||
|
||||
zend_ce_delayed_target_validation = register_class_DelayedTargetValidation();
|
||||
attr = zend_mark_internal_attribute(zend_ce_delayed_target_validation);
|
||||
}
|
||||
|
||||
void zend_attributes_shutdown(void)
|
||||
|
||||
@@ -50,6 +50,7 @@ extern ZEND_API zend_class_entry *zend_ce_sensitive_parameter_value;
|
||||
extern ZEND_API zend_class_entry *zend_ce_override;
|
||||
extern ZEND_API zend_class_entry *zend_ce_deprecated;
|
||||
extern ZEND_API zend_class_entry *zend_ce_nodiscard;
|
||||
extern ZEND_API zend_class_entry *zend_ce_delayed_target_validation;
|
||||
|
||||
typedef struct {
|
||||
zend_string *name;
|
||||
@@ -59,6 +60,9 @@ typedef struct {
|
||||
typedef struct _zend_attribute {
|
||||
zend_string *name;
|
||||
zend_string *lcname;
|
||||
/* Only non-null for internal attributes with validation errors that are
|
||||
* delayed until runtime via #[\DelayedTargetValidation] */
|
||||
zend_string *validation_error;
|
||||
uint32_t flags;
|
||||
uint32_t lineno;
|
||||
/* Parameter offsets start at 1, everything else uses 0. */
|
||||
@@ -70,7 +74,7 @@ typedef struct _zend_attribute {
|
||||
typedef struct _zend_internal_attribute {
|
||||
zend_class_entry *ce;
|
||||
uint32_t flags;
|
||||
void (*validator)(zend_attribute *attr, uint32_t target, zend_class_entry *scope);
|
||||
zend_string* (*validator)(zend_attribute *attr, uint32_t target, zend_class_entry *scope);
|
||||
} zend_internal_attribute;
|
||||
|
||||
ZEND_API zend_attribute *zend_get_attribute(HashTable *attributes, zend_string *lcname);
|
||||
|
||||
@@ -97,3 +97,9 @@ final class NoDiscard
|
||||
|
||||
public function __construct(?string $message = null) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @strict-properties
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_ALL)]
|
||||
final class DelayedTargetValidation {}
|
||||
|
||||
17
Zend/zend_attributes_arginfo.h
generated
17
Zend/zend_attributes_arginfo.h
generated
@@ -1,5 +1,5 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: 715016d1ff1b0a6abb325a71083eff397a080c44 */
|
||||
* Stub hash: fa08288df8338c1a16fbf83c179c4084a56007e1 */
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Attribute___construct, 0, 0, 0)
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "Attribute::TARGET_ALL")
|
||||
@@ -276,3 +276,18 @@ static zend_class_entry *register_class_NoDiscard(void)
|
||||
|
||||
return class_entry;
|
||||
}
|
||||
|
||||
static zend_class_entry *register_class_DelayedTargetValidation(void)
|
||||
{
|
||||
zend_class_entry ce, *class_entry;
|
||||
|
||||
INIT_CLASS_ENTRY(ce, "DelayedTargetValidation", NULL);
|
||||
class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES);
|
||||
|
||||
zend_string *attribute_name_Attribute_class_DelayedTargetValidation_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, 1);
|
||||
zend_attribute *attribute_Attribute_class_DelayedTargetValidation_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_DelayedTargetValidation_0, 1);
|
||||
zend_string_release(attribute_name_Attribute_class_DelayedTargetValidation_0);
|
||||
ZVAL_LONG(&attribute_Attribute_class_DelayedTargetValidation_0->args[0].value, ZEND_ATTRIBUTE_TARGET_ALL);
|
||||
|
||||
return class_entry;
|
||||
}
|
||||
|
||||
@@ -7557,19 +7557,41 @@ static void zend_compile_attributes(
|
||||
}
|
||||
|
||||
if (*attributes != NULL) {
|
||||
/* Allow delaying target validation for forward compatibility. */
|
||||
zend_attribute *delayed_target_validation = NULL;
|
||||
if (target == ZEND_ATTRIBUTE_TARGET_PARAMETER) {
|
||||
ZEND_ASSERT(offset >= 1);
|
||||
/* zend_get_parameter_attribute_str will add 1 too */
|
||||
delayed_target_validation = zend_get_parameter_attribute_str(
|
||||
*attributes,
|
||||
"delayedtargetvalidation",
|
||||
strlen("delayedtargetvalidation"),
|
||||
offset - 1
|
||||
);
|
||||
} else {
|
||||
delayed_target_validation = zend_get_attribute_str(
|
||||
*attributes,
|
||||
"delayedtargetvalidation",
|
||||
strlen("delayedtargetvalidation")
|
||||
);
|
||||
}
|
||||
/* Validate attributes in a secondary loop (needed to detect repeated attributes). */
|
||||
ZEND_HASH_PACKED_FOREACH_PTR(*attributes, attr) {
|
||||
if (attr->offset != offset || NULL == (config = zend_internal_attribute_get(attr->lcname))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool run_validator = true;
|
||||
if (!(target & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) {
|
||||
zend_string *location = zend_get_attribute_target_names(target);
|
||||
zend_string *allowed = zend_get_attribute_target_names(config->flags);
|
||||
if (delayed_target_validation == NULL) {
|
||||
zend_string *location = zend_get_attribute_target_names(target);
|
||||
zend_string *allowed = zend_get_attribute_target_names(config->flags);
|
||||
|
||||
zend_error_noreturn(E_ERROR, "Attribute \"%s\" cannot target %s (allowed targets: %s)",
|
||||
ZSTR_VAL(attr->name), ZSTR_VAL(location), ZSTR_VAL(allowed)
|
||||
);
|
||||
zend_error_noreturn(E_ERROR, "Attribute \"%s\" cannot target %s (allowed targets: %s)",
|
||||
ZSTR_VAL(attr->name), ZSTR_VAL(location), ZSTR_VAL(allowed)
|
||||
);
|
||||
}
|
||||
run_validator = false;
|
||||
}
|
||||
|
||||
if (!(config->flags & ZEND_ATTRIBUTE_IS_REPEATABLE)) {
|
||||
@@ -7578,8 +7600,17 @@ static void zend_compile_attributes(
|
||||
}
|
||||
}
|
||||
|
||||
if (config->validator != NULL) {
|
||||
config->validator(attr, target, CG(active_class_entry));
|
||||
/* Validators are not run if the target is already invalid */
|
||||
if (run_validator && config->validator != NULL) {
|
||||
zend_string *error = config->validator(attr, target, CG(active_class_entry));
|
||||
if (error != NULL) {
|
||||
if (delayed_target_validation == NULL) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "%s", ZSTR_VAL(error));
|
||||
zend_string_efree(error);
|
||||
} else {
|
||||
attr->validation_error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
}
|
||||
@@ -8425,6 +8456,10 @@ static zend_op_array *zend_compile_func_decl_ex(
|
||||
|
||||
CG(active_op_array) = op_array;
|
||||
|
||||
zend_oparray_context_begin(&orig_oparray_context, op_array);
|
||||
CG(context).active_property_info_name = property_info_name;
|
||||
CG(context).active_property_hook_kind = hook_kind;
|
||||
|
||||
if (decl->child[4]) {
|
||||
int target = ZEND_ATTRIBUTE_TARGET_FUNCTION;
|
||||
|
||||
@@ -8454,15 +8489,7 @@ static zend_op_array *zend_compile_func_decl_ex(
|
||||
op_array->fn_flags |= ZEND_ACC_DEPRECATED;
|
||||
}
|
||||
|
||||
zend_attribute *nodiscard_attribute = zend_get_attribute_str(
|
||||
op_array->attributes,
|
||||
"nodiscard",
|
||||
sizeof("nodiscard")-1
|
||||
);
|
||||
|
||||
if (nodiscard_attribute) {
|
||||
op_array->fn_flags |= ZEND_ACC_NODISCARD;
|
||||
}
|
||||
// ZEND_ACC_NODISCARD is added via an attribute validator
|
||||
}
|
||||
|
||||
/* Do not leak the class scope into free standing functions, even if they are dynamically
|
||||
@@ -8476,10 +8503,6 @@ static zend_op_array *zend_compile_func_decl_ex(
|
||||
op_array->fn_flags |= ZEND_ACC_TOP_LEVEL;
|
||||
}
|
||||
|
||||
zend_oparray_context_begin(&orig_oparray_context, op_array);
|
||||
CG(context).active_property_info_name = property_info_name;
|
||||
CG(context).active_property_hook_kind = hook_kind;
|
||||
|
||||
{
|
||||
/* Push a separator to the loop variable stack */
|
||||
zend_loop_var dummy_var;
|
||||
@@ -8514,9 +8537,12 @@ static zend_op_array *zend_compile_func_decl_ex(
|
||||
}
|
||||
|
||||
if (op_array->fn_flags & ZEND_ACC_NODISCARD) {
|
||||
if (is_hook) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "#[\\NoDiscard] is not supported for property hooks");
|
||||
}
|
||||
/* ZEND_ACC_NODISCARD gets added by the attribute validator, but only
|
||||
* if the method is not a hook; if it is a hook, then the validator
|
||||
* will have returned an error message, even if the error message was
|
||||
* delayed with #[\DelayedTargetValidation] that ZEND_ACC_NODISCARD
|
||||
* flag should not have been added. */
|
||||
ZEND_ASSERT(!is_hook);
|
||||
|
||||
if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
|
||||
zend_arg_info *return_info = CG(active_op_array)->arg_info - 1;
|
||||
|
||||
@@ -461,6 +461,7 @@ static void zend_file_cache_serialize_attribute(zval *zv,
|
||||
|
||||
SERIALIZE_STR(attr->name);
|
||||
SERIALIZE_STR(attr->lcname);
|
||||
SERIALIZE_STR(attr->validation_error);
|
||||
|
||||
for (i = 0; i < attr->argc; i++) {
|
||||
SERIALIZE_STR(attr->args[i].name);
|
||||
@@ -1352,6 +1353,7 @@ static void zend_file_cache_unserialize_attribute(zval *zv, zend_persistent_scri
|
||||
|
||||
UNSERIALIZE_STR(attr->name);
|
||||
UNSERIALIZE_STR(attr->lcname);
|
||||
UNSERIALIZE_STR(attr->validation_error);
|
||||
|
||||
for (i = 0; i < attr->argc; i++) {
|
||||
UNSERIALIZE_STR(attr->args[i].name);
|
||||
|
||||
@@ -311,6 +311,9 @@ static HashTable *zend_persist_attributes(HashTable *attributes)
|
||||
|
||||
zend_accel_store_interned_string(copy->name);
|
||||
zend_accel_store_interned_string(copy->lcname);
|
||||
if (copy->validation_error) {
|
||||
zend_accel_store_interned_string(copy->validation_error);
|
||||
}
|
||||
|
||||
for (i = 0; i < copy->argc; i++) {
|
||||
if (copy->args[i].name) {
|
||||
|
||||
@@ -181,6 +181,9 @@ static void zend_persist_attributes_calc(HashTable *attributes)
|
||||
ADD_SIZE(ZEND_ATTRIBUTE_SIZE(attr->argc));
|
||||
ADD_INTERNED_STRING(attr->name);
|
||||
ADD_INTERNED_STRING(attr->lcname);
|
||||
if (attr->validation_error != NULL) {
|
||||
ADD_INTERNED_STRING(attr->validation_error);
|
||||
}
|
||||
|
||||
for (i = 0; i < attr->argc; i++) {
|
||||
if (attr->args[i].name) {
|
||||
|
||||
@@ -7321,26 +7321,61 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
/* This code can be reached under one of three possible conditions:
|
||||
* - the attribute is an internal attribute, and it had the target and
|
||||
* and repetition validated already
|
||||
* - the attribute is an internal attribute and repetition was validated
|
||||
* already, the internal validator might have been run if the target was
|
||||
* correct, but any error would have been stored in
|
||||
* `zend_attribute.validation_error` instead of being thrown due to the
|
||||
* presence of #[DelayedTargetValidation]
|
||||
* - the attribute is a user attribute, and neither target nor repetition
|
||||
* have been validated.
|
||||
*/
|
||||
uint32_t flags = zend_attribute_attribute_get_flags(marker, ce);
|
||||
if (EG(exception)) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
/* No harm in always running target validation, for internal attributes
|
||||
* without #[DelayedTargetValidation] it isn't necessary but will always
|
||||
* succeed. */
|
||||
if (!(attr->target & flags)) {
|
||||
zend_string *location = zend_get_attribute_target_names(attr->target);
|
||||
zend_string *allowed = zend_get_attribute_target_names(flags);
|
||||
|
||||
zend_throw_error(NULL, "Attribute \"%s\" cannot target %s (allowed targets: %s)",
|
||||
ZSTR_VAL(attr->data->name), ZSTR_VAL(location), ZSTR_VAL(allowed)
|
||||
);
|
||||
|
||||
zend_string_release(location);
|
||||
zend_string_release(allowed);
|
||||
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
if (attr->data->validation_error != NULL) {
|
||||
/* Delayed validation errors should only be set for internal attributes. */
|
||||
ZEND_ASSERT(ce->type == ZEND_INTERNAL_CLASS);
|
||||
/* Delayed validation errors should only be set when
|
||||
* #[\DelayedTargetValidation] is used. Searching for the attribute is
|
||||
* more expensive than just an assertion and so we don't worry about it
|
||||
* for non-debug builds. See discussion on GH-18817. */
|
||||
#if ZEND_DEBUG
|
||||
zend_attribute *delayed_target_validation = zend_get_attribute_str(
|
||||
attr->attributes,
|
||||
"delayedtargetvalidation",
|
||||
strlen("delayedtargetvalidation")
|
||||
);
|
||||
ZEND_ASSERT(delayed_target_validation != NULL);
|
||||
#endif
|
||||
zend_throw_exception(zend_ce_error, ZSTR_VAL(attr->data->validation_error), 0);
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
/* Repetition validation is done even if #[DelayedTargetValidation] is used
|
||||
* and so can be skipped for internal attributes. */
|
||||
if (ce->type == ZEND_USER_CLASS) {
|
||||
uint32_t flags = zend_attribute_attribute_get_flags(marker, ce);
|
||||
if (EG(exception)) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
if (!(attr->target & flags)) {
|
||||
zend_string *location = zend_get_attribute_target_names(attr->target);
|
||||
zend_string *allowed = zend_get_attribute_target_names(flags);
|
||||
|
||||
zend_throw_error(NULL, "Attribute \"%s\" cannot target %s (allowed targets: %s)",
|
||||
ZSTR_VAL(attr->data->name), ZSTR_VAL(location), ZSTR_VAL(allowed)
|
||||
);
|
||||
|
||||
zend_string_release(location);
|
||||
zend_string_release(allowed);
|
||||
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
if (!(flags & ZEND_ATTRIBUTE_IS_REPEATABLE)) {
|
||||
if (zend_is_attribute_repeated(attr->attributes, attr->data)) {
|
||||
zend_throw_error(NULL, "Attribute \"%s\" must not be repeated", ZSTR_VAL(attr->data->name));
|
||||
|
||||
@@ -958,11 +958,12 @@ static zend_function *zend_test_class_static_method_get(zend_class_entry *ce, ze
|
||||
return zend_std_get_static_method(ce, name, NULL);
|
||||
}
|
||||
|
||||
void zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope)
|
||||
zend_string *zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope)
|
||||
{
|
||||
if (target != ZEND_ATTRIBUTE_TARGET_CLASS) {
|
||||
zend_error(E_COMPILE_ERROR, "Only classes can be marked with #[ZendTestAttribute]");
|
||||
return ZSTR_INIT_LITERAL("Only classes can be marked with #[ZendTestAttribute]", 0);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ZEND_METHOD(_ZendTestClass, __toString)
|
||||
|
||||
Reference in New Issue
Block a user