From 80e6b92d8cd6ed2451505618d5492543bf35a52c Mon Sep 17 00:00:00 2001 From: Yuya Hamada Date: Tue, 13 Jan 2026 17:05:01 +0900 Subject: [PATCH 1/6] [RFC] Add grapheme_strrev function Add more tests Arabic for grapheme_strrev function. --- ext/intl/grapheme/grapheme_string.cpp | 56 ++++++++++++++++++++++++++ ext/intl/php_intl.stub.php | 2 + ext/intl/php_intl_arginfo.h | 8 +++- ext/intl/tests/grapheme_strrev.phpt | Bin 0 -> 805 bytes 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 ext/intl/tests/grapheme_strrev.phpt diff --git a/ext/intl/grapheme/grapheme_string.cpp b/ext/intl/grapheme/grapheme_string.cpp index 6dd5a002a65b8..ababb0cc14851 100644 --- a/ext/intl/grapheme/grapheme_string.cpp +++ b/ext/intl/grapheme/grapheme_string.cpp @@ -1135,4 +1135,60 @@ U_CFUNC PHP_FUNCTION(grapheme_levenshtein) efree(ustring1); } +U_CFUNC PHP_FUNCTION(grapheme_strrev) +{ + zend_string *string; + UText *ut = nullptr; + UErrorCode ustatus = U_ZERO_ERROR; + UBreakIterator *bi; + char *pstr, *end, *p; + zend_string *ret; + int32_t pos = 0, current = 0, end_len = 0; + unsigned char u_break_iterator_buffer[U_BRK_SAFECLONE_BUFFERSIZE]; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(string) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(string) == 0) { + RETURN_EMPTY_STRING(); + } + + pstr = ZSTR_VAL(string); + ut = utext_openUTF8(ut, pstr, ZSTR_LEN(string), &ustatus); + + if (U_FAILURE(ustatus)) { + intl_error_set_code(nullptr, ustatus); + intl_error_set_custom_msg(nullptr, "Error opening UTF-8 text"); + + RETVAL_FALSE; + goto close; + } + + bi = nullptr; + ustatus = U_ZERO_ERROR; + + bi = grapheme_get_break_iterator((void*)u_break_iterator_buffer, &ustatus ); + ret = zend_string_alloc(ZSTR_LEN(string), 0); + p = ZSTR_VAL(ret); + + ubrk_setUText(bi, ut, &ustatus); + pos = ubrk_last(bi); + + current = ZSTR_LEN(string); + for (end = pstr; pos != UBRK_DONE; ) { + pos = ubrk_previous(bi); + end_len = current - pos; + for (int32_t j = 0; j < end_len; j++) { + *p++ = *(pstr + pos + j); + } + current = pos; + } + + RETVAL_NEW_STR(ret); + ubrk_close(bi); +close: + utext_close(ut); +} + /* }}} */ diff --git a/ext/intl/php_intl.stub.php b/ext/intl/php_intl.stub.php index 9a8f036865cd5..4bcb8587f786b 100644 --- a/ext/intl/php_intl.stub.php +++ b/ext/intl/php_intl.stub.php @@ -445,6 +445,8 @@ function grapheme_str_split(string $string, int $length = 1): array|false {} function grapheme_levenshtein(string $string1, string $string2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1, string $locale = ""): int|false {} +function grapheme_strrev(string $string): string|false {} + /** @param int $next */ function grapheme_extract(string $haystack, int $size, int $type = GRAPHEME_EXTR_COUNT, int $offset = 0, &$next = null): string|false {} diff --git a/ext/intl/php_intl_arginfo.h b/ext/intl/php_intl_arginfo.h index e00e51420d46e..81160349980cd 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: d9e331c3a1ae46f8eae07ef0d39cb9990e74a0d1 */ + * Stub hash: c52fd0def2530be628beedbbcdcfecdcb07449a8 */ 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") @@ -501,6 +501,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_grapheme_levenshtein, 0, 2, MAY_ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, locale, IS_STRING, 0, "\"\"") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_grapheme_strrev, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_grapheme_extract, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, haystack, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, size, IS_LONG, 0) @@ -922,6 +926,7 @@ ZEND_FUNCTION(grapheme_strstr); ZEND_FUNCTION(grapheme_stristr); ZEND_FUNCTION(grapheme_str_split); ZEND_FUNCTION(grapheme_levenshtein); +ZEND_FUNCTION(grapheme_strrev); ZEND_FUNCTION(grapheme_extract); ZEND_FUNCTION(idn_to_ascii); ZEND_FUNCTION(idn_to_utf8); @@ -1113,6 +1118,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(grapheme_stristr, arginfo_grapheme_stristr) ZEND_FE(grapheme_str_split, arginfo_grapheme_str_split) ZEND_FE(grapheme_levenshtein, arginfo_grapheme_levenshtein) + ZEND_FE(grapheme_strrev, arginfo_grapheme_strrev) ZEND_FE(grapheme_extract, arginfo_grapheme_extract) ZEND_FE(idn_to_ascii, arginfo_idn_to_ascii) ZEND_FE(idn_to_utf8, arginfo_idn_to_utf8) diff --git a/ext/intl/tests/grapheme_strrev.phpt b/ext/intl/tests/grapheme_strrev.phpt new file mode 100644 index 0000000000000000000000000000000000000000..b43236b16e088447acfea711542813ab0a1c1c83 GIT binary patch literal 805 zcma))F>As=7=}CFulNXp2^euttwTvcD7K(Op%%G>vk_tpmrJRLAO-0lUAhT^V_Z78 zmHv`swRP`L=v@+1OM{r@^1b)HFL&Q_s@l?| z52>nt)6yHw`cpL{;O3I|_)HQpfF z7R%fa$G7eFW2IsY84*^(hK8tZ+jnve(KK9#Z{aGMg@G`HK{SJK#ieyL%^XU4#qNL- zY+5oDlDlFr`2z)#x%`<;xeC&b0hqS$F+zbyK*~b}7gOAaQJi*Lxf_F|Wpl|!%wu*b zDM@)}P@oJ_+euyH*h>lXsgn`X&=?_NS_8-!YN&P=)4$fw_ut8(Gje#q$z^ZqP;Wj- d$j5k8qQH70n1TR$B4{4v+oua*5Mz$Xq92=#C;k8c literal 0 HcmV?d00001 From 63fac4ef1414561595d3a6a7be6e1a42f36a2358 Mon Sep 17 00:00:00 2001 From: Yuya Hamada Date: Fri, 20 Feb 2026 15:22:31 +0900 Subject: [PATCH 2/6] Add NEWS and UPGRADING --- NEWS | 1 + UPGRADING | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/NEWS b/NEWS index 3ea4407433664..dfbc3b9365ca1 100644 --- a/NEWS +++ b/NEWS @@ -37,6 +37,7 @@ PHP NEWS (BogdanUngureanu) . Fixed bug GH-20426 (Spoofchecker::setRestrictionLevel() error message suggests missing constants). (DanielEScherzer) + . Added grapheme_strrev (Yuya Hamada) - JSON: . Enriched JSON last error / exception message with error location. diff --git a/UPGRADING b/UPGRADING index 3df40b98a33d2..3e2a628bb9b37 100644 --- a/UPGRADING +++ b/UPGRADING @@ -118,6 +118,10 @@ PHP 8.6 UPGRADE NOTES - Reflection: . ReflectionConstant::inNamespace() +- Intl: + . `grapheme_strrev()` returns strrev for grapheme cluster unit. + RFC: https://wiki.php.net/rfc/grapheme_strrev + - Standard: . `clamp()` returns the given value if in range, else return the nearest bound. From e90e972eb69e71783a53c5da46f5f4c967375f16 Mon Sep 17 00:00:00 2001 From: Yuya Hamada Date: Mon, 2 Mar 2026 00:33:10 +0900 Subject: [PATCH 3/6] Add test for empty string --- ext/intl/grapheme/grapheme_string.cpp | 3 +++ ext/intl/tests/grapheme_strrev.phpt | Bin 805 -> 866 bytes 2 files changed, 3 insertions(+) diff --git a/ext/intl/grapheme/grapheme_string.cpp b/ext/intl/grapheme/grapheme_string.cpp index ababb0cc14851..76f6be6b121a5 100644 --- a/ext/intl/grapheme/grapheme_string.cpp +++ b/ext/intl/grapheme/grapheme_string.cpp @@ -1174,6 +1174,9 @@ U_CFUNC PHP_FUNCTION(grapheme_strrev) ubrk_setUText(bi, ut, &ustatus); pos = ubrk_last(bi); + if (pos == UBRK_DONE) { + goto close; + } current = ZSTR_LEN(string); for (end = pstr; pos != UBRK_DONE; ) { diff --git a/ext/intl/tests/grapheme_strrev.phpt b/ext/intl/tests/grapheme_strrev.phpt index b43236b16e088447acfea711542813ab0a1c1c83..dff84fbba8e97cdee5af5c0c3ce346df018afd14 100644 GIT binary patch delta 36 ocmZ3=_K0mmFr%OX5F{2AC01%EC@JXx@n#>!n~akUnN%3L0K9(*GXMYp delta 12 TcmaFFwv=r{FyrQI#!HL Date: Mon, 2 Mar 2026 00:38:12 +0900 Subject: [PATCH 4/6] Fix empty string --- ext/intl/grapheme/grapheme_string.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/intl/grapheme/grapheme_string.cpp b/ext/intl/grapheme/grapheme_string.cpp index 76f6be6b121a5..c93f8598216c1 100644 --- a/ext/intl/grapheme/grapheme_string.cpp +++ b/ext/intl/grapheme/grapheme_string.cpp @@ -1175,7 +1175,8 @@ U_CFUNC PHP_FUNCTION(grapheme_strrev) ubrk_setUText(bi, ut, &ustatus); pos = ubrk_last(bi); if (pos == UBRK_DONE) { - goto close; + RETVAL_EMPTY_STRING(); + goto ubrk_end; } current = ZSTR_LEN(string); @@ -1189,6 +1190,7 @@ U_CFUNC PHP_FUNCTION(grapheme_strrev) } RETVAL_NEW_STR(ret); +ubrk_end: ubrk_close(bi); close: utext_close(ut); From c2c28fd0757f05bb11620866a9feca7a868fe95f Mon Sep 17 00:00:00 2001 From: Yuya Hamada Date: Mon, 2 Mar 2026 01:29:14 +0900 Subject: [PATCH 5/6] Fix a bit --- ext/intl/grapheme/grapheme_string.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/intl/grapheme/grapheme_string.cpp b/ext/intl/grapheme/grapheme_string.cpp index c93f8598216c1..ef99a7b2ca0a9 100644 --- a/ext/intl/grapheme/grapheme_string.cpp +++ b/ext/intl/grapheme/grapheme_string.cpp @@ -1188,9 +1188,8 @@ U_CFUNC PHP_FUNCTION(grapheme_strrev) } current = pos; } - - RETVAL_NEW_STR(ret); ubrk_end: + RETVAL_NEW_STR(ret); ubrk_close(bi); close: utext_close(ut); From bd491e92b39531449561d7c1292d0fc6e13390d2 Mon Sep 17 00:00:00 2001 From: Yuya Hamada Date: Mon, 2 Mar 2026 15:42:53 +0900 Subject: [PATCH 6/6] Remove RETVAL_EMPTY_STRING --- ext/intl/grapheme/grapheme_string.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/intl/grapheme/grapheme_string.cpp b/ext/intl/grapheme/grapheme_string.cpp index ef99a7b2ca0a9..36c0cc0f732c8 100644 --- a/ext/intl/grapheme/grapheme_string.cpp +++ b/ext/intl/grapheme/grapheme_string.cpp @@ -1175,7 +1175,6 @@ U_CFUNC PHP_FUNCTION(grapheme_strrev) ubrk_setUText(bi, ut, &ustatus); pos = ubrk_last(bi); if (pos == UBRK_DONE) { - RETVAL_EMPTY_STRING(); goto ubrk_end; }