diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2c581fd1..a361c88b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,12 +1,26 @@ name: Test -on: [push, pull_request] +on: + pull_request: + branches: ["main", "master"] + paths: + - "src/**" + - "tests/**" + - "composer.json" + - ".github/workflows/test.yaml" + push: + branches: ["main", "master"] + paths: + - "src/**" + - "tests/**" + - "composer.json" + - ".github/workflows/test.yaml" jobs: test: name: PHP ${{ matrix.php-version }} (${{ matrix.experimental && 'experimental' || 'full support' }}) - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false @@ -17,15 +31,19 @@ jobs: - 7.3 - 7.4 - 8.0 + - 8.1 + - 8.2 + - 8.3 + - 8.4 experimental: [false] include: - - php-version: 8.1 + - php-version: 8.5 experimental: true continue-on-error: ${{ matrix.experimental }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v5 - name: Install PHP with extensions uses: shivammathur/setup-php@v2 @@ -35,7 +53,7 @@ jobs: tools: composer:v2 - name: Install Composer dependencies - uses: ramsey/composer-install@v1 + uses: ramsey/composer-install@v3 with: composer-options: --prefer-dist continue-on-error: ${{ matrix.experimental }} @@ -45,6 +63,7 @@ jobs: composer require pcov/clobber vendor/bin/pcov clobber continue-on-error: true + if: ${{ matrix.php-version == '7.1' }} - name: Run Tests run: composer tests @@ -53,3 +72,5 @@ jobs: - name: Check coding style run: composer coding-style continue-on-error: ${{ matrix.experimental }} + env: + PHP_CS_FIXER_IGNORE_ENV: 1 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/Functional.php b/src/Functional/Functional.php index 2856d6a1..5b9fc761 100644 --- a/src/Functional/Functional.php +++ b/src/Functional/Functional.php @@ -12,7 +12,6 @@ final class Functional { - /** * @see \Function\ary */ diff --git a/src/Functional/Group.php b/src/Functional/Group.php index af2ed3a1..3cf054bf 100644 --- a/src/Functional/Group.php +++ b/src/Functional/Group.php @@ -21,7 +21,7 @@ * @return array * @no-named-arguments */ -function group($collection, callable $callback) +function group($collection, callable $callback): array { InvalidArgumentException::assertCollection($collection, __FUNCTION__, 1); @@ -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 = (int) $groupKey; + } + if (!isset($groups[$groupKey])) { $groups[$groupKey] = []; } 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/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; } 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); diff --git a/tests/Functional/ErrorToExceptionTest.php b/tests/Functional/ErrorToExceptionTest.php index e68893e0..14c20b65 100644 --- a/tests/Functional/ErrorToExceptionTest.php +++ b/tests/Functional/ErrorToExceptionTest.php @@ -22,7 +22,7 @@ class ErrorToExceptionTest extends AbstractTestCase public function testErrorIsThrownAsException(): void { $origFn = function () { - \trigger_error('Some error', E_USER_ERROR); + \trigger_error('Some error', E_USER_DEPRECATED); }; $fn = error_to_exception($origFn); diff --git a/tests/Functional/GroupTest.php b/tests/Functional/GroupTest.php index fd9fe353..62e41749 100644 --- a/tests/Functional/GroupTest.php +++ b/tests/Functional/GroupTest.php @@ -118,4 +118,28 @@ public function testPassNonCallable(): void $this->expectCallableArgumentError('Functional\group', 2); group($this->list, 'undefinedFunction'); } + + public function testFloatGroupKeysAreBeingCastToInteger(): void + { + $values = [5, 10, 11, 15]; + $fn = function ($v, $k, $collection) { + return $v / 5; + }; + + $actual = group($values, $fn); + $expected = [ + 1 => [ + 0 => 5 + ], + 2 => [ + 1 => 10, + 2 => 11 + ], + 3 => [ + 3 => 15 + ] + ]; + + self::assertEquals($expected, $actual); + } } diff --git a/tests/Functional/PartialMethodTest.php b/tests/Functional/PartialMethodTest.php index 2417ed90..e52946b1 100644 --- a/tests/Functional/PartialMethodTest.php +++ b/tests/Functional/PartialMethodTest.php @@ -14,7 +14,6 @@ class PartialMethodTest extends AbstractPartialTestCase { - public function testWithNoArgs(): void { $method = partial_method('execute'); diff --git a/tests/Functional/RepeatTest.php b/tests/Functional/RepeatTest.php index 9ab5e8a4..de60cb89 100644 --- a/tests/Functional/RepeatTest.php +++ b/tests/Functional/RepeatTest.php @@ -12,6 +12,7 @@ use Functional\Exceptions\InvalidArgumentException; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Runner\Version as PHPUnitVersion; use function Functional\repeat; @@ -45,9 +46,23 @@ 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 + if (\version_compare(PHPUnitVersion::id(), '9.0.0') >= 0) { + // PHPUnit 10 changed the wording of the exception message + $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/' + ); + } else { + $this->expectExceptionMessageRegExp( + '/(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); }