From 9b98077b6b01ee5a1352125bb8a2a6efcf386a28 Mon Sep 17 00:00:00 2001 From: Michael D Johnson Date: Sun, 8 Dec 2024 23:21:03 -0600 Subject: [PATCH 1/5] Explicitly mark all nullable parameters This removes the deprecations introduced in PHP 8.4 --- src/Functional/CompareObjectHashOn.php | 4 ++-- src/Functional/CompareOn.php | 4 ++-- src/Functional/Every.php | 2 +- src/Functional/First.php | 4 ++-- src/Functional/Head.php | 4 ++-- src/Functional/Last.php | 4 ++-- src/Functional/Memoize.php | 2 +- src/Functional/None.php | 2 +- src/Functional/Pick.php | 2 +- src/Functional/Poll.php | 2 +- src/Functional/Reject.php | 2 +- src/Functional/Retry.php | 2 +- src/Functional/Select.php | 2 +- src/Functional/Some.php | 2 +- src/Functional/Tail.php | 4 ++-- src/Functional/Unique.php | 4 ++-- 16 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Functional/CompareObjectHashOn.php b/src/Functional/CompareObjectHashOn.php index c4194479..35e0ce04 100644 --- a/src/Functional/CompareObjectHashOn.php +++ b/src/Functional/CompareObjectHashOn.php @@ -14,11 +14,11 @@ * Returns a comparison function that can be used with e.g. `usort()` * * @param callable $comparison A function that compares the two values. Pick e.g. strcmp() or strnatcasecmp() - * @param callable $keyFunction A function that takes an argument and returns the value that should be compared + * @param callable|null $keyFunction A function that takes an argument and returns the value that should be compared * @return callable * @no-named-arguments */ -function compare_object_hash_on(callable $comparison, callable $keyFunction = null) +function compare_object_hash_on(callable $comparison, ?callable $keyFunction = null) { $keyFunction = $keyFunction ? compose($keyFunction, 'spl_object_hash') : 'spl_object_hash'; diff --git a/src/Functional/CompareOn.php b/src/Functional/CompareOn.php index b0e5cda0..2d7f2486 100644 --- a/src/Functional/CompareOn.php +++ b/src/Functional/CompareOn.php @@ -14,11 +14,11 @@ * Returns a comparison function that can be used with e.g. `usort()` * * @param callable $comparison A function that compares the two values. Pick e.g. strcmp() or strnatcasecmp() - * @param callable $reducer A function that takes an argument and returns the value that should be compared + * @param callable|null $reducer A function that takes an argument and returns the value that should be compared * @return callable * @no-named-arguments */ -function compare_on(callable $comparison, callable $reducer = null) +function compare_on(callable $comparison, ?callable $reducer = null) { if ($reducer === null) { return static function ($left, $right) use ($comparison) { diff --git a/src/Functional/Every.php b/src/Functional/Every.php index e4d8ff62..667cd72b 100644 --- a/src/Functional/Every.php +++ b/src/Functional/Every.php @@ -22,7 +22,7 @@ * @return bool * @no-named-arguments */ -function every($collection, callable $callback = null) +function every($collection, ?callable $callback = null) { InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1); diff --git a/src/Functional/First.php b/src/Functional/First.php index 87db945e..20c39274 100644 --- a/src/Functional/First.php +++ b/src/Functional/First.php @@ -19,11 +19,11 @@ * arguments will be element, index, collection * * @param Traversable|array $collection - * @param callable $callback + * @param callable|null $callback * @return mixed * @no-named-arguments */ -function first($collection, callable $callback = null) +function first($collection, ?callable $callback = null) { InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1); diff --git a/src/Functional/Head.php b/src/Functional/Head.php index c6de5e86..3019ee86 100644 --- a/src/Functional/Head.php +++ b/src/Functional/Head.php @@ -17,11 +17,11 @@ * Alias for Functional\first * * @param Traversable|array $collection - * @param callable $callback + * @param callable|null $callback * @return mixed * @no-named-arguments */ -function head($collection, callable $callback = null) +function head($collection, ?callable $callback = null) { InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1); diff --git a/src/Functional/Last.php b/src/Functional/Last.php index 8989f945..3c861d31 100644 --- a/src/Functional/Last.php +++ b/src/Functional/Last.php @@ -18,11 +18,11 @@ * Callback arguments will be element, index, collection * * @param Traversable|array $collection - * @param callable $callback + * @param callable|null $callback * @return mixed * @no-named-arguments */ -function last($collection, callable $callback = null) +function last($collection, ?callable $callback = null) { InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1); diff --git a/src/Functional/Memoize.php b/src/Functional/Memoize.php index 3813e3ce..aca716b5 100644 --- a/src/Functional/Memoize.php +++ b/src/Functional/Memoize.php @@ -21,7 +21,7 @@ * @return mixed * @no-named-arguments */ -function memoize(callable $callback = null, $arguments = [], $key = null) +function memoize(?callable $callback = null, $arguments = [], $key = null) { static $storage = []; if ($callback === null) { diff --git a/src/Functional/None.php b/src/Functional/None.php index edbd33a9..c90e4055 100644 --- a/src/Functional/None.php +++ b/src/Functional/None.php @@ -22,7 +22,7 @@ * @return bool * @no-named-arguments */ -function none($collection, callable $callback = null) +function none($collection, ?callable $callback = null) { InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1); diff --git a/src/Functional/Pick.php b/src/Functional/Pick.php index b4be3c13..7d29f92c 100644 --- a/src/Functional/Pick.php +++ b/src/Functional/Pick.php @@ -24,7 +24,7 @@ * @return mixed * @no-named-arguments */ -function pick($collection, $index, $default = null, callable $callback = null) +function pick($collection, $index, $default = null, ?callable $callback = null) { InvalidArgumentException::assertArrayAccess($collection, __FUNCTION__, 1); diff --git a/src/Functional/Poll.php b/src/Functional/Poll.php index 7c632d77..8f920d24 100644 --- a/src/Functional/Poll.php +++ b/src/Functional/Poll.php @@ -26,7 +26,7 @@ * @return boolean * @no-named-arguments */ -function poll(callable $callback, $timeout, Traversable $delaySequence = null) +function poll(callable $callback, $timeout, ?Traversable $delaySequence = null) { InvalidArgumentException::assertIntegerGreaterThanOrEqual($timeout, 0, __FUNCTION__, 2); diff --git a/src/Functional/Reject.php b/src/Functional/Reject.php index 505acc91..0efddeee 100644 --- a/src/Functional/Reject.php +++ b/src/Functional/Reject.php @@ -22,7 +22,7 @@ * @return array * @no-named-arguments */ -function reject($collection, callable $callback = null) +function reject($collection, ?callable $callback = null) { InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1); diff --git a/src/Functional/Retry.php b/src/Functional/Retry.php index 40ef0fa7..4466542a 100644 --- a/src/Functional/Retry.php +++ b/src/Functional/Retry.php @@ -29,7 +29,7 @@ * @return mixed Return value of the function * @no-named-arguments */ -function retry(callable $callback, $retries, Traversable $delaySequence = null) +function retry(callable $callback, $retries, ?Traversable $delaySequence = null) { InvalidArgumentException::assertIntegerGreaterThanOrEqual($retries, 1, __FUNCTION__, 2); diff --git a/src/Functional/Select.php b/src/Functional/Select.php index 93420357..eb0ab73d 100644 --- a/src/Functional/Select.php +++ b/src/Functional/Select.php @@ -22,7 +22,7 @@ * @return array * @no-named-arguments */ -function select($collection, callable $callback = null) +function select($collection, ?callable $callback = null) { InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1); diff --git a/src/Functional/Some.php b/src/Functional/Some.php index df27e0a8..9ae81ade 100644 --- a/src/Functional/Some.php +++ b/src/Functional/Some.php @@ -22,7 +22,7 @@ * @return bool * @no-named-arguments */ -function some($collection, callable $callback = null) +function some($collection, ?callable $callback = null) { InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1); diff --git a/src/Functional/Tail.php b/src/Functional/Tail.php index 495131d2..33637ffe 100644 --- a/src/Functional/Tail.php +++ b/src/Functional/Tail.php @@ -18,11 +18,11 @@ * Takes an optional callback for filtering the collection. * * @param Traversable|array $collection - * @param callable $callback + * @param callable|null $callback * @return array * @no-named-arguments */ -function tail($collection, callable $callback = null) +function tail($collection, ?callable $callback = null) { InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1); diff --git a/src/Functional/Unique.php b/src/Functional/Unique.php index 656ac2be..cfa12442 100644 --- a/src/Functional/Unique.php +++ b/src/Functional/Unique.php @@ -17,12 +17,12 @@ * Returns an array of unique elements * * @param Traversable|array $collection - * @param callable $callback + * @param callable|null $callback * @param bool $strict * @return array * @no-named-arguments */ -function unique($collection, callable $callback = null, $strict = true) +function unique($collection, ?callable $callback = null, $strict = true) { InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1); From 38356cbfc9f86cf53a4bf52538099030c27e8aa0 Mon Sep 17 00:00:00 2001 From: Michael D Johnson Date: Mon, 9 Dec 2024 00:33:03 -0600 Subject: [PATCH 2/5] Make iterator return types match Iterator PHP 8.4 makes not doing so a deprecation. --- src/Functional/Sequences/ExponentialSequence.php | 10 +++++----- src/Functional/Sequences/LinearSequence.php | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Functional/Sequences/ExponentialSequence.php b/src/Functional/Sequences/ExponentialSequence.php index 0a8b8d47..b550545a 100644 --- a/src/Functional/Sequences/ExponentialSequence.php +++ b/src/Functional/Sequences/ExponentialSequence.php @@ -38,28 +38,28 @@ public function __construct($start, $percentage) $this->percentage = $percentage; } - public function current() + public function current(): int { return $this->value; } - public function next() + public function next(): void { $this->value = (int) \round(\pow($this->start * (1 + $this->percentage / 100), $this->times)); $this->times++; } - public function key() + public function key(): ?int { return null; } - public function valid() + public function valid(): bool { return true; } - public function rewind() + public function rewind(): void { $this->times = 1; $this->value = $this->start; diff --git a/src/Functional/Sequences/LinearSequence.php b/src/Functional/Sequences/LinearSequence.php index ad99dfbb..e7345a6a 100644 --- a/src/Functional/Sequences/LinearSequence.php +++ b/src/Functional/Sequences/LinearSequence.php @@ -34,27 +34,27 @@ public function __construct($start, $amount) $this->amount = $amount; } - public function current() + public function current(): int { return $this->value; } - public function next() + public function next(): void { $this->value += $this->amount; } - public function key() + public function key(): int { return 0; } - public function valid() + public function valid(): bool { return true; } - public function rewind() + public function rewind(): void { $this->value = $this->start; } From ebaacaacde4eeb9ef86d8199165d873cac0d92ec Mon Sep 17 00:00:00 2001 From: Michael D Johnson Date: Mon, 9 Dec 2024 00:54:04 -0600 Subject: [PATCH 3/5] Skip tesetErrorIsThrownAsException for PHP >= 8.4 PHP 8.4 deprecates the use of E_USER_ERROR. This can mangle the exception thrown. So, skip the test in PHP 8.4. It may be possible to fix up what's being thrown, but I haven't found anything. Since using trigger_error for user errors is now deprecated, error_to_exception() should probably be deprecated as well. But that's a greater decision than making sure tests work correctly. --- tests/Functional/ErrorToExceptionTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/Functional/ErrorToExceptionTest.php b/tests/Functional/ErrorToExceptionTest.php index e68893e0..f5280cd8 100644 --- a/tests/Functional/ErrorToExceptionTest.php +++ b/tests/Functional/ErrorToExceptionTest.php @@ -21,6 +21,14 @@ class ErrorToExceptionTest extends AbstractTestCase { public function testErrorIsThrownAsException(): void { + if (PHP_VERSION_ID >= 80401) { + // As of PHP 8.4, trigger_error('...', E_USER_ERROR) is deprecated + // This means the actual error thrown is different from the one + // attempted + // See https://3v4l.org/MmFA6. + self::markTestSkipped('Only works with PHP <8.4 due to deprecated E_USER_ERROR'); + } + $origFn = function () { \trigger_error('Some error', E_USER_ERROR); }; From bdfef5f03589778dcf50f052f3d4c83f45815280 Mon Sep 17 00:00:00 2001 From: Michael D Johnson Date: Mon, 9 Dec 2024 00:57:10 -0600 Subject: [PATCH 4/5] Fix message matching in RepeatTest::testNegativeRepeatedtimes PHP 8.4 has changed the format of __FUNCTION__ for closures, at least. --- tests/Functional/RepeatTest.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/Functional/RepeatTest.php b/tests/Functional/RepeatTest.php index 9ab5e8a4..b58ee698 100644 --- a/tests/Functional/RepeatTest.php +++ b/tests/Functional/RepeatTest.php @@ -45,8 +45,13 @@ public function test(): void public function testNegativeRepeatedTimes(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage( - 'Functional\{closure}() expects parameter 1 to be positive integer, negative integer given' + + // See https://3v4l.org/Ms79G for message formats + // See https://regex101.com/r/hTvW3o/1 for regex setup + $this->expectExceptionMessageMatches( + '/(Functional\\\\{closure}' // PHP < 8.4 + . '|{closure:Functional\\\\repeat\(\):[0-9]+})' // PHP 8.4+ + . '\(\) expects parameter 1 to be positive integer, negative integer given/' ); repeat([$this->repeated, 'foo'])(-1); From 885f28b1c482c54d17c77a761f28a6e36ea5ac49 Mon Sep 17 00:00:00 2001 From: Michael D Johnson Date: Mon, 9 Dec 2024 01:08:50 -0600 Subject: [PATCH 5/5] Force doubles to ints in group() PHP 8.4 deprecates the implicit conversion from doubles to ints when doing so would lose precision. However, disallowing doubles as array keys in Functional would be a BC break. --- src/Functional/Group.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Functional/Group.php b/src/Functional/Group.php index af2ed3a1..0a533d44 100644 --- a/src/Functional/Group.php +++ b/src/Functional/Group.php @@ -32,6 +32,11 @@ function group($collection, callable $callback) InvalidArgumentException::assertValidArrayKey($groupKey, __FUNCTION__); + // Avoid implicit precision-loss from doubles (which cannot be keys) + if (is_numeric($groupKey)) { + $groupKey = intval($groupKey); + } + if (!isset($groups[$groupKey])) { $groups[$groupKey] = []; }