Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix: mbstring polyfills must not raise value errors in PHP 7 #501

Open
wants to merge 1 commit into
base: 1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 42 additions & 18 deletions src/Mbstring/Mbstring.php
Original file line number Diff line number Diff line change
Expand Up @@ -834,19 +834,32 @@ public static function mb_ord($s, $encoding = null)
return $code;
}

public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string
/** @return string|false */
public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null)
{
if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
if (\PHP_VERSION_ID < 80000) {
trigger_error('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH', \E_USER_WARNING);

return false;
}

throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
}

if (null === $encoding) {
$encoding = self::mb_internal_encoding();
} else {
self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given');
} elseif (!self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given')) {
return false;
}

if (self::mb_strlen($pad_string, $encoding) <= 0) {
if (\PHP_VERSION_ID < 80000) {
trigger_error('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string', \E_USER_WARNING);

return false;
}

throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
}

Expand All @@ -869,12 +882,13 @@ 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 string|false */
public static function mb_ucfirst(string $string, ?string $encoding = null)
{
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
} else {
self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
} elseif (!self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) {
return false;
}

$firstChar = mb_substr($string, 0, 1, $encoding);
Expand All @@ -883,12 +897,13 @@ public static function mb_ucfirst(string $string, ?string $encoding = null): str
return $firstChar.mb_substr($string, 1, null, $encoding);
}

public static function mb_lcfirst(string $string, ?string $encoding = null): string
/** @return string|false */
public static function mb_lcfirst(string $string, ?string $encoding = null)
{
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
} else {
self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
} elseif (!self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) {
return false;
}

$firstChar = mb_substr($string, 0, 1, $encoding);
Expand Down Expand Up @@ -971,27 +986,31 @@ private static function getEncoding($encoding)
return $encoding;
}

public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string
/** @return string|false */
public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null)
{
return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
}

public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string
/** @return string|false */
public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null)
{
return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__);
}

public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string
/** @return string|false */
public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null)
{
return self::mb_internal_trim('{[%s]+$}D', $string, $characters, $encoding, __FUNCTION__);
}

private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function): string
/** @return string|false */
private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function)
{
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
} else {
self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given');
} elseif (!self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given')) {
return false;
}

if ('' === $characters) {
Expand Down Expand Up @@ -1029,17 +1048,22 @@ private static function mb_internal_trim(string $regex, string $string, ?string
return iconv('UTF-8', $encoding.'//IGNORE', $string);
}

private static function assertEncoding(string $encoding, string $errorFormat): void
private static function assertEncoding(string $encoding, string $errorFormat): bool
{
try {
$validEncoding = @self::mb_check_encoding('', $encoding);
} catch (\ValueError $e) {
throw new \ValueError(sprintf($errorFormat, $encoding));
}

// BC for PHP 7.3 and lower
if (!$validEncoding) {
throw new \ValueError(sprintf($errorFormat, $encoding));
if (\PHP_VERSION_ID >= 80000) {
throw new \ValueError(sprintf($errorFormat, $encoding));
}

trigger_error(sprintf($errorFormat, $encoding), E_USER_WARNING);
}

return $validEncoding;
}
}
12 changes: 6 additions & 6 deletions src/Mbstring/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,27 +133,27 @@ function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstrin
}

if (!function_exists('mb_str_pad')) {
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); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about a bootstrap80.php file to add the return types when running on PHP8+?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That file already exists. ✌🏻

Copy link
Member

@nicolas-grekas nicolas-grekas Sep 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah indeed :)
there are some failures to fix ;)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you help we with those? Apparently, the test listener is skipping tests at a strange point in time which conflicts with the expectWarning() feature of PHPUnit. How do we solve that usually?

function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null) { 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); }
function mb_ucfirst(string $string, ?string $encoding = null) { 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); }
function mb_lcfirst(string $string, ?string $encoding = null) { return p\Mbstring::mb_lcfirst($string, $encoding); }
}

if (!function_exists('mb_trim')) {
function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim($string, $characters, $encoding); }
function mb_trim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_trim($string, $characters, $encoding); }
}

if (!function_exists('mb_ltrim')) {
function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim($string, $characters, $encoding); }
function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_ltrim($string, $characters, $encoding); }
}

if (!function_exists('mb_rtrim')) {
function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim($string, $characters, $encoding); }
function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_rtrim($string, $characters, $encoding); }
}


Expand Down
25 changes: 17 additions & 8 deletions src/Php83/Php83.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public static function json_validate(string $json, int $depth = 512, int $flags
return \JSON_ERROR_NONE === json_last_error();
}

public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string
/** @return string|false */
public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null)
{
if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
Expand All @@ -50,19 +51,27 @@ public static function mb_str_pad(string $string, int $length, string $pad_strin
$encoding = mb_internal_encoding();
}

$errorToTrigger = null;
try {
$validEncoding = @mb_check_encoding('', $encoding);
if (!@mb_check_encoding('', $encoding)) {
$errorToTrigger = sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding);
}
} catch (\ValueError $e) {
throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding));
$errorToTrigger = sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding);
}

// BC for PHP 7.3 and lower
if (!$validEncoding) {
throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding));
if (mb_strlen($pad_string, $encoding) <= 0) {
$errorToTrigger = 'mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string';
}

if (mb_strlen($pad_string, $encoding) <= 0) {
throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
if (null !== $errorToTrigger) {
if (PHP_VERSION_ID < 80000) {
trigger_error($errorToTrigger, E_USER_WARNING);

return false;
}

throw new \ValueError($errorToTrigger);
}

$paddingRequired = $length - mb_strlen($string, $encoding);
Expand Down
16 changes: 8 additions & 8 deletions src/Php83/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,6 @@
function json_validate(string $json, int $depth = 512, int $flags = 0): bool { return p\Php83::json_validate($json, $depth, $flags); }
}

if (extension_loaded('mbstring')) {
if (!function_exists('mb_str_pad')) {
function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Php83::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}
}

if (!function_exists('stream_context_set_options')) {
function stream_context_set_options($context, array $options): bool { return stream_context_set_option($context, $options); }
}
Expand All @@ -37,8 +31,14 @@ function str_increment(string $string): string { return p\Php83::str_increment($
function str_decrement(string $string): string { return p\Php83::str_decrement($string); }
}

if (\PHP_VERSION_ID >= 80100) {
return require __DIR__.'/bootstrap81.php';
if (\PHP_VERSION_ID >= 80000) {
return require __DIR__.'/bootstrap80.php';
}

if (extension_loaded('mbstring')) {
if (!function_exists('mb_str_pad')) {
function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null) { return p\Php83::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}
}

if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) {
Expand Down
19 changes: 19 additions & 0 deletions src/Php83/bootstrap80.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

if (extension_loaded('mbstring')) {
if (!function_exists('mb_str_pad')) {
function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Php83::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}
}

if (\PHP_VERSION_ID >= 80100) {
return require __DIR__.'/bootstrap81.php';
}

if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) {
function ldap_exop_sync($ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid); }
}

if (!function_exists('ldap_connect_wallet') && function_exists('ldap_connect')) {
function ldap_connect_wallet(?string $uri, string $wallet, string $password, int $auth_mode = \GSLC_SSL_NO_AUTH) { return ldap_connect($uri, $wallet, $password, $auth_mode); }
}
41 changes: 31 additions & 10 deletions src/Php84/Php84.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
*/
final class Php84
{
public static function mb_ucfirst(string $string, ?string $encoding = null): string
/** @return string|false */
public static function mb_ucfirst(string $string, ?string $encoding = null)
{
if (null === $encoding) {
$encoding = mb_internal_encoding();
Expand All @@ -31,8 +32,13 @@ public static function mb_ucfirst(string $string, ?string $encoding = null): str
throw new \ValueError(sprintf('mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding));
}

// BC for PHP 7.3 and lower
if (!$validEncoding) {
if (PHP_VERSION_ID < 80000) {
trigger_error(sprintf('mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding), \E_USER_WARNING);

return false;
}

throw new \ValueError(sprintf('mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding));
}

Expand All @@ -42,7 +48,8 @@ public static function mb_ucfirst(string $string, ?string $encoding = null): str
return $firstChar.mb_substr($string, 1, null, $encoding);
}

public static function mb_lcfirst(string $string, ?string $encoding = null): string
/** @return string|false */
public static function mb_lcfirst(string $string, ?string $encoding = null)
{
if (null === $encoding) {
$encoding = mb_internal_encoding();
Expand All @@ -54,8 +61,13 @@ public static function mb_lcfirst(string $string, ?string $encoding = null): str
throw new \ValueError(sprintf('mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding));
}

// BC for PHP 7.3 and lower
if (!$validEncoding) {
if (PHP_VERSION_ID < 80000) {
trigger_error(sprintf('mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding), \E_USER_WARNING);

return false;
}

throw new \ValueError(sprintf('mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding));
}

Expand Down Expand Up @@ -109,22 +121,26 @@ public static function array_all(array $array, callable $callback): bool
return true;
}

public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string
/** @return string|false */
public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null)
{
return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
}

public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string
/** @return string|false */
public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null)
{
return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__);
}

public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string
/** @return string|false */
public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null)
{
return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
return self::mb_internal_trim('{[%s]+$}D', $string, $characters, $encoding, __FUNCTION__);
}

private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function): string
/** @return string|false */
private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function)
{
if (null === $encoding) {
$encoding = mb_internal_encoding();
Expand All @@ -136,8 +152,13 @@ private static function mb_internal_trim(string $regex, string $string, ?string
throw new \ValueError(sprintf('%s(): Argument #3 ($encoding) must be a valid encoding, "%s" given', $function, $encoding));
}

// BC for PHP 7.3 and lower
if (!$validEncoding) {
if (PHP_VERSION_ID < 80000) {
trigger_error(sprintf('%s(): Argument #3 ($encoding) must be a valid encoding, "%s" given', $function, $encoding), \E_USER_WARNING);

return false;
}

throw new \ValueError(sprintf('%s(): Argument #3 ($encoding) must be a valid encoding, "%s" given', $function, $encoding));
}

Expand Down
14 changes: 9 additions & 5 deletions src/Php84/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,28 @@ function array_any(array $array, callable $callback): bool { return p\Php84::arr
function array_all(array $array, callable $callback): bool { return p\Php84::array_all($array, $callback); }
}

if (\PHP_VERSION_ID >= 80000) {
return require __DIR__ . '/bootstrap80.php';
}

if (extension_loaded('mbstring')) {
if (!function_exists('mb_ucfirst')) {
function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Php84::mb_ucfirst($string, $encoding); }
function mb_ucfirst(string $string, ?string $encoding = null) { return p\Php84::mb_ucfirst($string, $encoding); }
}

if (!function_exists('mb_lcfirst')) {
function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Php84::mb_lcfirst($string, $encoding); }
function mb_lcfirst(string $string, ?string $encoding = null) { return p\Php84::mb_lcfirst($string, $encoding); }
}

if (!function_exists('mb_trim')) {
function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Php84::mb_trim($string, $characters, $encoding); }
function mb_trim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Php84::mb_trim($string, $characters, $encoding); }
}

if (!function_exists('mb_ltrim')) {
function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Php84::mb_ltrim($string, $characters, $encoding); }
function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Php84::mb_ltrim($string, $characters, $encoding); }
}

if (!function_exists('mb_rtrim')) {
function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Php84::mb_rtrim($string, $characters, $encoding); }
function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Php84::mb_rtrim($string, $characters, $encoding); }
}
}
Loading
Loading