1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

[RFC] Allow #[\Deprecated] on traits (#19045)

https://wiki.php.net/rfc/deprecated_traits
This commit is contained in:
Daniel Scherzer
2025-09-05 12:30:50 -07:00
committed by GitHub
parent 8cd085a179
commit 34a6e86282
20 changed files with 448 additions and 11 deletions

View File

@@ -3,9 +3,10 @@ Constants listed in valid targets when used wrong (internal attribute)
--FILE--
<?php
#[Deprecated]
class Example {}
function demo(
#[Deprecated] $v
) {}
?>
--EXPECTF--
Fatal error: Attribute "Deprecated" cannot target class (allowed targets: function, method, class constant, constant) in %s on line %d
Fatal error: Attribute "Deprecated" cannot target parameter (allowed targets: class, function, method, class constant, constant) in %s on line %d

View File

@@ -0,0 +1,142 @@
--TEST--
#[\DelayedTargetValidation] with #[\Deprecated]: validator errors delayed
--FILE--
<?php
#[DelayedTargetValidation]
#[Deprecated]
interface DemoInterface {}
#[DelayedTargetValidation]
#[Deprecated]
class DemoClass {}
#[DelayedTargetValidation]
#[Deprecated]
enum DemoEnum {}
$cases = [
new ReflectionClass('DemoInterface'),
new ReflectionClass('DemoClass'),
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--
********************
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(10) "Deprecated"
}
}
Error: Cannot apply #[\Deprecated] to interface DemoInterface
********************
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(10) "Deprecated"
}
}
Error: Cannot apply #[\Deprecated] to class DemoClass
********************
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(10) "Deprecated"
}
}
Error: Cannot apply #[\Deprecated] to enum DemoEnum

View File

@@ -40,6 +40,14 @@ class DemoClass {
}
}
#[DelayedTargetValidation]
#[Deprecated] // Does something here
trait DeprecatedTrait {}
class WithDeprecatedTrait {
use DeprecatedTrait;
}
#[DelayedTargetValidation]
#[Deprecated] // Does something here
function demoFn() {
@@ -61,6 +69,7 @@ demoFn();
var_dump(GLOBAL_CONST);
?>
--EXPECTF--
Deprecated: Trait DeprecatedTrait used by WithDeprecatedTrait is deprecated in %s on line %d
Got: example
Deprecated: Method DemoClass::printVal() is deprecated in %s on line %d

View File

@@ -0,0 +1,11 @@
--TEST--
#[\Deprecated]: Using on a class
--FILE--
<?php
#[\Deprecated]
class Demo {}
?>
--EXPECTF--
Fatal error: Cannot apply #[\Deprecated] to class Demo in %s on line %d

View File

@@ -0,0 +1,11 @@
--TEST--
#[\Deprecated]: Using on an enum
--FILE--
<?php
#[\Deprecated]
enum Demo {}
?>
--EXPECTF--
Fatal error: Cannot apply #[\Deprecated] to enum Demo in %s on line %d

View File

@@ -0,0 +1,11 @@
--TEST--
#[\Deprecated]: Using on an interface
--FILE--
<?php
#[\Deprecated]
interface Demo {}
?>
--EXPECTF--
Fatal error: Cannot apply #[\Deprecated] to interface Demo in %s on line %d

View File

@@ -0,0 +1,33 @@
--TEST--
#[\Deprecated]: Basic trait deprecation
--FILE--
<?php
#[\Deprecated("please do not use")]
trait DemoTrait1 {}
#[\Deprecated("will be removed in 3.0", since: "2.7")]
trait DemoTrait2 {}
#[\Deprecated(message: "going away")]
trait DemoTrait3 {}
#[\Deprecated(since: "3.5")]
trait DemoTrait4 {}
class DemoClass {
use DemoTrait1;
use DemoTrait2;
use DemoTrait3;
use DemoTrait4;
}
?>
--EXPECTF--
Deprecated: Trait DemoTrait1 used by DemoClass is deprecated, please do not use in %s on line %d
Deprecated: Trait DemoTrait2 used by DemoClass is deprecated since 2.7, will be removed in 3.0 in %s on line %d
Deprecated: Trait DemoTrait3 used by DemoClass is deprecated, going away in %s on line %d
Deprecated: Trait DemoTrait4 used by DemoClass is deprecated since 3.5 in %s on line %d

View File

@@ -0,0 +1,18 @@
--TEST--
#[\Deprecated]: Deprecated traits only apply to direct use, not inheritance
--FILE--
<?php
#[\Deprecated]
trait DemoTrait {}
class DemoClass {
use DemoTrait;
}
class ChildClass extends DemoClass {
}
?>
--EXPECTF--
Deprecated: Trait DemoTrait used by DemoClass is deprecated in %s on line %d

View File

@@ -0,0 +1,43 @@
--TEST--
#[\Deprecated]: `insteadof` rendering a trait unused still triggers deprecation messages
--FILE--
<?php
#[Deprecated]
trait DemoTraitA {
public function lowerCase(): string {
return 'a';
}
public function upperCase(): string {
return 'A';
}
}
#[Deprecated]
trait DemoTraitB {
public function lowerCase(): string {
return 'b';
}
public function upperCase(): string {
return 'B';
}
}
class DemoClass {
use DemoTraita, DemoTraitB {
DemoTraitA::lowerCase insteadof DemoTraitB;
DemoTraitA::upperCase insteadof DemoTraitB;
}
}
$d = new DemoClass();
var_dump($d->lowerCase());
var_dump($d->upperCase());
?>
--EXPECTF--
Deprecated: Trait DemoTraitA used by DemoClass is deprecated in %s on line %d
Deprecated: Trait DemoTraitB used by DemoClass is deprecated in %s on line %d
string(1) "a"
string(1) "A"

View File

@@ -0,0 +1,22 @@
--TEST--
#[\Deprecated]: Using multiple traits
--FILE--
<?php
#[\Deprecated]
trait DemoTraitA {}
#[\Deprecated]
trait DemoTraitB {}
trait DemoTraitC {}
class DemoClass {
use DemoTraitA, DemoTraitB, DemoTraitC;
}
?>
--EXPECTF--
Deprecated: Trait DemoTraitA used by DemoClass is deprecated in %s on line %d
Deprecated: Trait DemoTraitB used by DemoClass is deprecated in %s on line %d

View File

@@ -0,0 +1,25 @@
--TEST--
#[\Deprecated]: Deprecation converted to ErrorException does not break
--FILE--
<?php
function my_error_handler(int $errno, string $errstr, ?string $errfile = null, ?int $errline = null) {
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler('my_error_handler');
#[\Deprecated]
trait DemoTrait {}
class DemoClass {
use DemoTrait;
}
?>
--EXPECTF--
Fatal error: Uncaught ErrorException: Trait DemoTrait used by DemoClass is deprecated in %s:%d
Stack trace:
#0 %s: my_error_handler(16384, 'Trait DemoTrait...', '%s', %d)
#1 {main}
thrown in %s on line %d

View File

@@ -0,0 +1,19 @@
--TEST--
#[\Deprecated]: Trait using a deprecated trait
--FILE--
<?php
#[\Deprecated]
trait DemoTraitA {}
trait DemoTraitB {
use DemoTraitA;
}
class DemoClass {
use DemoTraitB;
}
?>
--EXPECTF--
Deprecated: Trait DemoTraitA used by DemoTraitB is deprecated in %s on line %d

View File

@@ -0,0 +1,43 @@
--TEST--
#[\Deprecated]: Using multiple traits with conflict resolution
--FILE--
<?php
#[Deprecated]
trait DemoTraitA {
public function lowerCase(): string {
return 'a';
}
public function upperCase(): string {
return 'A';
}
}
#[Deprecated]
trait DemoTraitB {
public function lowerCase(): string {
return 'b';
}
public function upperCase(): string {
return 'B';
}
}
class DemoClass {
use DemoTraita, DemoTraitB {
DemoTraitA::lowerCase insteadof DemoTraitB;
DemoTraitB::upperCase insteadof DemoTraitA;
}
}
$d = new DemoClass();
var_dump($d->lowerCase());
var_dump($d->upperCase());
?>
--EXPECTF--
Deprecated: Trait DemoTraitA used by DemoClass is deprecated in %s on line %d
Deprecated: Trait DemoTraitB used by DemoClass is deprecated in %s on line %d
string(1) "a"
string(1) "B"

View File

@@ -110,6 +110,25 @@ static zend_string *validate_attribute(
return NULL;
}
static zend_string *validate_deprecated(
zend_attribute *attr,
uint32_t target,
zend_class_entry *scope
) {
if (target != ZEND_ATTRIBUTE_TARGET_CLASS) {
/* Being used for a method or something, validation does not apply */
return NULL;
}
if (!(scope->ce_flags & ZEND_ACC_TRAIT)) {
const char *type = zend_get_object_type_case(scope, false);
return zend_strpprintf(0, "Cannot apply #[\\Deprecated] to %s %s", type, ZSTR_VAL(scope->name));
}
scope->ce_flags |= ZEND_ACC_DEPRECATED;
return NULL;
}
ZEND_METHOD(Attribute, __construct)
{
zend_long flags = ZEND_ATTRIBUTE_TARGET_ALL;
@@ -579,6 +598,7 @@ void zend_register_attribute_ce(void)
zend_ce_deprecated = register_class_Deprecated();
attr = zend_mark_internal_attribute(zend_ce_deprecated);
attr->validator = validate_deprecated;
zend_ce_nodiscard = register_class_NoDiscard();
attr = zend_mark_internal_attribute(zend_ce_nodiscard);

View File

@@ -77,7 +77,7 @@ final class Override
/**
* @strict-properties
*/
#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION|Attribute::TARGET_CLASS_CONSTANT|Attribute::TARGET_CONSTANT)]
#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION|Attribute::TARGET_CLASS_CONSTANT|Attribute::TARGET_CONSTANT|Attribute::TARGET_CLASS)]
final class Deprecated
{
public readonly ?string $message;

View File

@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: fa08288df8338c1a16fbf83c179c4084a56007e1 */
* Stub hash: b868cb33f41d9442f42d0cec84e33fcc09f5d88c */
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")
@@ -253,7 +253,7 @@ static zend_class_entry *register_class_Deprecated(void)
zend_string *attribute_name_Attribute_class_Deprecated_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, 1);
zend_attribute *attribute_Attribute_class_Deprecated_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_Deprecated_0, 1);
zend_string_release(attribute_name_Attribute_class_Deprecated_0);
ZVAL_LONG(&attribute_Attribute_class_Deprecated_0->args[0].value, ZEND_ATTRIBUTE_TARGET_METHOD | ZEND_ATTRIBUTE_TARGET_FUNCTION | ZEND_ATTRIBUTE_TARGET_CLASS_CONST | ZEND_ATTRIBUTE_TARGET_CONST);
ZVAL_LONG(&attribute_Attribute_class_Deprecated_0->args[0].value, ZEND_ATTRIBUTE_TARGET_METHOD | ZEND_ATTRIBUTE_TARGET_FUNCTION | ZEND_ATTRIBUTE_TARGET_CLASS_CONST | ZEND_ATTRIBUTE_TARGET_CONST | ZEND_ATTRIBUTE_TARGET_CLASS);
return class_entry;
}

View File

@@ -255,6 +255,9 @@ typedef struct _zend_oparray_context {
/* or IS_CONSTANT_VISITED_MARK | | | */
#define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */
/* | | | */
/* deprecation flag | | | */
#define ZEND_ACC_DEPRECATED (1 << 11) /* X | X | | X */
/* | | | */
/* has #[\Override] attribute | | | */
#define ZEND_ACC_OVERRIDE (1 << 28) /* | X | X | */
/* | | | */
@@ -272,7 +275,7 @@ typedef struct _zend_oparray_context {
#define ZEND_ACC_PROTECTED_SET (1 << 11) /* | | X | */
#define ZEND_ACC_PRIVATE_SET (1 << 12) /* | | X | */
/* | | | */
/* Class Flags (unused: 30,31) | | | */
/* Class Flags (unused: 31) | | | */
/* =========== | | | */
/* | | | */
/* Special class types | | | */
@@ -290,7 +293,7 @@ typedef struct _zend_oparray_context {
/* | | | */
/* Class has magic methods __get/__set/__unset/ | | | */
/* __isset that use guards | | | */
#define ZEND_ACC_USE_GUARDS (1 << 11) /* X | | | */
#define ZEND_ACC_USE_GUARDS (1 << 30) /* X | | | */
/* | | | */
/* Class constants updated | | | */
#define ZEND_ACC_CONSTANTS_UPDATED (1 << 12) /* X | | | */
@@ -341,9 +344,6 @@ typedef struct _zend_oparray_context {
/* Function Flags (unused: 30) | | | */
/* ============== | | | */
/* | | | */
/* deprecation flag | | | */
#define ZEND_ACC_DEPRECATED (1 << 11) /* | X | | X */
/* | | | */
/* Function returning by reference | | | */
#define ZEND_ACC_RETURN_REFERENCE (1 << 12) /* | X | | */
/* | | | */

View File

@@ -2006,6 +2006,27 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_constant(const zend_consta
zend_string_release(message_suffix);
}
ZEND_API ZEND_COLD void zend_use_of_deprecated_trait(
zend_class_entry *trait,
const zend_string *used_by
) {
zend_string *message_suffix = ZSTR_EMPTY_ALLOC();
if (get_deprecation_suffix_from_attribute(trait->attributes, trait, &message_suffix) == FAILURE) {
return;
}
int code = trait->type == ZEND_INTERNAL_CLASS ? E_DEPRECATED : E_USER_DEPRECATED;
zend_error_unchecked(code, "Trait %s used by %s is deprecated%S",
ZSTR_VAL(trait->name),
ZSTR_VAL(used_by),
message_suffix
);
zend_string_release(message_suffix);
}
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_false_to_array_deprecated(void)
{
zend_error(E_DEPRECATED, "Automatic conversion of false to array is deprecated");

View File

@@ -66,6 +66,7 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_function(const zend_functi
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_nodiscard_function(const zend_function *fbc);
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_class_constant(const zend_class_constant *c, const zend_string *constant_name);
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_constant(const zend_constant *c, const zend_string *constant_name);
ZEND_API ZEND_COLD void zend_use_of_deprecated_trait(zend_class_entry *trait, const zend_string *used_by);
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_false_to_array_deprecated(void);
ZEND_COLD void ZEND_FASTCALL zend_param_must_be_ref(const zend_function *func, uint32_t arg_num);
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_use_resource_as_offset(const zval *dim);

View File

@@ -3524,6 +3524,13 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string
free_alloca(traits_and_interfaces, use_heap);
return NULL;
}
if (UNEXPECTED(trait->ce_flags & ZEND_ACC_DEPRECATED)) {
zend_use_of_deprecated_trait(trait, ce->name);
if (UNEXPECTED(EG(exception))) {
free_alloca(traits_and_interfaces, use_heap);
return NULL;
}
}
for (j = 0; j < i; j++) {
if (traits_and_interfaces[j] == trait) {
/* skip duplications */