diff --git a/README.md b/README.md index c5485051a..370956e57 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Polyfills are provided for: - the `str_increment` and `str_decrement` functions introduced in PHP 8.3; - the `Date*Exception/Error` classes introduced in PHP 8.3; - the `SQLite3Exception` class introduced in PHP 8.3; +- the `mb_ucfirst` and `mb_lcfirst` functions introduced in PHP 8.4; It is strongly recommended to upgrade your PHP version and/or install the missing extensions whenever possible. This polyfill should be used only when there is no diff --git a/src/Mbstring/Mbstring.php b/src/Mbstring/Mbstring.php index 04b35cb85..bccd83180 100644 --- a/src/Mbstring/Mbstring.php +++ b/src/Mbstring/Mbstring.php @@ -48,6 +48,8 @@ * - mb_strstr - Finds first occurrence of a string within another * - mb_strwidth - Return width of string * - mb_substr_count - Count the number of substring occurrences + * - mb_ucfirst - Make a string's first character uppercase + * - mb_lcfirst - Make a string's first character lowercase * * Not implemented: * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) @@ -871,6 +873,16 @@ public static function mb_str_pad(string $string, int $length, string $pad_strin } } + public static function mb_ucfirst(string $string, ?string $encoding = null): string + { + return \mb_convert_case(\mb_substr($string, 0, 1, $encoding), \MB_CASE_TITLE, $encoding) . \mb_substr($string, 1, null, $encoding); + } + + public static function mb_lcfirst(string $string, ?string $encoding = null): string + { + return \mb_strtolower(\mb_substr($string, 0, 1, $encoding), $encoding) . \mb_substr($string, 1, null, $encoding); + } + private static function getSubpart($pos, $part, $haystack, $encoding) { if (false === $pos) { diff --git a/src/Mbstring/bootstrap.php b/src/Mbstring/bootstrap.php index ecf1a0352..ef8a97f8a 100644 --- a/src/Mbstring/bootstrap.php +++ b/src/Mbstring/bootstrap.php @@ -136,6 +136,13 @@ function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstrin function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } } +if (!function_exists('mb_ucfirst')) { + function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); } +} +if (!function_exists('mb_lcfirst')) { + function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); } +} + if (extension_loaded('mbstring')) { return; } diff --git a/src/Mbstring/bootstrap80.php b/src/Mbstring/bootstrap80.php index 2f9fb5b42..c553cb34f 100644 --- a/src/Mbstring/bootstrap80.php +++ b/src/Mbstring/bootstrap80.php @@ -132,6 +132,14 @@ function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = nul function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } } +if (!function_exists('mb_ucfirst')) { + function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); } +} + +if (!function_exists('mb_lcfirst')) { + function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); } +} + if (extension_loaded('mbstring')) { return; } diff --git a/src/Php84/Php84.php b/src/Php84/Php84.php index dc8eea848..f80b71ff2 100644 --- a/src/Php84/Php84.php +++ b/src/Php84/Php84.php @@ -18,4 +18,13 @@ */ final class Php84 { + public static function mb_ucfirst(string $string, ?string $encoding = null): string + { + return \mb_convert_case(\mb_substr($string, 0, 1, $encoding), \MB_CASE_TITLE, $encoding) . \mb_substr($string, 1, null, $encoding); + } + + public static function mb_lcfirst(string $string, ?string $encoding = null): string + { + return \mb_strtolower(\mb_substr($string, 0, 1, $encoding), $encoding) . \mb_substr($string, 1, null, $encoding); + } } diff --git a/src/Php84/README.md b/src/Php84/README.md index af10869c5..e94050d92 100644 --- a/src/Php84/README.md +++ b/src/Php84/README.md @@ -3,6 +3,9 @@ Symfony Polyfill / Php84 This component provides features added to PHP 8.4 core: +- [`mb_lcfirst`](https://wiki.php.net/rfc/mb_ucfirst) +- [`mb_ucfirst`](https://wiki.php.net/rfc/mb_ucfirst) + More information can be found in the [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). diff --git a/src/Php84/bootstrap.php b/src/Php84/bootstrap.php index a0371b29e..bd16633e9 100644 --- a/src/Php84/bootstrap.php +++ b/src/Php84/bootstrap.php @@ -14,3 +14,11 @@ if (\PHP_VERSION_ID >= 80400) { return; } + +if (!function_exists('mb_ucfirst') && function_exists('mb_substr')) { + function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Php84::mb_ucfirst($string, $encoding); } +} + +if (!function_exists('mb_lcfirst') && function_exists('mb_substr')) { + function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Php84::mb_lcfirst($string, $encoding); } +} diff --git a/tests/Mbstring/MbstringTest.php b/tests/Mbstring/MbstringTest.php index 88cfc3265..d606876a0 100644 --- a/tests/Mbstring/MbstringTest.php +++ b/tests/Mbstring/MbstringTest.php @@ -105,7 +105,7 @@ public function testDecodeNumericEntity() $this->assertSame('déjà � â ã', mb_decode_numericentity('déjà � á â', $convmap, 'UTF-8')); $bogusDecEntities = 'déjà � áá &#áá á át'; - $this->assertSame('déjà � ââ &#áâ â ât', mb_decode_numericentity($bogusDecEntities, $convmap, 'UTF-8')); + $this->assertSame('déjà � ââ &#áâ â ât', p::mb_decode_numericentity($bogusDecEntities, $convmap, 'UTF-8')); $bogusHexEntities = 'déjà � áá á át á át'; $this->assertSame('déjà � ââ â ât â ât', mb_decode_numericentity($bogusHexEntities, $convmap, 'UTF-8')); @@ -521,7 +521,7 @@ public function testDetectEncoding() { $this->assertTrue(mb_detect_order('ASCII, UTF-8')); $this->assertSame('ASCII', mb_detect_encoding('abc')); - $this->assertSame('UTF-8', mb_detect_encoding('abc', 'UTF8, ASCII')); + $this->assertSame('UTF-8', mb_detect_encoding('abc', 'UTF-8, ASCII')); $this->assertSame('ISO-8859-1', mb_detect_encoding("\xE9", ['UTF-8', 'ASCII', 'ISO-8859-1'], true)); $this->assertFalse(mb_detect_encoding("\xE9", ['UTF-8', 'ASCII'], true)); } @@ -727,4 +727,28 @@ public static function mbStrPadInvalidArgumentsProvider(): iterable yield ['mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH', '▶▶', 6, ' ', 123456]; yield ['mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "unexisting" given', '▶▶', 6, ' ', \STR_PAD_BOTH, 'unexisting']; } + + /** + * @covers \Symfony\Polyfill\Mbstring\Mbstring::mb_lcfirst + */ + public function test_mb_lcfirst(): void + { + $this->assertSame('', p::mb_lcfirst('', 'UTF-8')); + $this->assertSame('aBS', p::mb_lcfirst('ABS', 'UTF-8')); + $this->assertSame('xin chào', p::mb_lcfirst('Xin chào', 'UTF-8')); + $this->assertSame('đẹp quá!', p::mb_lcfirst('Đẹp quá!', 'UTF-8')); + } + + /** + * @covers \Symfony\Polyfill\Mbstring\Mbstring::mb_ucfirst + */ + public function test_mb_ucfirst(): void + { + $this->assertSame('', p::mb_ucfirst('', 'UTF-8')); + $this->assertSame('Ab', p::mb_ucfirst('ab', 'UTF-8')); + $this->assertSame('ABS', p::mb_ucfirst('ABS', 'UTF-8')); + $this->assertSame('Đắt quá!', p::mb_ucfirst('đắt quá!', 'UTF-8')); + $this->assertSame('აბგ', p::mb_ucfirst('აბგ', 'UTF-8')); + $this->assertSame('Lj', p::mb_ucfirst('lj', 'UTF-8')); + } } diff --git a/tests/Php84/Php84Test.php b/tests/Php84/Php84Test.php index 43dd6e7ba..3a79cdff4 100644 --- a/tests/Php84/Php84Test.php +++ b/tests/Php84/Php84Test.php @@ -12,7 +12,25 @@ namespace Symfony\Polyfill\Tests\Php84; use PHPUnit\Framework\TestCase; +use Symfony\Polyfill\Php84\Php84 as p; class Php84Test extends TestCase { + public function test_mb_lcfirst(): void + { + $this->assertSame('', p::mb_lcfirst('', 'UTF-8')); + $this->assertSame('aBS', p::mb_lcfirst('ABS', 'UTF-8')); + $this->assertSame('xin chào', p::mb_lcfirst('Xin chào', 'UTF-8')); + $this->assertSame('đẹp quá!', p::mb_lcfirst('Đẹp quá!', 'UTF-8')); + } + + public function test_mb_ucfirst(): void + { + $this->assertSame('', p::mb_ucfirst('', 'UTF-8')); + $this->assertSame('Ab', p::mb_ucfirst('ab', 'UTF-8')); + $this->assertSame('ABS', p::mb_ucfirst('ABS', 'UTF-8')); + $this->assertSame('Đắt quá!', p::mb_ucfirst('đắt quá!', 'UTF-8')); + $this->assertSame('აბგ', p::mb_ucfirst('აბგ', 'UTF-8')); + $this->assertSame('Lj', p::mb_ucfirst('lj', 'UTF-8')); + } }