From 6446e5f3a02004599f99c16230b5cb304d3792d7 Mon Sep 17 00:00:00 2001 From: Alexandros Koutroulis Date: Thu, 22 Oct 2026 16:28:32 +0300 Subject: [PATCH 1/3] Fix Integer::fromNumeric bypassing subclass validation fromNumeric() used new self, so PositiveInteger::fromNumeric('-42') and NonNegativeInteger::fromNumeric('-1') returned a plain Integer without running the subclass constructor checks. Use new static instead. Add tests covering fromNumeric for Integer, PositiveInteger and NonNegativeInteger. --- src/Number/Integer.php | 2 +- tests/Number/IntegerTest.php | 20 +++++++++++++++++++ tests/Number/NonNegativeIntegerTest.php | 26 +++++++++++++++++++++++++ tests/Number/PositiveIntegerTest.php | 26 +++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/Number/Integer.php b/src/Number/Integer.php index aa61608..a6015ed 100644 --- a/src/Number/Integer.php +++ b/src/Number/Integer.php @@ -28,7 +28,7 @@ final public function getValue(): int final public static function fromNumeric($value): self { if (is_numeric($value)) { - return new self((int)$value); + return new static((int)$value); } throw new InvalidArgumentException('Value is not numeric.'); diff --git a/tests/Number/IntegerTest.php b/tests/Number/IntegerTest.php index cbbbba9..a1e8317 100644 --- a/tests/Number/IntegerTest.php +++ b/tests/Number/IntegerTest.php @@ -6,6 +6,7 @@ use Epignosis\Types\Number\Integer; use Epignosis\Types\Number\PositiveInteger; +use InvalidArgumentException; use PHPUnit\Framework\TestCase; final class IntegerTest extends TestCase @@ -31,6 +32,25 @@ public function test_canBeCreatedFromFloat(): void $this->assertEquals(42, $result); } + public function test_canBeCreatedFromNegativeNumericString(): void + { + $result = Integer::fromNumeric('-42')->getValue(); + + $this->assertEquals(-42, $result); + } + + public function test_fromNumericReturnsPlainInteger(): void + { + $this->assertSame(Integer::class, get_class(Integer::fromNumeric('42'))); + } + + public function test_CannotBeCreatedFromNonNumericString(): void + { + $this->expectException(InvalidArgumentException::class); + + Integer::fromNumeric('abc'); + } + public function test_IsEqualToSameTypeWithSameValue(): void { $integer1 = new Integer(42); diff --git a/tests/Number/NonNegativeIntegerTest.php b/tests/Number/NonNegativeIntegerTest.php index 0b022be..3e54e3c 100644 --- a/tests/Number/NonNegativeIntegerTest.php +++ b/tests/Number/NonNegativeIntegerTest.php @@ -39,6 +39,32 @@ public function test_canBeCreatedFromFloat(): void $this->assertEquals(42, $result); } + public function test_fromNumericReturnsInstanceOfCalledClass(): void + { + $this->assertInstanceOf(NonNegativeInteger::class, NonNegativeInteger::fromNumeric('42')); + } + + public function test_canBeCreatedFromZeroNumericString(): void + { + $result = NonNegativeInteger::fromNumeric('0')->getValue(); + + $this->assertEquals(0, $result); + } + + public function test_CannotBeCreatedFromNegativeNumericString(): void + { + $this->expectException(InvalidArgumentException::class); + + NonNegativeInteger::fromNumeric('-42'); + } + + public function test_CannotBeCreatedFromNegativeFloat(): void + { + $this->expectException(InvalidArgumentException::class); + + NonNegativeInteger::fromNumeric(-42.3); + } + public function test_CannotBeCreatedFromNegativeInteger(): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Number/PositiveIntegerTest.php b/tests/Number/PositiveIntegerTest.php index 0c4bdb4..ec657e5 100644 --- a/tests/Number/PositiveIntegerTest.php +++ b/tests/Number/PositiveIntegerTest.php @@ -73,4 +73,30 @@ public function test_isNotEqualToDifferentType(): void $this->assertFalse($integer1->equals($integer2)); $this->assertFalse($integer2->equals($integer1)); } + + public function test_fromNumericReturnsInstanceOfCalledClass(): void + { + $this->assertInstanceOf(PositiveInteger::class, PositiveInteger::fromNumeric('42')); + } + + public function test_CannotBeCreatedFromNegativeNumericString(): void + { + $this->expectException(InvalidArgumentException::class); + + PositiveInteger::fromNumeric('-42'); + } + + public function test_CannotBeCreatedFromZeroNumericString(): void + { + $this->expectException(InvalidArgumentException::class); + + PositiveInteger::fromNumeric('0'); + } + + public function test_CannotBeCreatedFromNegativeFloat(): void + { + $this->expectException(InvalidArgumentException::class); + + PositiveInteger::fromNumeric(-42.3); + } } From c752dddcd59d6c375fee9776750a3e84cde52fe2 Mon Sep 17 00:00:00 2001 From: Alexandros Koutroulis Date: Thu, 22 Oct 2026 16:29:43 +0300 Subject: [PATCH 2/3] Require PHP 8.0 or newer Bump the composer platform to 8.0 and declare a php >=8.0 requirement. Refresh composer.lock content hash. --- composer.json | 5 ++++- composer.lock | 14 ++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 0d2029a..aacdbde 100644 --- a/composer.json +++ b/composer.json @@ -14,9 +14,12 @@ ], "config": { "platform": { - "php": "7.4" + "php": "8.0" } }, + "require": { + "php": ">=8.0" + }, "require-dev": { "phpunit/phpunit": "*", "vimeo/psalm": "^5.1" diff --git a/composer.lock b/composer.lock index 79257bb..e0a8c26 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "eee0ba07948c8ad3aaffacb67e046a41", + "content-hash": "6dedaaddfb6da2e880f85085959ed001", "packages": [], "packages-dev": [ { @@ -3755,13 +3755,15 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [], + "platform": { + "php": ">=8.0" + }, + "platform-dev": {}, "platform-overrides": { - "php": "7.4" + "php": "8.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.9.0" } From 5a484e5bb8424847efaf97c6d996078ca1858070 Mon Sep 17 00:00:00 2001 From: Alexandros Koutroulis Date: Thu, 22 Oct 2026 16:30:58 +0300 Subject: [PATCH 3/3] Run CI on PHP 8.0 to 8.5 Drop PHP 7.4 from the matrix, add 8.4 and 8.5, and run mutation tests on every version now that the 7.4 exclusion is moot. --- .github/workflows/code-quality.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/code-quality.yaml b/.github/workflows/code-quality.yaml index cabf0a2..233ef1a 100644 --- a/.github/workflows/code-quality.yaml +++ b/.github/workflows/code-quality.yaml @@ -23,7 +23,7 @@ jobs: name: "Code Quality (PHP${{matrix.php_versions}})" strategy: matrix: - php_versions: ['7.4', '8.0', '8.1', '8.2', '8.3'] + php_versions: ['8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] fail-fast: true runs-on: ubuntu-latest timeout-minutes: 15 @@ -54,5 +54,4 @@ jobs: run: ./vendor/bin/phpunit - name: Mutation Tests - if: matrix.php_versions != '7.4' run: infection run -c infection.dist.json