PHP 8.5 updates

This commit is contained in:
Clemens Schwaighofer
2026-01-06 18:37:00 +09:00
parent f765f50350
commit 936b424065
10 changed files with 226 additions and 139 deletions

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phive xmlns="https://phar.io/phive"> <phive xmlns="https://phar.io/phive">
<phar name="phpunit" version="^9.6" installed="9.6.19" location="./tools/phpunit" copy="false"/> <phar name="phpunit" version="^9.6" installed="9.6.31" location="./tools/phpunit" copy="false"/>
<phar name="phpcs" version="^3.7.2" installed="3.10.0" location="./tools/phpcs" copy="false"/> <phar name="phpcs" version="^4.0.0" installed="4.0.1" location="./tools/phpcs" copy="false"/>
<phar name="phpcbf" version="^3.7.2" installed="3.10.0" location="./tools/phpcbf" copy="false"/> <phar name="phpcbf" version="^4.0.0" installed="4.0.1" location="./tools/phpcbf" copy="false"/>
<phar name="psalm" version="^5.15.0" installed="5.24.0" location="./tools/psalm" copy="false"/> <phar name="psalm" version="^5.15.0" installed="5.26.1" location="./tools/psalm" copy="false"/>
<phar name="phpstan" version="^1.10.37" installed="1.11.1" location="./tools/phpstan" copy="false"/> <phar name="phpstan" version="^1.10.37" installed="1.12.32" location="./tools/phpstan" copy="false"/>
<phar name="phan" version="^5.4.2" installed="5.4.3" location="./tools/phan" copy="false"/> <phar name="phan" version="^5.4.2" installed="5.5.2" location="./tools/phan" copy="false"/>
</phive> </phive>

View File

@@ -114,7 +114,7 @@ class Encoding
(($char != $r_char && (!self::$mb_error_char || (($char != $r_char && (!self::$mb_error_char ||
in_array(self::$mb_error_char, ['none', 'long', 'entity']))) || in_array(self::$mb_error_char, ['none', 'long', 'entity']))) ||
($char != $r_char && $r_char == self::$mb_error_char && self::$mb_error_char)) && ($char != $r_char && $r_char == self::$mb_error_char && self::$mb_error_char)) &&
ord($char) != 194 ord($char[0]) != 194
) { ) {
$failed[] = $char; $failed[] = $char;
} }

View File

@@ -14,6 +14,7 @@ class Byte
public const BYTE_FORMAT_NOSPACE = 1; public const BYTE_FORMAT_NOSPACE = 1;
public const BYTE_FORMAT_ADJUST = 2; public const BYTE_FORMAT_ADJUST = 2;
public const BYTE_FORMAT_SI = 4; public const BYTE_FORMAT_SI = 4;
public const RETURN_AS_STRING = 8;
/** /**
* This function replaces the old byteStringFormat * This function replaces the old byteStringFormat
@@ -119,7 +120,9 @@ class Byte
* @param int $flags bitwise flag with use space turned on * @param int $flags bitwise flag with use space turned on
* BYTE_FORMAT_SI: use 1000 instead of 1024 * BYTE_FORMAT_SI: use 1000 instead of 1024
* @return string|int|float converted value or original value * @return string|int|float converted value or original value
* @throws \InvalidArgumentException 1: no valid flag set * @throws \InvalidArgumentException no valid flag set
* @throws \LengthException number too large to convert to int
* @throws \RuntimeException BCMath extension not loaded if flag is set to string
*/ */
public static function stringByteFormat(string|int|float $number, int $flags = 0): string|int|float public static function stringByteFormat(string|int|float $number, int $flags = 0): string|int|float
{ {
@@ -129,7 +132,12 @@ class Byte
} else { } else {
$si = false; $si = false;
} }
if ($flags != 0 && $flags != 4) { if ($flags & self::RETURN_AS_STRING) {
$return_as_string = true;
} else {
$return_as_string = false;
}
if ($flags != 0 && $flags != 4 && $flags != 8 && $flags != 12) {
throw new \InvalidArgumentException("Invalid flags parameter: $flags", 1); throw new \InvalidArgumentException("Invalid flags parameter: $flags", 1);
} }
// matches in regex // matches in regex
@@ -142,6 +150,10 @@ class Byte
strtolower((string)$number), strtolower((string)$number),
$matches $matches
); );
$number_negative = false;
if (!empty($matches[1])) {
$number_negative = true;
}
if (isset($matches[2]) && isset($matches[3])) { if (isset($matches[2]) && isset($matches[3])) {
// remove all non valid characters from the number // remove all non valid characters from the number
$number = preg_replace('/[^0-9\.]/', '', $matches[2]); $number = preg_replace('/[^0-9\.]/', '', $matches[2]);
@@ -152,12 +164,48 @@ class Byte
if ($unit) { if ($unit) {
$number = $number * pow($si ? 1000 : 1024, stripos($valid_units_, $unit[0]) ?: 0); $number = $number * pow($si ? 1000 : 1024, stripos($valid_units_, $unit[0]) ?: 0);
} }
// if the number is too large, we cannot convert to int directly
if ($number <= PHP_INT_MIN || $number >= PHP_INT_MAX) {
// if we do not want to convert to string
if (!$return_as_string) {
throw new \LengthException(
'Number too large be converted to int: ' . (string)$number
);
}
// for string, check if bcmath is loaded, if not this will not work
if (!extension_loaded('bcmath')) {
throw new \RuntimeException(
'Number too large be converted to int and BCMath extension not loaded: ' . (string)$number
);
}
}
// string return
if ($return_as_string) {
// return as string to avoid overflow
// $number = (string)round($number);
$number = bcmul(number_format(
$number,
12,
'.',
''
), "1");
if ($number_negative) {
$number = '-' . $number;
}
return $number;
}
// convert to INT to avoid +E output // convert to INT to avoid +E output
$number = (int)round($number); $number = (int)round($number);
// if negative input, keep nnegative // if negative input, keep nnegative
if (!empty($matches[1])) { if ($number_negative) {
$number *= -1; $number *= -1;
} }
// check if number is negative but should be, this is Lenght overflow
if (!$number_negative && $number < 0) {
throw new \LengthException(
'Number too large be converted to int: ' . (string)$number
);
}
} }
// if not matching return as is // if not matching return as is
return $number; return $number;

View File

@@ -62,10 +62,15 @@ class Math
* *
* @param float $number Number to cubic root * @param float $number Number to cubic root
* @return float Calculated value * @return float Calculated value
* @throws \InvalidArgumentException if $number is negative
*/ */
public static function cbrt(float|int $number): float public static function cbrt(float|int $number): float
{ {
return pow((float)$number, 1.0 / 3); $value = pow((float)$number, 1.0 / 3);
if (is_nan($value)) {
throw new \InvalidArgumentException('cube root from this number is not supported: ' . $number);
}
return $value;
} }
/** /**

View File

@@ -291,7 +291,7 @@ class ErrorMessage
*/ */
public function getLastErrorMsg(): array public function getLastErrorMsg(): array
{ {
return $this->error_str[array_key_last($this->error_str)] ?? [ return $this->error_str[array_key_last($this->error_str) ?? -1] ?? [
'level' => '', 'level' => '',
'str' => '', 'str' => '',
'id' => '', 'id' => '',

View File

@@ -365,9 +365,6 @@ class Image
imagepng($thumb, $thumbnail_write_path . $thumbnail); imagepng($thumb, $thumbnail_write_path . $thumbnail);
break; break;
} }
// free up resources (in case we are called in a loop)
imagedestroy($source);
imagedestroy($thumb);
} else { } else {
throw new \RuntimeException( throw new \RuntimeException(
'Invalid source image file. Only JPEG/PNG are allowed: ' . $filename, 'Invalid source image file. Only JPEG/PNG are allowed: ' . $filename,
@@ -543,8 +540,6 @@ class Image
imagepng($img, $filename); imagepng($img, $filename);
break; break;
} }
// clean up image if we have an image
imagedestroy($img);
} }
} }

View File

@@ -614,8 +614,6 @@ class Curl implements Interface\RequestsInterface
// print "CURLINFO_HEADER_OUT: <pre>" . curl_getinfo($handle, CURLINFO_HEADER_OUT) . "</pre>"; // print "CURLINFO_HEADER_OUT: <pre>" . curl_getinfo($handle, CURLINFO_HEADER_OUT) . "</pre>";
// get response code and bail on not authorized // get response code and bail on not authorized
$http_response = $this->handleCurlResponse($handle, $http_result, $options['http_errors']); $http_response = $this->handleCurlResponse($handle, $http_result, $options['http_errors']);
// close handler
$this->handleCurlClose($handle);
// return response and result // return response and result
return [ return [
'code' => (string)$http_response, 'code' => (string)$http_response,
@@ -838,17 +836,6 @@ class Curl implements Interface\RequestsInterface
); );
} }
/**
* close the current curl handle
*
* @param \CurlHandle $handle
* @return void
*/
private function handleCurlClose(\CurlHandle $handle): void
{
curl_close($handle);
}
// ********************************************************************* // *********************************************************************
// MARK: PUBLIC METHODS // MARK: PUBLIC METHODS
// ********************************************************************* // *********************************************************************

View File

@@ -123,47 +123,6 @@ final class CoreLibsConvertByteTest extends TestCase
]; ];
} }
/**
* Undocumented function
*
* @return array
*/
public function byteStringProvider(): array
{
return [
'negative number' => [
0 => '-117.42 MB',
1 => -123123794,
2 => -117420000,
],
'megabyte' => [
0 => '242.98 MB',
1 => 254782996,
2 => 242980000
],
'megabyte si' => [
0 => '254.78 MiB',
1 => 267156193,
2 => 254780000
],
'petabyte' => [
0 => '1 EiB',
1 => 1152921504606846976,
2 => 1000000000000000000,
],
'max int' => [
0 => '8 EB',
1 => -9223372036854775807 - 1,
2 => 8000000000000000000,
],
'exabyte, overflow' => [
0 => '867.36EB',
1 => 3873816255479021568,
2 => 363028535651074048,
]
];
}
/** /**
* Undocumented function * Undocumented function
* *
@@ -180,7 +139,7 @@ final class CoreLibsConvertByteTest extends TestCase
* @return void * @return void
*/ */
public function testHumanReadableByteFormat( public function testHumanReadableByteFormat(
$input, string|int|float $input,
string $expected, string $expected,
string $expected_si, string $expected_si,
string $expected_no_space, string $expected_no_space,
@@ -217,6 +176,73 @@ final class CoreLibsConvertByteTest extends TestCase
); );
} }
/**
* Undocumented function
*
* @return array
*/
public function byteStringProvider(): array
{
return [
'negative number' => [
0 => '-117.42 MB',
1 => -123123794,
2 => -117420000,
3 => "-123123793",
4 => "-117420000",
5 => null,
],
'megabyte' => [
0 => '242.98 MB',
1 => 254782996,
2 => 242980000,
3 => "254782996",
4 => "242980000",
5 => null,
],
'megabyte si' => [
0 => '254.78 MiB',
1 => 267156193,
2 => 254780000,
3 => "267156193",
4 => "254780000",
5 => null,
],
'petabyte' => [
0 => '1 EiB',
1 => 1152921504606846976,
2 => 1000000000000000000,
3 => "1152921504606846976",
4 => "1000000000000000000",
5 => null,
],
'max int' => [
0 => '8 EB',
1 => 0,
2 => 0,
3 => "9223372036854775808",
4 => "8000000000000000000",
5 => \LengthException::class,
],
'exabyte, overflow' => [
0 => '867.36EB',
1 => 0,
2 => 0,
3 => "999997996235794808832",
4 => "867360000000000000000",
5 => \LengthException::class,
],
'huge exabyte, overflow' => [
0 => '1000EB',
1 => 0,
2 => 0,
3 => "1152921504606846976000",
4 => "1000000000000000000000",
5 => \LengthException::class,
],
];
}
/** /**
* Undocumented function * Undocumented function
* *
@@ -227,10 +253,22 @@ final class CoreLibsConvertByteTest extends TestCase
* @param string|int|float $input * @param string|int|float $input
* @param string|int|float $expected * @param string|int|float $expected
* @param string|int|float $expected_si * @param string|int|float $expected_si
* @param string|int|float $expected_string
* @param string|int|float $expected_string_si
* @param ?string $exception
* @return void * @return void
*/ */
public function testStringByteFormat($input, $expected, $expected_si): void public function testStringByteFormat(
{ string|int|float $input,
string|int|float $expected,
string|int|float $expected_si,
string|int|float $expected_string,
string|int|float $expected_string_si,
?string $exception
): void {
if ($exception !== null) {
$this->expectException($exception);
}
$this->assertEquals( $this->assertEquals(
$expected, $expected,
\CoreLibs\Convert\Byte::stringByteFormat($input) \CoreLibs\Convert\Byte::stringByteFormat($input)
@@ -239,6 +277,17 @@ final class CoreLibsConvertByteTest extends TestCase
$expected_si, $expected_si,
\CoreLibs\Convert\Byte::stringByteFormat($input, \CoreLibs\Convert\Byte::BYTE_FORMAT_SI) \CoreLibs\Convert\Byte::stringByteFormat($input, \CoreLibs\Convert\Byte::BYTE_FORMAT_SI)
); );
$this->assertEquals(
$expected_string,
\CoreLibs\Convert\Byte::stringByteFormat($input, \CoreLibs\Convert\Byte::RETURN_AS_STRING)
);
$this->assertEquals(
$expected_string_si,
\CoreLibs\Convert\Byte::stringByteFormat(
$input,
\CoreLibs\Convert\Byte::BYTE_FORMAT_SI | \CoreLibs\Convert\Byte::RETURN_AS_STRING
)
);
} }
/** /**

View File

@@ -122,9 +122,9 @@ final class CoreLibsConvertMathTest extends TestCase
public function providerCbrt(): array public function providerCbrt(): array
{ {
return [ return [
'cube root of 2' => [2, 1.25992, 5], 'cube root of 2' => [2, 1.25992, 5, null],
'cube root of 3' => [3, 1.44225, 5], 'cube root of 3' => [3, 1.44225, 5, null],
'cube root of -1' => [-1, 'NAN', 0], 'cube root of -1' => [-1, 'NAN', 0, \InvalidArgumentException::class],
]; ];
} }
@@ -138,10 +138,14 @@ final class CoreLibsConvertMathTest extends TestCase
* @param float|int $number * @param float|int $number
* @param float $expected * @param float $expected
* @param int $round_to * @param int $round_to
* @param ?string $exception
* @return void * @return void
*/ */
public function testCbrt(float|int $number, float|string $expected, int $round_to): void public function testCbrt(float|int $number, float|string $expected, int $round_to, ?string $exception): void
{ {
if ($exception !== null) {
$this->expectException($exception);
}
$this->assertEquals( $this->assertEquals(
$expected, $expected,
round(\CoreLibs\Convert\Math::cbrt($number), $round_to) round(\CoreLibs\Convert\Math::cbrt($number), $round_to)

View File

@@ -59,8 +59,6 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase
continue; continue;
} }
$this->url_basic = $url; $this->url_basic = $url;
// split out the last / part for url set test
curl_close($handle);
// print "Open: $url\n"; // print "Open: $url\n";
break; break;
} }
@@ -969,76 +967,77 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase
"query" => ["foo-get" => "bar"] "query" => ["foo-get" => "bar"]
]); ]);
$this->assertEquals("200", $response["code"], "multi call: get response code not matching"); $this->assertEquals("200", $response["code"], "multi call: get response code not matching");
if (PHP_VERSION_ID >= 80400) { $request_expected = json_decode(
$this->assertEquals( <<<JSON
'{"HEADERS":{"HTTP_HOST":"soba.egplusww.jp",' {
. '"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_FIRST_CALL":"get",' "HEADERS":{
. '"HTTP_ACCEPT":"*\/*"},"REQUEST_TYPE":"GET","PARAMS":{"foo-get":"bar"},"BODY":null}', "HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",
$response['content'], "HTTP_FIRST_CALL":"get","HTTP_ACCEPT":"*\/*",
'multi call: get content not matching' "HTTP_HOST":"soba.egplusww.jp"
); },
} else { "REQUEST_TYPE":"GET",
$this->assertEquals( "PARAMS":{"foo-get":"bar"},"BODY":null
'{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' }
. '"HTTP_FIRST_CALL":"get","HTTP_ACCEPT":"*\/*",' JSON,
. '"HTTP_HOST":"soba.egplusww.jp"},' true
. '"REQUEST_TYPE":"GET",' );
. '"PARAMS":{"foo-get":"bar"},"BODY":null}', $this->assertEquals(
$response['content'], $request_expected,
'multi call: get content not matching' json_decode($response['content'], true),
); 'multi call: get content not matching'
} );
// post // post
$response = $curl->post($this->url_basic, [ $response = $curl->post($this->url_basic, [
"headers" => ["second-call" => "post"], "headers" => ["second-call" => "post"],
"body" => ["foo-post" => "baz"] "body" => ["foo-post" => "baz"]
]); ]);
$this->assertEquals("200", $response["code"], "multi call: post response code not matching"); $this->assertEquals("200", $response["code"], "multi call: post response code not matching");
if (PHP_VERSION_ID >= 80400) { $request_expected = json_decode(
$this->assertEquals( <<<JSON
'{"HEADERS":{"HTTP_HOST":"soba.egplusww.jp",' {
. '"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' "HEADERS":{
. '"HTTP_SECOND_CALL":"post","HTTP_ACCEPT":"*\/*"},' "HTTP_HOST":"soba.egplusww.jp",
. '"REQUEST_TYPE":"POST","PARAMS":[],"BODY":{"foo-post":"baz"}}', "HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",
$response['content'], "HTTP_SECOND_CALL":"post",
'multi call: post content not matching' "HTTP_ACCEPT":"*\/*"
); },
} else { "REQUEST_TYPE":"POST",
$this->assertEquals( "PARAMS":[],
'{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' "BODY":{"foo-post":"baz"}
. '"HTTP_SECOND_CALL":"post","HTTP_ACCEPT":"*\/*",' }
. '"HTTP_HOST":"soba.egplusww.jp"},' JSON,
. '"REQUEST_TYPE":"POST",' true
. '"PARAMS":[],"BODY":{"foo-post":"baz"}}', );
$response['content'], $this->assertEquals(
'multi call: post content not matching' $request_expected,
); json_decode($response['content'], true),
} 'multi call: post content not matching'
);
// delete // delete
$response = $curl->delete($this->url_basic, [ $response = $curl->delete($this->url_basic, [
"headers" => ["third-call" => "delete"], "headers" => ["third-call" => "delete"],
]); ]);
$this->assertEquals("200", $response["code"], "multi call: delete response code not matching"); $this->assertEquals("200", $response["code"], "multi call: delete response code not matching");
if (PHP_VERSION_ID >= 80400) { $request_expected = json_decode(
$this->assertEquals( <<<JSON
'{"HEADERS":{"HTTP_HOST":"soba.egplusww.jp",' {
. '"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' "HEADERS":{
. '"HTTP_THIRD_CALL":"delete","HTTP_ACCEPT":"*\/*"},' "HTTP_HOST":"soba.egplusww.jp",
. '"REQUEST_TYPE":"DELETE","PARAMS":[],"BODY":[]}', "HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",
$response['content'], "HTTP_THIRD_CALL":"delete","HTTP_ACCEPT":"*\/*"
'multi call: delete content not matching' },
); "REQUEST_TYPE":"DELETE",
} else { "PARAMS":[],
$this->assertEquals( "BODY":[]
'{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' }
. '"HTTP_THIRD_CALL":"delete","HTTP_ACCEPT":"*\/*",' JSON,
. '"HTTP_HOST":"soba.egplusww.jp"},' true
. '"REQUEST_TYPE":"DELETE",' );
. '"PARAMS":[],"BODY":[]}', $this->assertEquals(
$response['content'], $request_expected,
'multi call: delete content not matching' json_decode($response['content'], true),
); 'multi call: delete content not matching'
} );
} }
// MARK: auth header set via config // MARK: auth header set via config