Commit 7a1c2612c01 for php.net
commit 7a1c2612c01d9bd4d0c2d128fb5b2bf8f0a2e671
Author: tekimen <youkidearitai@gmail.com>
Date: Wed Mar 4 09:47:26 2026 +0900
[RFC] Add grapheme_strrev function (#20949)
* [RFC] Add grapheme_strrev function
Add more tests Arabic for grapheme_strrev function.
diff --git a/NEWS b/NEWS
index 75332b89a1e..4e1196fb094 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 7e47d0ba481..454a7b900f3 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -137,6 +137,10 @@ PHP 8.6 UPGRADE NOTES
. Added ReflectionProperty::isReadable() and ReflectionProperty::isWritable().
RFC: https://wiki.php.net/rfc/isreadable-iswriteable
+- 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.
diff --git a/ext/intl/grapheme/grapheme_string.cpp b/ext/intl/grapheme/grapheme_string.cpp
index 6dd5a002a65..36c0cc0f732 100644
--- a/ext/intl/grapheme/grapheme_string.cpp
+++ b/ext/intl/grapheme/grapheme_string.cpp
@@ -1135,4 +1135,63 @@ out_ustring1:
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);
+ if (pos == UBRK_DONE) {
+ goto ubrk_end;
+ }
+
+ 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;
+ }
+ubrk_end:
+ 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 9a8f036865c..4bcb8587f78 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 e00e51420d4..81160349980 100644
Binary files a/ext/intl/php_intl_arginfo.h and b/ext/intl/php_intl_arginfo.h differ
diff --git a/ext/intl/tests/grapheme_strrev.phpt b/ext/intl/tests/grapheme_strrev.phpt
new file mode 100644
index 00000000000..dff84fbba8e
Binary files /dev/null and b/ext/intl/tests/grapheme_strrev.phpt differ