Skip to content

Commit 4395ef4

Browse files
beberleiTimWolla
andcommitted
Add #[\Deprecated] attribute
Co-authored-by: Tim Düsterhus <[email protected]>
1 parent 7192379 commit 4395ef4

10 files changed

+266
-7
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
#[Deprecated] attribute
3+
--FILE--
4+
<?php
5+
6+
error_reporting(E_ALL | E_DEPRECATED);
7+
ini_set('display_errors', true);
8+
9+
#[Deprecated]
10+
function test() {
11+
}
12+
13+
#[Deprecated("use test() instead")]
14+
function test2() {
15+
}
16+
17+
class Clazz {
18+
#[Deprecated]
19+
function test() {
20+
}
21+
22+
#[Deprecated("use test() instead")]
23+
function test2() {
24+
}
25+
}
26+
27+
test();
28+
test2();
29+
call_user_func("test");
30+
31+
$cls = new Clazz();
32+
$cls->test();
33+
$cls->test2();
34+
35+
call_user_func([$cls, "test"]);
36+
--EXPECTF--
37+
Deprecated: Function test() is deprecated in %s
38+
39+
Deprecated: Function test2() is deprecated, use test() instead in %s
40+
41+
Deprecated: Function test() is deprecated in %s
42+
43+
Deprecated: Method Clazz::test() is deprecated in %s
44+
45+
Deprecated: Method Clazz::test2() is deprecated, use test() instead in %s
46+
47+
Deprecated: Method Clazz::test() is deprecated in %s
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
#[Deprecated] attribute
3+
--FILE--
4+
<?php
5+
6+
#[Deprecated()]
7+
function test() {
8+
}
9+
10+
#[Deprecated("use test() instead")]
11+
function test2() {
12+
}
13+
14+
$reflection = new ReflectionFunction('test');
15+
var_dump($reflection->getAttributes()[0]->newInstance());
16+
17+
$reflection = new ReflectionFunction('test2');
18+
var_dump($reflection->getAttributes()[0]->newInstance());
19+
20+
--EXPECTF--
21+
object(Deprecated)#3 (1) {
22+
["message"]=>
23+
NULL
24+
}
25+
object(Deprecated)#2 (1) {
26+
["message"]=>
27+
string(18) "use test() instead"
28+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
#[Deprecated] attribute with wrong type
3+
--FILE--
4+
<?php
5+
6+
#[Deprecated(1234)]
7+
function test() {
8+
}
9+
10+
11+
--EXPECTF--
12+
Fatal error: Deprecated::__construct: Argument #1 ($message) must be of type string, int given in %s

Zend/zend_attributes.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ ZEND_API zend_class_entry *zend_ce_allow_dynamic_properties;
3030
ZEND_API zend_class_entry *zend_ce_sensitive_parameter;
3131
ZEND_API zend_class_entry *zend_ce_sensitive_parameter_value;
3232
ZEND_API zend_class_entry *zend_ce_override;
33+
ZEND_API zend_class_entry *zend_ce_deprecated_attribute;
3334

3435
static zend_object_handlers attributes_object_handlers_sensitive_parameter_value;
3536

@@ -80,6 +81,32 @@ static void validate_allow_dynamic_properties(
8081
scope->ce_flags |= ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES;
8182
}
8283

84+
void validate_deprecated_attribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope)
85+
{
86+
// TODO: More proper signature validation: Too many args, incorrect arg names.
87+
if (attr->argc == 1) {
88+
zval message;
89+
90+
/* As this is run in the middle of compilation, fetch the attribute value without
91+
* specifying a scope. The class is not fully linked yet, and we may seen an
92+
* inconsistent state. */
93+
if (FAILURE == zend_get_attribute_value(&message, attr, 0, NULL)) {
94+
return;
95+
}
96+
97+
if (Z_TYPE(message) != IS_STRING) {
98+
zval_ptr_dtor(&message);
99+
100+
zend_error_noreturn(E_COMPILE_ERROR,
101+
"Deprecated::__construct: Argument #1 ($message) must be of type string, %s given",
102+
zend_zval_type_name(&message)
103+
);
104+
}
105+
106+
zval_ptr_dtor(&message);
107+
}
108+
}
109+
83110
ZEND_METHOD(Attribute, __construct)
84111
{
85112
zend_long flags = ZEND_ATTRIBUTE_TARGET_ALL;
@@ -142,6 +169,22 @@ ZEND_METHOD(Override, __construct)
142169
ZEND_PARSE_PARAMETERS_NONE();
143170
}
144171

172+
ZEND_METHOD(Deprecated, __construct)
173+
{
174+
zend_string *message = NULL;
175+
176+
ZEND_PARSE_PARAMETERS_START(0, 1)
177+
Z_PARAM_OPTIONAL
178+
Z_PARAM_STR_OR_NULL(message)
179+
ZEND_PARSE_PARAMETERS_END();
180+
181+
if (message) {
182+
ZVAL_STR(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), message);
183+
} else {
184+
ZVAL_NULL(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0));
185+
}
186+
}
187+
145188
static zend_attribute *get_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset)
146189
{
147190
if (attributes) {
@@ -380,6 +423,10 @@ void zend_register_attribute_ce(void)
380423

381424
zend_ce_override = register_class_Override();
382425
zend_mark_internal_attribute(zend_ce_override);
426+
427+
zend_ce_deprecated_attribute = register_class_Deprecated();
428+
attr = zend_mark_internal_attribute(zend_ce_deprecated_attribute);
429+
attr->validator = validate_deprecated_attribute;
383430
}
384431

385432
void zend_attributes_shutdown(void)

Zend/zend_attributes.stub.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,12 @@ final class Override
7171
{
7272
public function __construct() {}
7373
}
74+
75+
/**
76+
* @strict-properties
77+
*/
78+
#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION)]
79+
final class Deprecated
80+
{
81+
public function __construct(?string $message = null) {}
82+
}

Zend/zend_attributes_arginfo.h

Lines changed: 29 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Zend/zend_compile.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7795,6 +7795,7 @@ static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel)
77957795
zend_ast *return_type_ast = decl->child[3];
77967796
bool is_method = decl->kind == ZEND_AST_METHOD;
77977797
zend_string *lcname;
7798+
zend_attribute *deprecated;
77987799

77997800
zend_class_entry *orig_class_entry = CG(active_class_entry);
78007801
zend_op_array *orig_op_array = CG(active_op_array);
@@ -7890,6 +7891,12 @@ static void zend_compile_func_decl(znode *result, zend_ast *ast, bool toplevel)
78907891
zend_compile_closure_uses(uses_ast);
78917892
}
78927893

7894+
deprecated = zend_get_attribute_str(op_array->attributes, "deprecated", sizeof("deprecated")-1);
7895+
7896+
if (deprecated && stmt_ast != NULL) {
7897+
op_array->fn_flags |= ZEND_ACC_DEPRECATED;
7898+
}
7899+
78937900
if (ast->kind == ZEND_AST_ARROW_FUNC && decl->child[2]->kind != ZEND_AST_RETURN) {
78947901
bool needs_return = true;
78957902
if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {

Zend/zend_execute.c

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "zend_observer.h"
4444
#include "zend_system_id.h"
4545
#include "zend_call_stack.h"
46+
#include "zend_attributes.h"
4647
#include "Optimizer/zend_func_info.h"
4748

4849
/* Virtual current working directory support */
@@ -1724,13 +1725,45 @@ ZEND_API ZEND_COLD void zend_wrong_string_offset_error(void)
17241725

17251726
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_function(const zend_function *fbc)
17261727
{
1727-
if (fbc->common.scope) {
1728-
zend_error(E_DEPRECATED, "Method %s::%s() is deprecated",
1729-
ZSTR_VAL(fbc->common.scope->name),
1730-
ZSTR_VAL(fbc->common.function_name)
1731-
);
1728+
zend_attribute *deprecated;
1729+
zend_string *message_suffix = NULL;
1730+
1731+
if (fbc->common.attributes != NULL) {
1732+
deprecated = zend_get_attribute_str(fbc->common.attributes, "deprecated", sizeof("deprecated")-1);
1733+
1734+
if (deprecated->argc >= 1) {
1735+
zval message;
1736+
1737+
if (FAILURE != zend_get_attribute_value(&message, deprecated, 0, fbc->common.scope)) {
1738+
message_suffix = Z_STR(message);
1739+
}
1740+
}
1741+
}
1742+
1743+
if (message_suffix != NULL) {
1744+
if (fbc->common.scope) {
1745+
zend_error(E_DEPRECATED, "Method %s::%s() is deprecated, %s",
1746+
ZSTR_VAL(fbc->common.scope->name),
1747+
ZSTR_VAL(fbc->common.function_name),
1748+
ZSTR_VAL(message_suffix)
1749+
);
1750+
} else {
1751+
zend_error(E_DEPRECATED, "Function %s() is deprecated, %s",
1752+
ZSTR_VAL(fbc->common.function_name),
1753+
ZSTR_VAL(message_suffix)
1754+
);
1755+
}
1756+
1757+
zend_string_release(message_suffix);
17321758
} else {
1733-
zend_error(E_DEPRECATED, "Function %s() is deprecated", ZSTR_VAL(fbc->common.function_name));
1759+
if (fbc->common.scope) {
1760+
zend_error(E_DEPRECATED, "Method %s::%s() is deprecated",
1761+
ZSTR_VAL(fbc->common.scope->name),
1762+
ZSTR_VAL(fbc->common.function_name)
1763+
);
1764+
} else {
1765+
zend_error(E_DEPRECATED, "Function %s() is deprecated", ZSTR_VAL(fbc->common.function_name));
1766+
}
17341767
}
17351768
}
17361769

Zend/zend_vm_def.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4048,6 +4048,10 @@ ZEND_VM_HOT_HANDLER(130, ZEND_DO_UCALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
40484048
zend_function *fbc = call->func;
40494049
zval *ret;
40504050

4051+
if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) {
4052+
zend_deprecated_function(fbc);
4053+
}
4054+
40514055
SAVE_OPLINE();
40524056
EX(call) = call->prev_execute_data;
40534057

@@ -4082,6 +4086,10 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL,OBSERVER))
40824086
ret = EX_VAR(opline->result.var);
40834087
}
40844088

4089+
if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) {
4090+
zend_deprecated_function(fbc);
4091+
}
4092+
40854093
call->prev_execute_data = execute_data;
40864094
execute_data = call;
40874095
i_init_func_execute_data(&fbc->op_array, ret, 0 EXECUTE_DATA_CC);
@@ -4180,6 +4188,10 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
41804188
ret = EX_VAR(opline->result.var);
41814189
}
41824190

4191+
if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) {
4192+
zend_deprecated_function(fbc);
4193+
}
4194+
41834195
call->prev_execute_data = execute_data;
41844196
execute_data = call;
41854197
i_init_func_execute_data(&fbc->op_array, ret, 1 EXECUTE_DATA_CC);

0 commit comments

Comments
 (0)