rfc:deprecated_attribute

PHP RFC: #[\Deprecated] Attribute

Introduction

PHP’s internal functions 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.

Proposal

A new attribute #[\Deprecated] shall be added:

#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION)]
final class Deprecated
{
    public readonly ?string $message;
 
    public function __construct(?string $message = null) {}
}

Applying this attribute to a userland function or method will add the internal ZEND_ACC_DEPRECATED flag to the function, resulting in a behavior that is consistent with the existing deprecation functionality for internal functions. This means:

  • ReflectionFunctionAbstract::isDeprecated() will return true.
  • Calling the function will emit a E_USER_DEPRECATED error (internal functions emit E_DEPRECATED, but this error code is reserved for internal functions).

The $message given within the attribute definition will be included in the error message when calling the function. It goes without saying that it will also be available to static analysis tools, just like the parameters of any other attribute.

Examples

<?php
 
#[\Deprecated]
function test() {
}
 
#[\Deprecated("use test() instead")]
function test2() {
}
 
class Clazz {
    #[\Deprecated]
    function test() {
    }
 
    #[\Deprecated("use test() instead")]
    function test2() {
    }
}
 
test(); // Deprecated: Function test() is deprecated in test.php on line 21
test2(); // Deprecated: Function test2() is deprecated, use test() instead in test.php on line 22
call_user_func("test"); // Deprecated: Function test() is deprecated in test.php on line 23
 
$cls = new Clazz();
$cls->test(); // Deprecated: Method Clazz::test() is deprecated in test.php on line 26
$cls->test2(); // Deprecated: Method Clazz::test2() is deprecated, use test() instead in test.php on line 27
 
call_user_func([$cls, "test"]); // Deprecated: Method Clazz::test() is deprecated in test.php on line 29
 
?>
<?php
 
#[\Deprecated]
function test() {
}
 
$r = new ReflectionFunction('test');
 
var_dump($r->isDeprecated()); // bool(true)
 
?>
<?php
 
#[\Deprecated]
function test1() {
}
 
#[\Deprecated()]
function test2() {
}
 
#[\Deprecated("use test() instead")]
function test3() {
}
 
$reflection = new ReflectionFunction('test1');
var_dump($reflection->getAttributes()[0]->newInstance());
/*
object(Deprecated)#3 (1) {
  ["message"]=>
  NULL
}
*/
 
$reflection = new ReflectionFunction('test2');
var_dump($reflection->getAttributes()[0]->newInstance());
/*
object(Deprecated)#2 (1) {
  ["message"]=>
  NULL
}
*/
 
$reflection = new ReflectionFunction('test3');
var_dump($reflection->getAttributes()[0]->newInstance());
/*
object(Deprecated)#1 (1) {
  ["message"]=>
  string(18) "use test() instead"
}
*/
 
?>

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. 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

Future Scope

  • Supporting #[\Deprecated] on classes or other targets of attributes.
  • Adding further metadata to the #[\Deprecated] attribute beyond a custom message (e.g. hints for replacements that IDEs could use).

Proposed Voting Choices

Implement the #[\Deprecated] attribute as described?
Real name Yes No
Final result: 0 0
This poll has been closed.

Patches and Tests

Implementation

n/a

References

Rejected Features

  • Changes to the runtime behavior of deprecated functions 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.
rfc/deprecated_attribute.txt · Last modified: 2024/04/25 12:29 by beberlei