Skip to content

Commit 4d51bfa

Browse files
[RFC] Add mb_ucfirst and mb_lcfirst functions (#13161)
1 parent 5430ecd commit 4d51bfa

File tree

6 files changed

+168
-1
lines changed

6 files changed

+168
-1
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ PHP NEWS
9090

9191
- MBString:
9292
. Added mb_trim, mb_ltrim and mb_rtrim. (Yuya Hamada)
93+
. Added mb_ucfirst and mb_lcfirst. (Yuya Hamada)
9394

9495
- MySQLnd:
9596
. Fixed bug GH-13440 (PDO quote bottleneck). (nielsdos)

UPGRADING

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,8 @@ PHP 8.4 UPGRADE NOTES
431431
- MBString:
432432
. Added mb_trim, mb_ltrim and mb_rtrim functions.
433433
RFC: https://wiki.php.net/rfc/mb_trim
434+
. Added mb_ucfirst and mb_lcfirst functions.
435+
RFC: https://wiki.php.net/rfc/mb_ucfirst
434436

435437
- Opcache:
436438
. If JIT is enabled, PHP will now exit with a fatal error on startup in case

ext/mbstring/mbstring.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2951,6 +2951,50 @@ PHP_FUNCTION(mb_strtolower)
29512951
RETURN_STR(mbstring_convert_case(PHP_UNICODE_CASE_LOWER, ZSTR_VAL(str), ZSTR_LEN(str), enc));
29522952
}
29532953

2954+
static void php_mb_ulcfirst(INTERNAL_FUNCTION_PARAMETERS, php_case_mode mode)
2955+
{
2956+
zend_string *str, *from_encoding = NULL;
2957+
2958+
ZEND_PARSE_PARAMETERS_START(1, 2)
2959+
Z_PARAM_STR(str)
2960+
Z_PARAM_OPTIONAL
2961+
Z_PARAM_STR_OR_NULL(from_encoding)
2962+
ZEND_PARSE_PARAMETERS_END();
2963+
2964+
const mbfl_encoding *enc = php_mb_get_encoding(from_encoding, 2);
2965+
if (!enc) {
2966+
RETURN_THROWS();
2967+
}
2968+
2969+
zend_string *first = mb_get_substr(str, 0, 1, enc);
2970+
zend_string *head = mbstring_convert_case(mode, ZSTR_VAL(first), ZSTR_LEN(first), enc);
2971+
2972+
if (zend_string_equals(first, head)) {
2973+
zend_string_release_ex(first, false);
2974+
zend_string_release_ex(head, false);
2975+
RETURN_STR(zend_string_copy(str));
2976+
}
2977+
2978+
zend_string *second = mb_get_substr(str, 1, MBFL_SUBSTR_UNTIL_END, enc);
2979+
zend_string *retval = zend_string_concat2(ZSTR_VAL(head), ZSTR_LEN(head), ZSTR_VAL(second), ZSTR_LEN(second));
2980+
2981+
zend_string_release_ex(first, false);
2982+
zend_string_release_ex(head, false);
2983+
zend_string_release_ex(second, false);
2984+
2985+
RETVAL_STR(retval);
2986+
}
2987+
2988+
PHP_FUNCTION(mb_ucfirst)
2989+
{
2990+
php_mb_ulcfirst(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_UNICODE_CASE_TITLE);
2991+
}
2992+
2993+
PHP_FUNCTION(mb_lcfirst)
2994+
{
2995+
php_mb_ulcfirst(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_UNICODE_CASE_LOWER);
2996+
}
2997+
29542998
typedef enum {
29552999
MB_LTRIM = 1,
29563000
MB_RTRIM = 2,

ext/mbstring/mbstring.stub.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ function mb_strtoupper(string $string, ?string $encoding = null): string {}
135135
/** @refcount 1 */
136136
function mb_strtolower(string $string, ?string $encoding = null): string {}
137137

138+
function mb_ucfirst(string $string, ?string $encoding = null): string {}
139+
140+
function mb_lcfirst(string $string, ?string $encoding = null): string {}
141+
138142
function mb_trim(string $string, string $characters = " \f\n\r\t\v\x00\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}", ?string $encoding = null): string {}
139143

140144
function mb_ltrim(string $string, string $characters = " \f\n\r\t\v\x00\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}", ?string $encoding = null): string {}

ext/mbstring/mbstring_arginfo.h

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
--TEST--
2+
mb_ucfirst(), mb_lcfirst functions tests
3+
--EXTENSIONS--
4+
mbstring
5+
--FILE--
6+
<?php
7+
mb_internal_encoding("UTF-8");
8+
9+
function test_ascii_mb_ucfirst() {
10+
for ($i = 0; $i < 128; $i++) {
11+
if ($i >= 97 && $i <= 122) { /* a to z */
12+
if (mb_ucfirst(chr($i)) !== chr($i - (97 - 65))) {
13+
echo "mb_ucfirst compare failed: " . chr($i) . "\n";
14+
}
15+
} else {
16+
if (mb_ucfirst(chr($i)) !== chr($i)) {
17+
echo "mb_ucfirst compare failed: " . chr($i) . "\n";
18+
}
19+
}
20+
}
21+
echo "Done mb_ucfirst\n";
22+
}
23+
24+
function test_ascii_mb_lcfirst() {
25+
for ($i = 0; $i < 128; $i++) {
26+
if ($i >= 65 && $i <= 90) { /* A to Z */
27+
if (mb_lcfirst(chr($i)) !== chr($i + (97 - 65))) {
28+
echo "mb_lcfirst compare failed: " . chr($i) . "\n";
29+
return;
30+
}
31+
} else {
32+
if (mb_lcfirst(chr($i)) !== chr($i)) {
33+
echo "mb_lcfirst compare failed: " . chr($i) . "\n";
34+
return;
35+
}
36+
}
37+
}
38+
echo "Done mb_lcfirst\n";
39+
}
40+
41+
echo "== Empty String ==\n";
42+
var_dump(mb_ucfirst(""));
43+
var_dump(mb_lcfirst(""));
44+
echo "== ASCII ==\n";
45+
test_ascii_mb_ucfirst();
46+
test_ascii_mb_lcfirst();
47+
echo "== mb_ucfirst ==\n";
48+
var_dump(mb_ucfirst("ab"));
49+
var_dump(mb_ucfirst("ABS"));
50+
var_dump(mb_ucfirst("đắt quá!"));
51+
var_dump(mb_ucfirst("აბგ"));
52+
var_dump(mb_ucfirst("lj"));
53+
echo "== mb_lcfirst ==\n";
54+
var_dump(mb_lcfirst("ABS"));
55+
var_dump(mb_lcfirst("Xin chào"));
56+
var_dump(mb_lcfirst("Đẹp quá!"));
57+
echo "== SJIS ==\n";
58+
var_dump(bin2hex(mb_ucfirst(mb_convert_encoding("ebi", "SJIS", "UTF-8"), "SJIS")));
59+
var_dump(bin2hex(mb_lcfirst(mb_convert_encoding("EBI", "SJIS", "UTF-8"), "SJIS")));
60+
var_dump(bin2hex(mb_ucfirst(hex2bin("8471"), "SJIS"))); /* б */
61+
var_dump(bin2hex(mb_lcfirst(hex2bin("8441"), "SJIS"))); /* Б */
62+
var_dump(bin2hex(mb_ucfirst(hex2bin("83bf"), "SJIS"))); /* α */
63+
var_dump(bin2hex(mb_lcfirst(hex2bin("839f"), "SJIS"))); /* Α */
64+
var_dump(bin2hex(mb_lcfirst(hex2bin("82a0"), "SJIS"))); /* あ */
65+
var_dump(bin2hex(mb_ucfirst(hex2bin("83bf8471"), "SJIS")));
66+
var_dump(bin2hex(mb_lcfirst(hex2bin("839f8441"), "SJIS")));
67+
echo "== EUC-JP ==\n";
68+
var_dump(bin2hex(mb_ucfirst(hex2bin("a6d8"), "EUC-JP"))); /* Ω */
69+
var_dump(bin2hex(mb_lcfirst(hex2bin("a6b8"), "EUC-JP"))); /* ω */
70+
var_dump(bin2hex(mb_ucfirst(hex2bin("a4a2a4a2"), "EUC-JP"))); /* あ */
71+
echo "== Longer strings ==\n";
72+
var_dump(mb_ucfirst("э" . str_repeat("A", 65536)) === "Э" . str_repeat("A", 65536));
73+
var_dump(mb_lcfirst("Э" . str_repeat("A", 65536)) === "э" . str_repeat("A", 65536));
74+
?>
75+
--EXPECT--
76+
== Empty String ==
77+
string(0) ""
78+
string(0) ""
79+
== ASCII ==
80+
Done mb_ucfirst
81+
Done mb_lcfirst
82+
== mb_ucfirst ==
83+
string(6) "Ab"
84+
string(9) "ABS"
85+
string(12) "Đắt quá!"
86+
string(9) "აბგ"
87+
string(2) "Lj"
88+
== mb_lcfirst ==
89+
string(9) "aBS"
90+
string(9) "xin chào"
91+
string(12) "đẹp quá!"
92+
== SJIS ==
93+
string(12) "826482828289"
94+
string(12) "828582618268"
95+
string(4) "8441"
96+
string(4) "8471"
97+
string(4) "839f"
98+
string(4) "83bf"
99+
string(4) "82a0"
100+
string(8) "839f8471"
101+
string(8) "83bf8441"
102+
== EUC-JP ==
103+
string(4) "a6b8"
104+
string(4) "a6d8"
105+
string(8) "a4a2a4a2"
106+
== Longer strings ==
107+
bool(true)
108+
bool(true)

0 commit comments

Comments
 (0)