From 0c1d809a75d05963511eaee8057f6147051dde30 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Tue, 30 Jun 2026 14:52:10 +0800 Subject: [PATCH 1/2] Add Locale::getDisplayKeyword() and Locale::getDisplayKeywordValue() (#22264) implement RFC: https://wiki.php.net/rfc/getdisplaykeyword_and_getdisplaykeywordvalue --- NEWS | 3 + UPGRADING | 4 + ext/intl/locale/locale.stub.php | 10 ++ ext/intl/locale/locale_arginfo.h | 17 ++- ext/intl/locale/locale_methods.cpp | 117 ++++++++++++++++++ ext/intl/php_intl.stub.php | 4 + ext/intl/php_intl_arginfo.h | 17 ++- ext/intl/tests/locale/bug74993.phpt | 19 +++ .../tests/locale_get_display_keyword.phpt | 40 ++++++ .../locale_get_display_keyword_error.phpt | 20 +++ ...locale_get_display_keyword_null_bytes.phpt | 40 ++++++ ext/intl/tests/ut_common.inc | 8 ++ 12 files changed, 297 insertions(+), 2 deletions(-) create mode 100644 ext/intl/tests/locale_get_display_keyword.phpt create mode 100644 ext/intl/tests/locale_get_display_keyword_error.phpt create mode 100644 ext/intl/tests/locale_get_display_keyword_null_bytes.phpt diff --git a/NEWS b/NEWS index 308ebe4d8f2e..9d326431321c 100644 --- a/NEWS +++ b/NEWS @@ -88,6 +88,9 @@ PHP NEWS - Intl: . Fixed malformed ResourceBundle::get() error message when fallback is disabled. (Weilin Du) + . Added Locale::getDisplayKeyword() and Locale::getDisplayKeywordValue(), + with the alias of locale_get_display_keyword() and + locale_get_display_keyword_value() respectively. (Weilin Du) . Fix incorrect argument positions for invalid start/end arguments in transliterator_transliterate(). (Weilin Du) . Fixed IntlTimeZone::getDisplayName() to synchronize object error state diff --git a/UPGRADING b/UPGRADING index 1c75894512f9..02aa821acecf 100644 --- a/UPGRADING +++ b/UPGRADING @@ -208,6 +208,10 @@ PHP 8.6 UPGRADE NOTES . finfo_file() now works with remote streams. - Intl: + . Added Locale::getDisplayKeyword() and Locale::getDisplayKeywordValue(), + with the alias of locale_get_display_keyword() and + locale_get_display_keyword_value() respectively. + RFC: https://wiki.php.net/rfc/getdisplaykeyword_and_getdisplaykeywordvalue . Added IntlNumberRangeFormatter class to format an interval of two numbers with a given skeleton, locale, IntlNumberRangeFormatter::COLLAPSE_AUTO, IntlNumberRangeFormatter::COLLAPSE_NONE, diff --git a/ext/intl/locale/locale.stub.php b/ext/intl/locale/locale.stub.php index 03813ecaf194..fc012768de9f 100644 --- a/ext/intl/locale/locale.stub.php +++ b/ext/intl/locale/locale.stub.php @@ -92,6 +92,16 @@ public static function getDisplayLanguage(string $locale, ?string $displayLocale */ public static function getDisplayVariant(string $locale, ?string $displayLocale = null): string|false {} + /** + * @alias locale_get_display_keyword + */ + public static function getDisplayKeyword(string $keyword, ?string $displayLocale = null): string|false {} + + /** + * @alias locale_get_display_keyword_value + */ + public static function getDisplayKeywordValue(string $locale, string $keyword, ?string $displayLocale = null): string|false {} + /** * @tentative-return-type * @alias locale_compose diff --git a/ext/intl/locale/locale_arginfo.h b/ext/intl/locale/locale_arginfo.h index 1d6e6683e21a..a01f583b0451 100644 --- a/ext/intl/locale/locale_arginfo.h +++ b/ext/intl/locale/locale_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit locale.stub.php instead. - * Stub hash: ff1f75bd34a52f57210734e2f5e29efb87566137 */ + * Stub hash: 43dbc3aa294044e9cef923a5f39249dae86b67f0 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Locale_getDefault, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -33,6 +33,17 @@ ZEND_END_ARG_INFO() #define arginfo_class_Locale_getDisplayVariant arginfo_class_Locale_getDisplayScript +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_Locale_getDisplayKeyword, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, keyword, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, displayLocale, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_Locale_getDisplayKeywordValue, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, locale, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, keyword, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, displayLocale, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_Locale_composeLocale, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, subtags, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -83,6 +94,8 @@ ZEND_FUNCTION(locale_get_display_region); ZEND_FUNCTION(locale_get_display_name); ZEND_FUNCTION(locale_get_display_language); ZEND_FUNCTION(locale_get_display_variant); +ZEND_FUNCTION(locale_get_display_keyword); +ZEND_FUNCTION(locale_get_display_keyword_value); ZEND_FUNCTION(locale_compose); ZEND_FUNCTION(locale_parse); ZEND_FUNCTION(locale_get_all_variants); @@ -106,6 +119,8 @@ static const zend_function_entry class_Locale_methods[] = { ZEND_RAW_FENTRY("getDisplayName", zif_locale_get_display_name, arginfo_class_Locale_getDisplayName, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("getDisplayLanguage", zif_locale_get_display_language, arginfo_class_Locale_getDisplayLanguage, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("getDisplayVariant", zif_locale_get_display_variant, arginfo_class_Locale_getDisplayVariant, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) + ZEND_RAW_FENTRY("getDisplayKeyword", zif_locale_get_display_keyword, arginfo_class_Locale_getDisplayKeyword, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) + ZEND_RAW_FENTRY("getDisplayKeywordValue", zif_locale_get_display_keyword_value, arginfo_class_Locale_getDisplayKeywordValue, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("composeLocale", zif_locale_compose, arginfo_class_Locale_composeLocale, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("parseLocale", zif_locale_parse, arginfo_class_Locale_parseLocale, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("getAllVariants", zif_locale_get_all_variants, arginfo_class_Locale_getAllVariants, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) diff --git a/ext/intl/locale/locale_methods.cpp b/ext/intl/locale/locale_methods.cpp index 14fa2a8f6bf5..0733ffc8abb7 100644 --- a/ext/intl/locale/locale_methods.cpp +++ b/ext/intl/locale/locale_methods.cpp @@ -44,6 +44,8 @@ ZEND_EXTERN_MODULE_GLOBALS( intl ) #define EXTLANG_PREFIX "a" #define PRIVATE_PREFIX "x" #define DISP_NAME "name" +#define DISP_KEYWORD "keyword" +#define DISP_KEYWORD_VALUE "keyword_value" #define MAX_NO_VARIANT 15 #define MAX_NO_EXTLANG 3 @@ -671,6 +673,107 @@ static void get_icu_disp_value_src_php( const char* tag_name, INTERNAL_FUNCTION_ } /* }}} */ +/* {{{ + * common code shared by display keyword functions to get the value from ICU + }}} */ +static void get_icu_disp_keyword_value_src_php(const char* tag_name, INTERNAL_FUNCTION_PARAMETERS) +{ + char* loc_name = NULL; + size_t loc_name_len = 0; + char* keyword_name = NULL; + size_t keyword_name_len = 0; + char* disp_loc_name = NULL; + size_t disp_loc_name_len = 0; + int free_loc_name = 0; + + UChar* disp_name = NULL; + int32_t disp_name_len = 0; + int32_t buflen = 512; + UErrorCode status = U_ZERO_ERROR; + + zend_string* u8str; + char* msg = NULL; + + intl_error_reset( NULL ); + + if (strcmp(tag_name, DISP_KEYWORD) == 0) { + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_PATH(keyword_name, keyword_name_len) + Z_PARAM_OPTIONAL + Z_PARAM_PATH_OR_NULL(disp_loc_name, disp_loc_name_len) + ZEND_PARSE_PARAMETERS_END(); + } else { + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_PATH(loc_name, loc_name_len) + Z_PARAM_PATH(keyword_name, keyword_name_len) + Z_PARAM_OPTIONAL + Z_PARAM_PATH_OR_NULL(disp_loc_name, disp_loc_name_len) + ZEND_PARSE_PARAMETERS_END(); + + if (loc_name_len > ULOC_FULLNAME_CAPACITY) { + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "name too long"); + RETURN_FALSE; + } + + if (loc_name_len == 0) { + loc_name = (char *)intl_locale_get_default(); + } + } + + if (!disp_loc_name) { + disp_loc_name = estrdup(intl_locale_get_default()); + free_loc_name = 1; + } + + do { + disp_name = reinterpret_cast(erealloc(disp_name, buflen * sizeof(UChar))); + disp_name_len = buflen; + + if (strcmp(tag_name, DISP_KEYWORD) == 0) { + buflen = uloc_getDisplayKeyword(keyword_name, disp_loc_name, disp_name, disp_name_len, &status); + } else { + buflen = uloc_getDisplayKeywordValue(loc_name, keyword_name, disp_loc_name, disp_name, disp_name_len, &status); + } + + /* U_STRING_NOT_TERMINATED_WARNING is admissible here; don't look for it */ + if (U_FAILURE(status)) { + if (status == U_BUFFER_OVERFLOW_ERROR) { + status = U_ZERO_ERROR; + continue; + } + + spprintf(&msg, 0, "unable to get locale %s", tag_name); + intl_error_set( NULL, status, msg); + efree(msg); + if (disp_name) { + efree(disp_name); + } + if (free_loc_name) { + efree((void *)disp_loc_name); + disp_loc_name = NULL; + } + RETURN_FALSE; + } + } while (buflen > disp_name_len); + + if (free_loc_name) { + efree((void *)disp_loc_name); + disp_loc_name = NULL; + } + + u8str = intl_convert_utf16_to_utf8(disp_name, buflen, &status); + efree(disp_name); + if (!u8str) { + spprintf(&msg, 0, "error converting display name for %s to UTF-8", tag_name); + intl_error_set( NULL, status, msg); + efree(msg); + RETURN_FALSE; + } + + RETVAL_NEW_STR(u8str); +} +/* }}} */ + /* {{{ gets the name for the $locale in $in_locale or default_locale */ U_CFUNC PHP_FUNCTION(locale_get_display_name) { @@ -711,6 +814,20 @@ U_CFUNC PHP_FUNCTION(locale_get_display_variant) { get_icu_disp_value_src_php( LOC_VARIANT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU ); } +/* }}} */ + +/* {{{ gets the keyword display label in $in_locale or default_locale */ +U_CFUNC PHP_FUNCTION(locale_get_display_keyword) +{ + get_icu_disp_keyword_value_src_php(DISP_KEYWORD, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ gets the keyword value display label in $in_locale or default_locale */ +U_CFUNC PHP_FUNCTION(locale_get_display_keyword_value) +{ + get_icu_disp_keyword_value_src_php(DISP_KEYWORD_VALUE, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} /* }}} */ /* {{{ return an associative array containing keyword-value diff --git a/ext/intl/php_intl.stub.php b/ext/intl/php_intl.stub.php index 4bcb8587f786..1e6c5cacf203 100644 --- a/ext/intl/php_intl.stub.php +++ b/ext/intl/php_intl.stub.php @@ -487,6 +487,10 @@ function locale_get_display_language(string $locale, ?string $displayLocale = nu function locale_get_display_variant(string $locale, ?string $displayLocale = null): string|false {} +function locale_get_display_keyword(string $keyword, ?string $displayLocale = null): string|false {} + +function locale_get_display_keyword_value(string $locale, string $keyword, ?string $displayLocale = null): string|false {} + function locale_compose(array $subtags): string|false {} function locale_parse(string $locale): ?array {} diff --git a/ext/intl/php_intl_arginfo.h b/ext/intl/php_intl_arginfo.h index 81160349980c..00de5986f1ef 100644 --- a/ext/intl/php_intl_arginfo.h +++ b/ext/intl/php_intl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit php_intl.stub.php instead. - * Stub hash: c52fd0def2530be628beedbbcdcfecdcb07449a8 */ + * Stub hash: f94e7c9cc372878f1f8bd0e948092ea72076e687 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_intlcal_create_instance, 0, 0, IntlCalendar, 1) ZEND_ARG_OBJ_TYPE_MASK(0, timezone, IntlTimeZone|DateTimeZone, MAY_BE_STRING|MAY_BE_NULL, "null") @@ -553,6 +553,17 @@ ZEND_END_ARG_INFO() #define arginfo_locale_get_display_variant arginfo_locale_get_display_script +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_locale_get_display_keyword, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, keyword, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, displayLocale, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_locale_get_display_keyword_value, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, locale, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, keyword, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, displayLocale, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_locale_compose, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, subtags, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -941,6 +952,8 @@ ZEND_FUNCTION(locale_get_display_region); ZEND_FUNCTION(locale_get_display_name); ZEND_FUNCTION(locale_get_display_language); ZEND_FUNCTION(locale_get_display_variant); +ZEND_FUNCTION(locale_get_display_keyword); +ZEND_FUNCTION(locale_get_display_keyword_value); ZEND_FUNCTION(locale_compose); ZEND_FUNCTION(locale_parse); ZEND_FUNCTION(locale_get_all_variants); @@ -1133,6 +1146,8 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(locale_get_display_name, arginfo_locale_get_display_name) ZEND_FE(locale_get_display_language, arginfo_locale_get_display_language) ZEND_FE(locale_get_display_variant, arginfo_locale_get_display_variant) + ZEND_FE(locale_get_display_keyword, arginfo_locale_get_display_keyword) + ZEND_FE(locale_get_display_keyword_value, arginfo_locale_get_display_keyword_value) ZEND_FE(locale_compose, arginfo_locale_compose) ZEND_FE(locale_parse, arginfo_locale_parse) ZEND_FE(locale_get_all_variants, arginfo_locale_get_all_variants) diff --git a/ext/intl/tests/locale/bug74993.phpt b/ext/intl/tests/locale/bug74993.phpt index 1d0a1a97b75c..7bcf931c4e2b 100644 --- a/ext/intl/tests/locale/bug74993.phpt +++ b/ext/intl/tests/locale/bug74993.phpt @@ -5,6 +5,8 @@ intl --FILE-- --EXPECT-- +Function [ function locale_get_display_keyword ] { + + - Parameters [2] { + Parameter #0 [ string $keyword ] + Parameter #1 [ ?string $displayLocale = null ] + } + - Return [ string|false ] +} +Function [ function locale_get_display_keyword_value ] { + + - Parameters [3] { + Parameter #0 [ string $locale ] + Parameter #1 [ string $keyword ] + Parameter #2 [ ?string $displayLocale = null ] + } + - Return [ string|false ] +} Function [ function locale_get_display_language ] { - Parameters [2] { diff --git a/ext/intl/tests/locale_get_display_keyword.phpt b/ext/intl/tests/locale_get_display_keyword.phpt new file mode 100644 index 000000000000..9d53d7a02586 --- /dev/null +++ b/ext/intl/tests/locale_get_display_keyword.phpt @@ -0,0 +1,40 @@ +--TEST-- +locale_get_display_keyword() basic +--EXTENSIONS-- +intl +--FILE-- + +--EXPECTREGEX-- +string\([1-9][0-9]*\) "[A-Za-z ]*Calendar[A-Za-z ]*" +bool\(true\) +string\([1-9][0-9]*\) "[A-Za-z ]*Gregorian[A-Za-z ]*" +bool\(true\) +string\([1-9][0-9]*\) "[A-Za-z ]*Phonebook[A-Za-z ]*" +string\([1-9][0-9]*\) "[A-Za-z ]*Calendar[A-Za-z ]*" +bool\(true\) +string\([1-9][0-9]*\) "[A-Za-z ]*Gregorian[A-Za-z ]*" +bool\(true\) +string\([1-9][0-9]*\) "[A-Za-z ]*Phonebook[A-Za-z ]*" diff --git a/ext/intl/tests/locale_get_display_keyword_error.phpt b/ext/intl/tests/locale_get_display_keyword_error.phpt new file mode 100644 index 000000000000..6f489ccfe8fa --- /dev/null +++ b/ext/intl/tests/locale_get_display_keyword_error.phpt @@ -0,0 +1,20 @@ +--TEST-- +locale_get_display_keyword_value() error path +--EXTENSIONS-- +intl +--FILE-- + +--EXPECT-- +bool(false) +string(73) "Locale::getDisplayKeywordValue(): name too long: U_ILLEGAL_ARGUMENT_ERROR" +bool(false) +string(75) "locale_get_display_keyword_value(): name too long: U_ILLEGAL_ARGUMENT_ERROR" diff --git a/ext/intl/tests/locale_get_display_keyword_null_bytes.phpt b/ext/intl/tests/locale_get_display_keyword_null_bytes.phpt new file mode 100644 index 000000000000..fe83ac1b731f --- /dev/null +++ b/ext/intl/tests/locale_get_display_keyword_null_bytes.phpt @@ -0,0 +1,40 @@ +--TEST-- +locale_get_display_keyword() throwing null bytes exceptions. +--EXTENSIONS-- +intl +--FILE-- + ut_loc_get_display_keyword("cur\0rency", "fr"), + fn() => ut_loc_get_display_keyword("currency", "f\0r"), + fn() => ut_loc_get_display_keyword_value("de_DE@calendar=gregorian\0", "calendar", "en"), + fn() => ut_loc_get_display_keyword_value("de_DE@calendar=gregorian", "cal\0endar", "en"), + fn() => ut_loc_get_display_keyword_value("de_DE@calendar=gregorian", "calendar", "e\0n"), + ]; + + foreach ($calls as $call) { + try { + $call(); + } catch (\ValueError $e) { + echo $e->getMessage(), PHP_EOL; + } + } +} + +include_once 'ut_common.inc'; +ut_run(); +?> +--EXPECT-- +Locale::getDisplayKeyword(): Argument #1 ($keyword) must not contain any null bytes +Locale::getDisplayKeyword(): Argument #2 ($displayLocale) must not contain any null bytes +Locale::getDisplayKeywordValue(): Argument #1 ($locale) must not contain any null bytes +Locale::getDisplayKeywordValue(): Argument #2 ($keyword) must not contain any null bytes +Locale::getDisplayKeywordValue(): Argument #3 ($displayLocale) must not contain any null bytes +locale_get_display_keyword(): Argument #1 ($keyword) must not contain any null bytes +locale_get_display_keyword(): Argument #2 ($displayLocale) must not contain any null bytes +locale_get_display_keyword_value(): Argument #1 ($locale) must not contain any null bytes +locale_get_display_keyword_value(): Argument #2 ($keyword) must not contain any null bytes +locale_get_display_keyword_value(): Argument #3 ($displayLocale) must not contain any null bytes diff --git a/ext/intl/tests/ut_common.inc b/ext/intl/tests/ut_common.inc index fdc013dea41c..26f33f75f7fe 100644 --- a/ext/intl/tests/ut_common.inc +++ b/ext/intl/tests/ut_common.inc @@ -272,6 +272,14 @@ function ut_loc_get_display_variant( $locale , $dispLocale ) { return $GLOBALS['oo-mode'] ? Locale::getDisplayVariant( $locale , $dispLocale ) : locale_get_display_variant( $locale, $dispLocale ); } +function ut_loc_get_display_keyword( $keyword , $dispLocale ) +{ + return $GLOBALS['oo-mode'] ? Locale::getDisplayKeyword( $keyword , $dispLocale ) : locale_get_display_keyword( $keyword, $dispLocale ); +} +function ut_loc_get_display_keyword_value( $locale , $keyword , $dispLocale ) +{ + return $GLOBALS['oo-mode'] ? Locale::getDisplayKeywordValue( $locale , $keyword , $dispLocale ) : locale_get_display_keyword_value( $locale, $keyword, $dispLocale ); +} function ut_loc_locale_compose( $loc_parts_arr ) { return $GLOBALS['oo-mode'] ? Locale::composeLocale( $loc_parts_arr ) : locale_compose( $loc_parts_arr ); From 18d783719d58420fa7f333175830fcf40c916bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Tue, 30 Jun 2026 10:03:55 +0200 Subject: [PATCH 2/2] Deprecate returning values from `__construct()` and `__destruct()` (#21982) * Deprecate returning values from `__construct()` and `__destruct()` * Deprecate making `__construct()` and `__destruct()` a `Generator` * NEWS / UPGRADING --- NEWS | 1 + UPGRADING | 4 +++ .../constructor_destructor_return.phpt | 35 +++++++++++++++++++ .../prop_const_expr/non_enums_catchable.phpt | 1 + Zend/tests/traits/bug60536_001.phpt | 4 +++ Zend/zend_compile.c | 30 ++++++++++++---- ext/pdo_mysql/tests/pdo_mysql_subclass.phpt | 1 + ext/reflection/tests/bug36434.phpt | 5 ++- .../array_splice_uaf_original_case.phpt | 3 +- .../zend_object_init_with_constructor.phpt | 15 +++++--- 10 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 Zend/tests/magic_methods/constructor_destructor_return.phpt 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-- +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