PHP RFC: #[\Deprecated] Attribute
- Version: 1.4
- Date: 2024-05-04
- Author: Benjamin Eberlei, Tim Düsterhus
- Status: Implemented
- Target Version: PHP 8.4
- First Published at: http://wiki.php.net/rfc/deprecated_attribute
Introduction
PHP’s internal functions and (class-)constants can be marked as deprecated, making this information available to Reflection and emitting deprecation errors (E_DEPRECATED
), but there is no equivalent functionality for functions defined in userland.
While the functionality can be emulated using trigger_error()
to emit a E_USER_DEPRECATED
error when calling a deprecated function, combined with either parsing a doc comment for the /** @deprecated */
annotation or attaching a custom-defined attribute and reading it with Reflection, it requires special handling depending on whether the function in question is an internal or a userland function. ReflectionFunctionAbstract::isDeprecated()
always returns false
for userland functions - and doc comments / custom attributes are unavailable for internal functions. For class constants the deprecation behavior cannot be emulated at all.
Proposal
A new attribute #[\Deprecated]
shall be added:
#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION|Attribute::TARGET_CLASS_CONSTANT)] final class Deprecated { public readonly ?string $message; public readonly ?string $since; public function __construct(?string $message = null, ?string $since = null) { /* … */ } }
Applying this attribute to a userland function, method, or class constant will add the internal ZEND_ACC_DEPRECATED
flag to the element, resulting in a behavior that is consistent with the existing deprecation functionality for internal functions and class constants. This means:
- For functions and methods
ReflectionFunctionAbstract::isDeprecated()
will returntrue
. - For class constants
ReflectionClassConstant::isDeprecated()
will returntrue
(this method is newly added in PHP 8.4). - Calling a function will emit a
E_USER_DEPRECATED
error (internal functions emitE_DEPRECATED
, but this error code is reserved for internal functions). - Accessing a class constant will emit a
E_USER_DEPRECATED
error (likewise internal class constants useE_DEPRECATED
).
The $message
given within the attribute definition will be included in the error message when calling the function or accessing the class constant. It goes without saying that it will also be available to static analysis tools, just like the parameters of any other attribute.
Likewise the $since
value is also included within the error message, intended to indicate since when the element having the attribute is deprecated. It is included for easier programmatic access, due to its pre-existing use within the PHP documentation and also userland libraries and frameworks. The contents of $since
may be a free-form string that is not validated. The semantics of the property is left to the library applying the #[\Deprecated]
attribute. It may contain a version number, a date, or anything else the library in question considers appropriate.
While internal, non-class constants can also be marked as deprecated, there is no support for attributes on them yet, and ReflectionConstant
has just been added in PHP 8.4. Adding attribute support for non-class constants is out of scope of this RFC/PR for now.
Enum cases are internally implemented as class constants. Adding the #[\Deprecated]
attribute to an enum case will behave as expected.
Examples
<?php #[\Deprecated] function test() { } #[\Deprecated("use test() instead")] function test2() { } #[\Deprecated("use test() instead", since: "2.4")] function test3() { } #[\Deprecated(since: "2024-05-07")] function test4() { } class Clazz { #[\Deprecated] public const OLD_WAY = 'foo'; #[\Deprecated] function test() { } #[\Deprecated("use test() instead")] function test2() { } } enum MyEnum { #[\Deprecated] case OldCase; } test(); // Deprecated: Function test() is deprecated in test.php on line 37 test2(); // Deprecated: Function test2() is deprecated, use test() instead in test.php on line 38 test3(); // Deprecated: Function test2() is deprecated since 2.4, use test() instead in test.php on line 39 test4(); // Deprecated: Function test4() is deprecated since 2024-05-07 in test.php on line 40 call_user_func("test"); // Deprecated: Function test() is deprecated in test.php on line 41 $cls = new Clazz(); $cls->test(); // Deprecated: Method Clazz::test() is deprecated in test.php on line 44 $cls->test2(); // Deprecated: Method Clazz::test2() is deprecated, use test() instead in test.php on line 45 Clazz::OLD_WAY; // Deprecated: Constant Clazz::OLD_WAY is deprecated in test.php on line 46 MyEnum::OldCase; // Deprecated: Enum case MyEnum::OldCase is deprecated in test.php on line 48 call_user_func([$cls, "test"]); // Deprecated: Method Clazz::test() is deprecated in test.php on line 50 ?>
<?php #[\Deprecated] function test() { } $r = new ReflectionFunction('test'); var_dump($r->isDeprecated()); // bool(true) ?>
<?php class Clazz { #[\Deprecated] public const OLD_WAY = 'foo'; } $r = new ReflectionClassConstant(Clazz::class, 'OLD_WAY'); var_dump($r->isDeprecated()); // bool(true) ?>
<?php #[\Deprecated] function test1() { } #[\Deprecated()] function test2() { } #[\Deprecated("use test() instead")] function test3() { } #[\Deprecated("use test() instead", since: "2.4")] function test4() { } #[\Deprecated(since: "2024-05-07")] function test5() { } $reflection = new ReflectionFunction('test1'); var_dump($reflection->getAttributes()[0]->newInstance()); /* object(Deprecated)#3 (2) { ["message"]=> NULL ["since"]=> NULL } */ $reflection = new ReflectionFunction('test2'); var_dump($reflection->getAttributes()[0]->newInstance()); /* object(Deprecated)#2 (2) { ["message"]=> NULL ["since"]=> NULL } */ $reflection = new ReflectionFunction('test3'); var_dump($reflection->getAttributes()[0]->newInstance()); /* object(Deprecated)#1 (2) { ["message"]=> string(18) "use test() instead" ["since"]=> NULL } */ $reflection = new ReflectionFunction('test4'); var_dump($reflection->getAttributes()[0]->newInstance()); /* object(Deprecated)#3 (2) { ["message"]=> string(18) "use test() instead" ["since"]=> string(3) "2.4" } */ $reflection = new ReflectionFunction('test5'); var_dump($reflection->getAttributes()[0]->newInstance()); /* object(Deprecated)#2 (2) { ["message"]=> NULL ["since"]=> string(10) "2024-05-07" } */ ?>
Further examples are given by the newly added tests within the PR for this RFC.
Backward Incompatible Changes
Deprecated
can no longer be used as a class name in the global namespace. A GitHub search for “class Deprecated ” language:php symbol:deprecated
revealed a total of 173 matches in source code. The vast majority of them appear to be defined within a namespace.
Proposed PHP Version(s)
Next minor (PHP 8.4).
RFC Impact
To SAPIs
None.
To Existing Extensions
The #[\Deprecated]
attribute will also be available to internal functions and internal class constants. Within a stub file it will have the same effect as adding a /** @deprecated */
doc comment. The attribute will not be automatically applied to existing functions having the doc comment, but extension authors are encouraged to apply the attribute for consistency reasons.
For extensions that are part of php-src the attribute will replace the existing doc comment as part of this RFC.
To Opcache
None.
New Constants
None.
php.ini Defaults
None.
Open Issues
A few things tracked in https://github.com/php/php-src/pull/11293
Future Scope
- Supporting
#[\Deprecated]
on other targets of attributes that to not yet support deprecations for internally defined symbols, for example classes. - Adding further metadata to the
#[\Deprecated]
attribute beyond a custom message (e.g. hints for replacements that IDEs could use).
Proposed Voting Choices
Primary Vote
2/3 majority for the primary vote:
Secondary Votes
50% majority for each secondary vote. Ties are broken at the RFC authors’ discretion.
Patches and Tests
Implementation
- Creating the Attribute: https://github.com/php/php-src/commit/72c874691b99a88feada151fca337ff9e471c31e
- Using the Attribute in Stubs: https://github.com/php/php-src/commit/29f98e748568ebd66aaae061c0dcefbba92ca058
References
- Implementation: https://github.com/php/php-src/pull/11293
- Early Mailing List Discussion: https://externals.io/message/112554#112554
Rejected Features
- Changes to the runtime behavior of deprecated functions and class constants are out of scope of this RFC (i.e. not emitting the
E_DEPRECATED
error for internal functions). - Making the
Deprecated
attribute class non-final: Child classes of attributes are not understood by the engine for technical reasons and the semantics of a child class would be less clear for static analysis tools.