[php-src] master: Deprecate returning values from `__construct()` and `__destruct()` (#21982)
Tim Düsterhus via GitHub <[email protected]>
| Newsgroups | gmane.comp.php.cvs.general |
|---|---|
| Message-ID | <[email protected]> |
Author: Tim Düsterhus (TimWolla)
Committer: GitHub (web-flow)
Pusher: TimWolla
Date: 2026-06-30T10:03:55+02:00
Commit: https://github.com/php/php-src/commit/18d783719d58420fa7f333175830fcf40c916bea
Raw diff: https://github.com/php/php-src/commit/18d783719d58420fa7f333175830fcf40c916bea.diff
Deprecate returning values from `__construct()` and `__destruct()` (#21982)
* Deprecate returning values from `__construct()` and `__destruct()`
* Deprecate making `__construct()` and `__destruct()` a `Generator`
* NEWS / UPGRADING
Changed paths:
A Zend/tests/magic_methods/constructor_destructor_return.phpt
M NEWS
M UPGRADING
M Zend/tests/prop_const_expr/non_enums_catchable.phpt
M Zend/tests/traits/bug60536_001.phpt
M Zend/zend_compile.c
M ext/pdo_mysql/tests/pdo_mysql_subclass.phpt
M ext/reflection/tests/bug36434.phpt
M ext/standard/tests/array/gh16649/array_splice_uaf_original_case.phpt
M ext/zend_test/tests/zend_object_init_with_constructor.phpt
Diff:
diff --git a/NEWS b/NEWS
index 9d326431321c..667a0bc591e3 100644
--- a/NEWS
+++ b/NEWS
@@ -37,6 +37,7 @@ PHP NEWS
. Fixed bug GH-22257 (type confusion in Exception::getTraceAsString()).
(David Carlier)
. TSRM: make CG, EG, SCNG and AG compile-time offsets. (henderkes)
+ . Deprecate returning values from __construct() and __destruct(). (timwolla)
- BCMath:
. Added NUL-byte validation to BCMath functions. (jorgsowa)
diff --git a/UPGRADING b/UPGRADING
index 02aa821acecf..fa9a816f7aed 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -283,6 +283,10 @@ PHP 8.6 UPGRADE NOTES
- Core:
. Specifying a return type of array|null / ?array for __debugInfo() is now
deprecated. Specify array instead.
+ . Returning values from __construct() and __destruct() is now deprecated.
+ RFC: https://wiki.php.net/rfc/deprecate-return-value-from-construct
+ . Making __construct() and __destruct() a Generator is now deprecated.
+ RFC: https://wiki.php.net/rfc/deprecate-return-value-from-construct
- GMP
. The shift (<<, >>) and exponentiation (**) operators on GMP objects now
diff --git a/Zend/tests/magic_methods/constructor_destructor_return.phpt b/Zend/tests/magic_methods/constructor_destructor_return.phpt
new file mode 100644
index 000000000000..635548bb6f47
--- /dev/null
+++ b/Zend/tests/magic_methods/constructor_destructor_return.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Returning values from constructors and destructors is deprecated
+--FILE--
+<?php
+
+class A {
+ public function __construct() { return ''; }
+ public function __destruct() { return ''; }
+}
+
+class B {
+ public function __construct() { return $this->voidMethod(); }
+ public function __destruct() { return $this->voidMethod(); }
+
+ public function voidMethod(): void { }
+}
+
+class Gen {
+ public function __construct() { yield ''; }
+ public function __destruct() { yield ''; }
+}
+
+?>
+--EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a destructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a destructor is deprecated in %s on line %d
+
+Deprecated: Making a constructor a Generator is deprecated in %s on line %d
+
+Deprecated: Making a destructor a Generator is deprecated in %s on line %d
diff --git a/Zend/tests/prop_const_expr/non_enums_catchable.phpt b/Zend/tests/prop_const_expr/non_enums_catchable.phpt
index 6f410ac7acbe..702bc024122d 100644
--- a/Zend/tests/prop_const_expr/non_enums_catchable.phpt
+++ b/Zend/tests/prop_const_expr/non_enums_catchable.phpt
@@ -18,6 +18,7 @@ const A_prop = (new A)->{new Printer ? 'printer' : null};
?>
--EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
Printer
Fatal error: Uncaught Error: Fetching properties on non-enums in constant expressions is not allowed in %s:%d
diff --git a/Zend/tests/traits/bug60536_001.phpt b/Zend/tests/traits/bug60536_001.phpt
index a58098ca3112..712600a4b1d1 100644
--- a/Zend/tests/traits/bug60536_001.phpt
+++ b/Zend/tests/traits/bug60536_001.phpt
@@ -23,5 +23,9 @@ $a->__construct();
echo "DONE";
?>
--EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
Warning: Undefined property: Z::$x in %s on line %d
DONE
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 9f8ebad8ab29..4c1375215888 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -530,6 +530,12 @@ ZEND_API bool zend_is_compiling(void) /* {{{ */
}
/* }}} */
+static bool zend_is_constructor(const zend_string *name) /* {{{ */
+{
+ return zend_string_equals_literal_ci(name, ZEND_CONSTRUCTOR_FUNC_NAME);
+}
+/* }}} */
+
static zend_always_inline uint32_t get_temporary_variable(void) /* {{{ */
{
return (uint32_t)CG(active_op_array)->T++;
@@ -5549,12 +5555,6 @@ static void zend_compile_method_call(znode *result, zend_ast *ast, uint32_t type
}
/* }}} */
-static bool zend_is_constructor(const zend_string *name) /* {{{ */
-{
- return zend_string_equals_literal_ci(name, ZEND_CONSTRUCTOR_FUNC_NAME);
-}
-/* }}} */
-
static bool is_func_accessible(const zend_function *fbc)
{
if ((fbc->common.fn_flags & ZEND_ACC_PUBLIC) || fbc->common.scope == CG(active_class_entry)) {
@@ -5993,6 +5993,16 @@ static void zend_compile_return(const zend_ast *ast) /* {{{ */
zend_compile_expr(&expr_node, expr_ast);
}
+ if (expr_ast) {
+ if (CG(active_class_entry) != NULL) {
+ if (zend_is_constructor(CG(active_op_array)->function_name)) {
+ zend_error(E_DEPRECATED, "Returning a value from a constructor is deprecated");
+ } else if (zend_string_equals_literal_ci(CG(active_op_array)->function_name, ZEND_DESTRUCTOR_FUNC_NAME)) {
+ zend_error(E_DEPRECATED, "Returning a value from a destructor is deprecated");
+ }
+ }
+ }
+
if ((CG(active_op_array)->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK)
&& (expr_node.op_type == IS_CV || (by_ref && expr_node.op_type == IS_VAR))
&& zend_has_finally()) {
@@ -8843,6 +8853,14 @@ static zend_op_array *zend_compile_func_decl_ex(
zend_compile_params(params_ast, return_type_ast,
is_method && zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME) ? IS_STRING : 0);
if (CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) {
+ if (CG(active_class_entry) != NULL) {
+ if (zend_is_constructor(CG(active_op_array)->function_name)) {
+ zend_error(E_DEPRECATED, "Making a constructor a Generator is deprecated");
+ } else if (zend_string_equals_literal_ci(CG(active_op_array)->function_name, ZEND_DESTRUCTOR_FUNC_NAME)) {
+ zend_error(E_DEPRECATED, "Making a destructor a Generator is deprecated");
+ }
+ }
+
zend_mark_function_as_generator();
zend_emit_op(NULL, ZEND_GENERATOR_CREATE, NULL, NULL);
}
diff --git a/ext/pdo_mysql/tests/pdo_mysql_subclass.phpt b/ext/pdo_mysql/tests/pdo_mysql_subclass.phpt
index d74e348fb99a..be7eb74b5b35 100644
--- a/ext/pdo_mysql/tests/pdo_mysql_subclass.phpt
+++ b/ext/pdo_mysql/tests/pdo_mysql_subclass.phpt
@@ -76,6 +76,7 @@ $db = MySQLPDOTest::factory();
$db->exec('DROP TABLE IF EXISTS test_subclass');
?>
--EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
__construct('%S', '%S', %s)
Deprecated: Callables of the form ["MyPDO", "parent::__construct"] are deprecated in %s on line %d
diff --git a/ext/reflection/tests/bug36434.phpt b/ext/reflection/tests/bug36434.phpt
index 3f4cc70c7554..0dfa5d117b2c 100644
--- a/ext/reflection/tests/bug36434.phpt
+++ b/ext/reflection/tests/bug36434.phpt
@@ -26,6 +26,9 @@ foreach ($r->GetProperties() as $p)
}
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
bar foo
ancestor ancestor
diff --git a/ext/standard/tests/array/gh16649/array_splice_uaf_original_case.phpt b/ext/standard/tests/array/gh16649/array_splice_uaf_original_case.phpt
index 4a82d5893157..dcfa8f2e759c 100644
--- a/ext/standard/tests/array/gh16649/array_splice_uaf_original_case.phpt
+++ b/ext/standard/tests/array/gh16649/array_splice_uaf_original_case.phpt
@@ -25,5 +25,6 @@ try {
echo "Exception caught: " . $e->getMessage() . "\n";
}
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning a value from a destructor is deprecated in %s on line %d
Exception caught: Array was modified during array_splice operation
diff --git a/ext/zend_test/tests/zend_object_init_with_constructor.phpt b/ext/zend_test/tests/zend_object_init_with_constructor.phpt
index 65b111447f0b..aa3ad7ee845a 100644
--- a/ext/zend_test/tests/zend_object_init_with_constructor.phpt
+++ b/ext/zend_test/tests/zend_object_init_with_constructor.phpt
@@ -139,7 +139,14 @@ $o = zend_object_init_with_constructor("TestUserWithConstructorNoParams");
var_dump($o);
unset($o);
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
Testing impossible initializations
Error: Cannot instantiate interface _ZendTestInterface
Error: Cannot instantiate trait _ZendTestTrait
@@ -152,14 +159,14 @@ Testing param passing
ArgumentCountError: Too few arguments to function TestUserWithConstructorArgs::__construct(), 0 passed and exactly 2 expected
TypeError: TestUserWithConstructorArgs::__construct(): Argument #1 ($int_param) must be of type int, string given
Error: Unknown named parameter $unused_param
-object(TestUserWithConstructorArgs)#1 (0) {
+object(TestUserWithConstructorArgs)#%d (0) {
}
Destructor for TestUserWithConstructorArgs
Passing too many args to constructor
-object(TestUserWithConstructorArgs)#1 (0) {
+object(TestUserWithConstructorArgs)#%d (0) {
}
Destructor for TestUserWithConstructorArgs
Testing class with defined constructor and no params
-object(TestUserWithConstructorNoParams)#1 (0) {
+object(TestUserWithConstructorNoParams)#%d (0) {
}
Destructor for TestUserWithConstructorNoParams