diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 3fb0712..3d42914 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -7,30 +7,30 @@ on: jobs: phpstan: name: PHPStan - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.3' + php-version: '8.4' tools: composer:v2 coverage: none env: update: true - name: Install Dependencies - uses: nick-invision/retry@v2 + uses: nick-invision/retry@v3 with: timeout_minutes: 5 max_attempts: 5 command: composer update --no-interaction --no-progress - name: Install PHPStan - uses: nick-invision/retry@v2 + uses: nick-invision/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -41,30 +41,30 @@ jobs: psalm: name: Psalm - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.3' + php-version: '8.4' tools: composer:v2 coverage: none env: update: true - name: Install Dependencies - uses: nick-invision/retry@v2 + uses: nick-invision/retry@v3 with: timeout_minutes: 5 max_attempts: 5 command: composer update --no-interaction --no-progress - name: Install Psalm - uses: nick-invision/retry@v2 + uses: nick-invision/retry@v3 with: timeout_minutes: 5 max_attempts: 5 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ac73b00..2c30df9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,15 +7,15 @@ on: jobs: tests: name: PHP ${{ matrix.php }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: - php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -30,7 +30,7 @@ jobs: run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Install PHP Dependencies - uses: nick-invision/retry@v2 + uses: nick-invision/retry@v3 with: timeout_minutes: 5 max_attempts: 5 diff --git a/Makefile b/Makefile index 07abb41..b56bc42 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,24 @@ install: - @docker run -it -w /data -v ${PWD}:/data:delegated -v ~/.composer:/root/.composer:delegated --entrypoint composer --rm registry.gitlab.com/grahamcampbell/php:8.3-base update - @docker run -it -w /data -v ${PWD}:/data:delegated -v ~/.composer:/root/.composer:delegated --entrypoint composer --rm registry.gitlab.com/grahamcampbell/php:8.3-base bin all update + @docker run -it -w /data -v ${PWD}:/data:delegated -v ~/.composer:/root/.composer:delegated --entrypoint composer --rm registry.gitlab.com/grahamcampbell/php:8.4-base update + @docker run -it -w /data -v ${PWD}:/data:delegated -v ~/.composer:/root/.composer:delegated --entrypoint composer --rm registry.gitlab.com/grahamcampbell/php:8.4-base bin all update phpunit: - @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/phpunit --rm registry.gitlab.com/grahamcampbell/php:8.3-cli + @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/phpunit --rm registry.gitlab.com/grahamcampbell/php:8.4-cli phpstan-analyze: - @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/phpstan --rm registry.gitlab.com/grahamcampbell/php:8.3-cli analyze + @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/phpstan --rm registry.gitlab.com/grahamcampbell/php:8.4-cli analyze phpstan-baseline: - @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/phpstan --rm registry.gitlab.com/grahamcampbell/php:8.3-cli analyze --generate-baseline + @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/phpstan --rm registry.gitlab.com/grahamcampbell/php:8.4-cli analyze --generate-baseline psalm-analyze: - @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/psalm.phar --rm registry.gitlab.com/grahamcampbell/php:8.3-cli + @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/psalm.phar --rm registry.gitlab.com/grahamcampbell/php:8.4-cli psalm-baseline: - @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/psalm.phar --rm registry.gitlab.com/grahamcampbell/php:8.3-cli --set-baseline=psalm-baseline.xml + @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/psalm.phar --rm registry.gitlab.com/grahamcampbell/php:8.4-cli --set-baseline=psalm-baseline.xml psalm-show-info: - @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/psalm.phar --rm registry.gitlab.com/grahamcampbell/php:8.3-cli --show-info=true + @docker run -it -w /data -v ${PWD}:/data:delegated --entrypoint vendor/bin/psalm.phar --rm registry.gitlab.com/grahamcampbell/php:8.4-cli --show-info=true test: phpunit phpstan-analyze psalm-analyze diff --git a/composer.json b/composer.json index 91dd6fb..c077040 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" }, "autoload": { "psr-4": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5310bd2..c1077ba 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,32 +1,44 @@ parameters: ignoreErrors: - - message: "#^Method PhpOption\\\\Option\\:\\:ensure\\(\\) should return PhpOption\\\\Option\\ but returns PhpOption\\\\LazyOption\\\\.$#" + message: '#^Call to function is_callable\(\) with callable\(mixed \.\.\.\)\: PhpOption\\Option\ will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/PhpOption/LazyOption.php + + - + message: '#^Method PhpOption\\Option\:\:ensure\(\) should return PhpOption\\Option\ but returns PhpOption\\LazyOption\\.$#' + identifier: return.type count: 1 path: src/PhpOption/Option.php - - message: "#^Method PhpOption\\\\Option\\:\\:fromReturn\\(\\) has parameter \\$arguments with no value type specified in iterable type array\\.$#" + message: '#^Method PhpOption\\Option\:\:fromReturn\(\) has parameter \$arguments with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue count: 1 path: src/PhpOption/Option.php - - message: "#^Method PhpOption\\\\Option\\:\\:fromReturn\\(\\) should return PhpOption\\\\LazyOption\\ but returns PhpOption\\\\LazyOption\\\\.$#" + message: '#^Method PhpOption\\Option\:\:fromReturn\(\) should return PhpOption\\LazyOption\ but returns PhpOption\\LazyOption\\.$#' + identifier: return.type count: 1 path: src/PhpOption/Option.php - - message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(mixed\\)\\: mixed\\)\\|null, Closure\\(PhpOption\\\\Option\\)\\: T given\\.$#" + message: '#^Parameter \#1 \$callback of function array_map expects \(callable\(mixed\)\: mixed\)\|null, Closure\(PhpOption\\Option\)\: T given\.$#' + identifier: argument.type count: 1 path: src/PhpOption/Option.php - - message: "#^Parameter \\#2 \\$callback of function array_reduce expects callable\\(bool\\|TReturn, mixed\\)\\: \\(bool\\|TReturn\\), Closure\\(mixed, PhpOption\\\\Option\\)\\: \\(bool\\|TReturn\\) given\\.$#" + message: '#^Parameter \#2 \$callback of function array_reduce expects callable\(bool\|TReturn, mixed\)\: \(bool\|TReturn\), Closure\(mixed, PhpOption\\Option\)\: \(bool\|TReturn\) given\.$#' + identifier: argument.type count: 1 path: src/PhpOption/Option.php - - message: "#^Template type S of method PhpOption\\\\Option\\:\\:lift\\(\\) is not referenced in a parameter\\.$#" + message: '#^Template type S of method PhpOption\\Option\:\:lift\(\) is not referenced in a parameter\.$#' + identifier: method.templateTypeNotInParameter count: 1 path: src/PhpOption/Option.php diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 7bf39a8..d04c5aa 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,2 +1,2 @@ - + diff --git a/psalm.xml b/psalm.xml index 7ee17a8..0d745a3 100644 --- a/psalm.xml +++ b/psalm.xml @@ -12,4 +12,8 @@ + + + + diff --git a/tests/PhpOption/Tests/EnsureTest.php b/tests/PhpOption/Tests/EnsureTest.php index 7384cea..82ffc6c 100644 --- a/tests/PhpOption/Tests/EnsureTest.php +++ b/tests/PhpOption/Tests/EnsureTest.php @@ -7,11 +7,6 @@ use PhpOption\Some; use PHPUnit\Framework\TestCase; -/** - * Tests for Option::ensure() method. - * - * @covers Option::ensure - */ class EnsureTest extends TestCase { private static function ensure($value, $noneValue = null): Option diff --git a/tests/PhpOption/Tests/LazyOptionTest.php b/tests/PhpOption/Tests/LazyOptionTest.php index e1b5128..4ecf850 100644 --- a/tests/PhpOption/Tests/LazyOptionTest.php +++ b/tests/PhpOption/Tests/LazyOptionTest.php @@ -178,11 +178,12 @@ public function testFoldLeftRight(): void $callback = function () { }; - $option = self::getMockForAbstractClass(Option::class); + // Use TestOption as a concrete implementation to test with + $option = self::createPartialMock(TestOption::class, ['foldLeft', 'foldRight']); $option->expects(self::once()) ->method('foldLeft') ->with(5, $callback) - ->will(self::returnValue(6)); + ->willReturn(6); $lazyOption = new LazyOption(function () use ($option) { return $option; }); @@ -191,10 +192,166 @@ public function testFoldLeftRight(): void $option->expects(self::once()) ->method('foldRight') ->with(5, $callback) - ->will(self::returnValue(6)); + ->willReturn(6); $lazyOption = new LazyOption(function () use ($option) { return $option; }); self::assertSame(6, $lazyOption->foldRight(5, $callback)); } } + +class TestOption extends Option +{ + private $value; + + public function __construct($value = null) + { + $this->value = $value; + } + + public function get() + { + return $this->value; + } + + public function getOrElse($default) + { + if ($this->isDefined()) { + return $this->value; + } + + return $default; + } + + public function getOrCall($callable) + { + if ($this->isDefined()) { + return $this->value; + } + + return call_user_func($callable); + } + + public function getOrThrow(\Exception $ex) + { + if ($this->isDefined()) { + return $this->value; + } + + throw $ex; + } + + public function isEmpty() + { + return $this->value === null; + } + + public function isDefined() + { + return $this->value !== null; + } + + public function orElse(Option $else) + { + if ($this->isDefined()) { + return $this; + } + + return $else; + } + + public function ifDefined($callable) + { + if ($this->isDefined()) { + call_user_func($callable, $this->value); + } + } + + public function forAll($callable) + { + if ($this->isDefined()) { + call_user_func($callable, $this->value); + } + } + + public function map($callable) + { + if ($this->isDefined()) { + return new self(call_user_func($callable, $this->value)); + } + + return $this; + } + + public function flatMap($callable) + { + if ($this->isDefined()) { + return call_user_func($callable, $this->value); + } + + return $this; + } + + public function filter($callable) + { + if ($this->isDefined() && call_user_func($callable, $this->value)) { + return $this; + } + + return None::create(); + } + + public function filterNot($callable) + { + if ($this->isDefined() && !call_user_func($callable, $this->value)) { + return $this; + } + + return None::create(); + } + + public function select($value) + { + if ($this->isDefined() && $this->value === $value) { + return $this; + } + + return None::create(); + } + + public function reject($value) + { + if ($this->isDefined() && $this->value !== $value) { + return $this; + } + + return None::create(); + } + + public function foldLeft($initialValue, $callable) + { + if ($this->isDefined()) { + return call_user_func($callable, $initialValue, $this->value); + } + + return $initialValue; + } + + public function foldRight($initialValue, $callable) + { + if ($this->isDefined()) { + return call_user_func($callable, $this->value, $initialValue); + } + + return $initialValue; + } + + public function getIterator(): \Traversable + { + if ($this->isDefined()) { + return new \ArrayIterator([$this->value]); + } + + return new \ArrayIterator([]); + } +} diff --git a/vendor-bin/phpstan/composer.json b/vendor-bin/phpstan/composer.json index e24f982..7c3fa69 100644 --- a/vendor-bin/phpstan/composer.json +++ b/vendor-bin/phpstan/composer.json @@ -1,6 +1,6 @@ { "require": { - "phpstan/phpstan": "1.11.7" + "phpstan/phpstan": "2.1.22" }, "config": { "preferred-install": "dist" diff --git a/vendor-bin/psalm/composer.json b/vendor-bin/psalm/composer.json index 5aceaa6..5e841f5 100644 --- a/vendor-bin/psalm/composer.json +++ b/vendor-bin/psalm/composer.json @@ -1,6 +1,6 @@ { "require": { - "psalm/phar": "5.25.0" + "psalm/phar": "6.13.1" }, "config": { "preferred-install": "dist"