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