From 4181258962fb33c75a795023de8947d606caf5b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimondas=20Rimkevi=C4=8Dius=20=28aka=20MekDrop=29?= Date: Wed, 9 Jul 2025 01:16:31 +0300 Subject: [PATCH] Added phpstan Resolves #54 --- .github/workflows/on-pull-request.yml | 2 + composer.json | 6 +- phpstan.neon | 5 ++ src/ErrorsCollection.php | 12 ++- src/ErrorsTrait.php | 2 +- tests/ErrorsCollectionTest.php | 108 +++++++++++++------------- tests/ErrorsTraitTest.php | 7 ++ 7 files changed, 82 insertions(+), 60 deletions(-) create mode 100644 phpstan.neon diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml index fac84f4..7b41eee 100644 --- a/.github/workflows/on-pull-request.yml +++ b/.github/workflows/on-pull-request.yml @@ -33,6 +33,8 @@ jobs: run: composer install --no-progress --prefer-dist --optimize-autoloader - name: phpcs run: composer phpcs + - name: phpstan + run: composer phpstan - name: PHPUnit run: composer test diff --git a/composer.json b/composer.json index 53c52ac..0affc3f 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,8 @@ "type": "library", "require-dev": { "phpunit/phpunit": "^12.0", - "squizlabs/php_codesniffer": "^3.9" + "squizlabs/php_codesniffer": "^3.9", + "phpstan/phpstan": "^2" }, "require": { "ext-json": "*", @@ -35,6 +36,7 @@ "scripts": { "test": "phpunit", "phpcs": "phpcs", - "phpcbf": "phpcbf" + "phpcbf": "phpcbf", + "phpstan": "phpstan analyse --memory-limit=1G" } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..1cd333b --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 8 + paths: + - src + - tests diff --git a/src/ErrorsCollection.php b/src/ErrorsCollection.php index 8c389e8..c2f1119 100644 --- a/src/ErrorsCollection.php +++ b/src/ErrorsCollection.php @@ -10,12 +10,15 @@ /** * Collection of errors + * + * @implements ArrayAccess */ class ErrorsCollection implements ArrayAccess, Countable, JsonSerializable, Stringable { /** * Errors data */ + /** @var array */ private array $errors = []; /** @@ -158,29 +161,34 @@ public function count(): int public function __serialize(): array { + /** @var array{ParamsMode, array} */ return [ $this->mode, $this->errors ]; } + /** + * @param array{0: ParamsMode, 1: array} $data + */ public function __unserialize(array $data): void { [$this->mode, $this->errors] = $data; } /** - * @inheritDoc + * @return array */ public function jsonSerialize(): array { + /** @var array */ return $this->errors; } /** * Export data to array * - * @return array + * @return array */ public function toArray(): array { diff --git a/src/ErrorsTrait.php b/src/ErrorsTrait.php index 340770f..efec28e 100644 --- a/src/ErrorsTrait.php +++ b/src/ErrorsTrait.php @@ -26,7 +26,7 @@ public function __construct() * * @param bool $asHTML Format using HTML? * - * @return array|string an array of errors + * @return array|string an array of errors */ public function getErrors(bool $asHTML = true): array|string { diff --git a/tests/ErrorsCollectionTest.php b/tests/ErrorsCollectionTest.php index 074fadb..5360878 100644 --- a/tests/ErrorsCollectionTest.php +++ b/tests/ErrorsCollectionTest.php @@ -18,7 +18,7 @@ public function testDefaultConstructorParams(): void $this->assertSame( ParamsMode::Mode1, $instance->mode, - message: 'When creating ErrorsCollection instance default mode should be MODE_1_PARAM, but isn\'t' + 'When creating ErrorsCollection instance default mode should be MODE_1_PARAM, but isn\'t' ); } @@ -29,70 +29,70 @@ public function testConstructorParams(ParamsMode $mode): void $this->assertSame( $mode, $instance->mode, - message: 'Mode ' . $mode->name . ' is different after creating instance' + 'Mode ' . $mode->name . ' is different after creating instance' ); } public function testOffsetExists(): void { $instance = new ErrorsCollection(ParamsMode::Mode2); - $key = crc32(time()); + $key = (string)crc32((string)time()); $this->assertArrayNotHasKey( $key, $instance, - message: 'Random key already exists in array but shouldn\'t' + 'Random key already exists in array but shouldn\'t' ); - $instance->add($key, crc32(time())); + $instance->add($key, (string)crc32((string)time())); $this->assertArrayHasKey( $key, $instance, - message: 'Random key was not found but it should' + 'Random key was not found but it should' ); } public function testOffsetGet(): void { - $offset = crc32(time()); - $data = sha1(time()); + $offset = (string)crc32((string)time()); + $data = sha1((string)time()); $instance = new ErrorsCollection(ParamsMode::Mode2); $instance->add($offset, $data); - $this->assertSame($instance[$offset], $data, message: 'Data is not same #1'); - $this->assertSame($instance->offsetGet($offset), $data, message: 'Data is not same #2'); - $this->assertNotNull($instance[$offset], message: 'Data is not same #3'); - $this->assertNotNull($instance->offsetGet($offset), message: 'Data is not same #4'); + $this->assertSame($instance[$offset], $data, 'Data is not same #1'); + $this->assertSame($instance->offsetGet($offset), $data, 'Data is not same #2'); + $this->assertNotNull($instance[$offset], 'Data is not same #3'); + $this->assertNotNull($instance->offsetGet($offset), 'Data is not same #4'); } public function testOffsetSet(): void { - $offset = crc32(time()); - $data = sha1(time()); + $offset = (string)crc32((string)time()); + $data = sha1((string)time()); $instance = new ErrorsCollection(ParamsMode::Mode2); $instance->add($offset, $data); - $ndata = md5(time()); + $ndata = md5((string)time()); $instance[$offset] = $ndata; - $this->assertSame($instance[$offset], $ndata, message: 'Changed data is not same as readed one #1'); + $this->assertSame($instance[$offset], $ndata, 'Changed data is not same as readed one #1'); - $ndata = soundex($ndata); + $ndata = soundex((string)$ndata); $instance->offsetSet($offset, $ndata); - $this->assertSame($instance[$offset], $ndata, message: 'Changed data is not same as readed one #2'); + $this->assertSame($instance[$offset], $ndata, 'Changed data is not same as readed one #2'); - $offset = md5(microtime(true)); + $offset = md5((string)microtime(true)); - $ndata = metaphone($ndata); + $ndata = metaphone((string)$ndata); $instance[$offset] = $ndata; - $this->assertSame($instance[$offset], $ndata, message: 'Changed data is not same as readed one #3'); + $this->assertSame($instance[$offset], $ndata, 'Changed data is not same as readed one #3'); - $ndata = soundex($ndata); + $ndata = soundex((string)$ndata); $instance->offsetSet($offset, $ndata); - $this->assertSame($instance[$offset], $ndata, message: 'Changed data is not same as readed one #4'); + $this->assertSame($instance[$offset], $ndata, 'Changed data is not same as readed one #4'); } public function testOffsetUnset(): void { - $offset = crc32(time()); + $offset = (string)crc32((string)time()); $instance = new ErrorsCollection(ParamsMode::Mode2); @@ -103,18 +103,18 @@ public function testOffsetUnset(): void public function testIsEmpty(): void { $instance = new ErrorsCollection(); - $this->assertTrue($instance->isEmpty(), message: 'Is not empty after creation'); + $this->assertTrue($instance->isEmpty(), 'Is not empty after creation'); - $instance->add(crc32(time())); - $this->assertNotTrue($instance->isEmpty(), message: 'Is still empty after one element was added'); + $instance->add((string)crc32((string)time())); + $this->assertNotTrue($instance->isEmpty(), 'Is still empty after one element was added'); } public function testClear(): void { $instance = new ErrorsCollection(); - $instance->add(crc32(time())); + $instance->add((string)crc32((string)time())); $instance->clear(); - $this->assertEmpty($instance, message: 'Clear() must clear'); + $this->assertEmpty($instance, 'Clear() must clear'); } public function testStringConversion(): void @@ -123,47 +123,45 @@ public function testStringConversion(): void $this->assertEmpty( (string)$instance, - message: 'Converted to string empty ErrorsCollection must be empty' + 'Converted to string empty ErrorsCollection must be empty' ); $this->assertEmpty( $instance->getHtml(), - message: 'Converted to HTML empty ErrorsCollection must be empty' + 'Converted to HTML empty ErrorsCollection must be empty' ); - $instance->add(crc32(time())); + $instance->add((string)crc32((string)time())); $this->assertNotEmpty( (string)$instance, - message: 'Converted to string not empty ErrorsCollection must be not empty' + 'Converted to string not empty ErrorsCollection must be not empty' ); $this->assertNotEmpty( $instance->getHtml(), - message: 'Converted to HTML not empty ErrorsCollection must be not empty' - ); - - $this->assertIsString( - $instance->getHtml(), - message: 'getHTML must generate strings' + 'Converted to HTML not empty ErrorsCollection must be not empty' ); } public function testCount(): void { $instance = new ErrorsCollection(); - $this->assertCount(0, $instance, message: 'Count is not 0 when collection was just created'); + $this->assertCount(0, $instance, 'Count is not 0 when collection was just created'); - $instance->add(crc32(time())); - $this->assertSame(1, $instance->count(), message: 'Count must be 1 after one element was added'); + $instance->add((string)crc32((string)time())); + $this->assertSame(1, $instance->count(), 'Count must be 1 after one element was added'); - $this->assertCount(1, $instance, message: 'Count function doesn\'t work'); + $this->assertCount(1, $instance, 'Count function doesn\'t work'); } + /** + * @return Generator, expectedKey: int|string}> + */ public static function provideTestAddData(): Generator { yield 'mode1' => [ 'mode' => ParamsMode::Mode1, 'addParams' => [ - md5(time()) + md5((string)time()) ], 'expectedKey' => 0 ]; @@ -171,22 +169,25 @@ public static function provideTestAddData(): Generator yield 'mode2asprefix' => [ 'mode' => ParamsMode::Mode2AsPrefix, 'addParams' => [ - md5(time()) + md5((string)time()) ], 'expectedKey' => 0 ]; - $key = crc32(time()); + $key = (string)crc32((string)time()); yield 'mode2' => [ 'mode' => ParamsMode::Mode2, 'addParams' => [ $key, - md5(time()) + md5((string)time()) ], 'expectedKey' => $key ]; } + /** + * @param string[] $addParams + */ #[DataProvider('provideTestAddData')] public function testAdd(ParamsMode $mode, array $addParams, int|string $expectedKey): void { @@ -202,29 +203,26 @@ public function testSerialization(): void { $instance = new ErrorsCollection(ParamsMode::Mode2); - $instance->add(md5(time()), sha1(time())); - $instance->add(crc32(time()), soundex(time())); + $instance->add(md5((string)time()), sha1((string)time())); + $instance->add((string)crc32((string)time()), soundex((string)time())); $serialized = serialize($instance); $unserialized = unserialize($serialized); $this->assertInstanceOf( ErrorsCollection::class, $unserialized, - message: 'Unserialized data is not ErrorsCollection class type but it should be #1' + 'Unserialized data is not ErrorsCollection class type but it should be #1' ); $this->assertSame( $instance->mode, $unserialized->mode, - message: 'Serialization-unserialization fails #1' + 'Serialization-unserialization fails #1' ); $this->assertSame( $instance->toArray(), $unserialized->toArray(), - message: 'Serialization-unserialization fails #2' + 'Serialization-unserialization fails #2' ); - - $this->assertIsArray($instance->toArray(), message: 'toArray doesn\'t makes an array'); - $this->assertIsString($instance->toJson(), message: 'toJSON doesn\'t makes a string'); } public static function provideConstructorParams(): Generator diff --git a/tests/ErrorsTraitTest.php b/tests/ErrorsTraitTest.php index f4c3f85..cf3239f 100644 --- a/tests/ErrorsTraitTest.php +++ b/tests/ErrorsTraitTest.php @@ -18,6 +18,8 @@ public function testGetErrors(): void { $mock = $this->createFakeInstance(); + assert(method_exists($mock, 'getErrors')); + $this->assertIsArray($mock->getErrors(false)); $this->assertIsString($mock->getErrors(true)); } @@ -26,6 +28,8 @@ public function testGetHtmlErrors(): void { $mock = $this->createFakeInstance(); + assert(method_exists($mock, 'getHtmlErrors')); + $this->assertIsString($mock->getHtmlErrors()); } @@ -33,6 +37,9 @@ public function testHasAndSetError(): void { $mock = $this->createFakeInstance(); + assert(method_exists($mock, 'hasError')); + assert(method_exists($mock, 'setErrors')); + $this->assertIsBool($mock->hasError()); $this->assertFalse($mock->hasError());