From 4d68d854c97c796d7c0d6789b70cfbcb56b94f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Sun, 12 Jul 2026 04:45:40 +0200 Subject: [PATCH] [CI] Pin PHPStan and raise analysis to level max - Add phpstan/phpstan and phpstan/phpstan-webmozart-assert to require-dev - Include the webmozart extension in phpstan.dist.neon - Raise the analysis level from 9 to max - CI: run vendor/bin/phpstan on the resolved dependencies - Use Assert::isMap/allIsMap for nested JSON objects - Use Assert::allString for string lists - Remove redundant assertions and an obsolete @phpstan-ignore - Set Audit and Metric array annotations to array --- .github/workflows/CI.yaml | 11 +++++------ composer.json | 4 +++- phpstan.dist.neon | 5 ++++- src/Analysis.php | 4 +++- src/Analysis/Audit.php | 4 ++-- src/Analysis/AuditRef.php | 1 + src/Analysis/CategoryGroup.php | 1 - src/Analysis/CategoryResult.php | 2 +- src/Analysis/LighthouseResult.php | 14 ++++++++------ src/Analysis/LoadingExperience.php | 5 ++--- src/Analysis/Metric.php | 2 +- src/PageSpeedApi.php | 6 +++++- tests/Analysis/CategoryTest.php | 1 - tests/Analysis/StrategyTest.php | 1 - tests/Fixtures/Factory/AnalysisFactory.php | 7 ++++++- tests/Fixtures/Factory/ConfigSettingsFactory.php | 7 ++++++- tests/Fixtures/Factory/EnvironmentFactory.php | 7 ++++++- .../Factory/LighthouseCategoryResultFactory.php | 7 ++++++- tests/Fixtures/Factory/LighthouseResultFactory.php | 7 ++++++- .../Fixtures/Factory/LoadingExperienceFactory.php | 7 ++++++- tests/Fixtures/Factory/MetricFactory.php | 7 ++++++- tests/PageSpeedApiTest.php | 1 - 22 files changed, 77 insertions(+), 34 deletions(-) diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 6cd8e88..a05ce40 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -63,12 +63,11 @@ jobs: steps: - name: "Git: checkout" uses: actions/checkout@v4 - - name: "PHP: setup ${{ matrix.php-version }}" + - name: "PHP: setup 8.3" uses: shivammathur/setup-php@v2 with: php-version: '8.3' coverage: none - tools: phpstan - name: "Composer: cache config" id: composer-cache run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT @@ -80,12 +79,12 @@ jobs: restore-keys: ${{ runner.os }}-composer- - name: "Composer: validate" run: composer validate --strict - - name: "Composer: install" - run: composer install --prefer-dist --no-progress --no-suggest + - name: "Composer: update" + run: composer update --prefer-dist --no-progress - name: "PHPStan: version" - run: phpstan --version + run: vendor/bin/phpstan --version - name: "PHPStan: analyse" - run: phpstan analyse src/ + run: vendor/bin/phpstan analyse tests: name: "Tests (PHP ${{ matrix.php-version }}, ${{ matrix.dependency-versions }} deps)" diff --git a/composer.json b/composer.json index 29d1425..e36ddcf 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,9 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.56", - "phpunit/phpunit": "^11.2" + "phpunit/phpunit": "^11.2", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-webmozart-assert": "^2.0" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/phpstan.dist.neon b/phpstan.dist.neon index 374036b..846c12c 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -1,5 +1,8 @@ +includes: + - vendor/phpstan/phpstan-webmozart-assert/extension.neon + parameters: - level: 9 + level: max paths: - src/ - tests/ diff --git a/src/Analysis.php b/src/Analysis.php index 46598fc..1919afb 100644 --- a/src/Analysis.php +++ b/src/Analysis.php @@ -40,6 +40,7 @@ public static function create(array $values): self $values['loadingExperience'] ??= null; if (is_array($values['loadingExperience'])) { if (array_key_exists('id', $values['loadingExperience'])) { + Assert::isMap($values['loadingExperience']); $loadingExperience = LoadingExperience::create($values['loadingExperience']); } } @@ -47,6 +48,7 @@ public static function create(array $values): self $values['originLoadingExperience'] ??= null; if (is_array($values['originLoadingExperience'])) { if (array_key_exists('id', $values['originLoadingExperience'])) { + Assert::isMap($values['originLoadingExperience']); $originLoadingExperience = LoadingExperience::create($values['originLoadingExperience']); } } @@ -55,7 +57,7 @@ public static function create(array $values): self Assert::string($values['analysisUTCTimestamp']); Assert::keyExists($values, 'lighthouseResult'); - Assert::isArray($values['lighthouseResult']); + Assert::isMap($values['lighthouseResult']); return new self( $values['id'], diff --git a/src/Analysis/Audit.php b/src/Analysis/Audit.php index 790ccae..128e9d5 100644 --- a/src/Analysis/Audit.php +++ b/src/Analysis/Audit.php @@ -18,8 +18,8 @@ final readonly class Audit { /** - * @param ?array $details - * @param ?array $warnings + * @param ?array $details + * @param ?array $warnings */ public function __construct( public string $id, diff --git a/src/Analysis/AuditRef.php b/src/Analysis/AuditRef.php index 1b6d64c..cf70fbd 100644 --- a/src/Analysis/AuditRef.php +++ b/src/Analysis/AuditRef.php @@ -52,6 +52,7 @@ public static function create(array $values): self $relevantAudits = $values['relevantAudits']; if (is_array($relevantAudits)) { $relevantAudits = array_values($relevantAudits); + Assert::allString($relevantAudits); } return new self( diff --git a/src/Analysis/CategoryGroup.php b/src/Analysis/CategoryGroup.php index 35d6033..4749aca 100644 --- a/src/Analysis/CategoryGroup.php +++ b/src/Analysis/CategoryGroup.php @@ -36,7 +36,6 @@ public static function create(array $values): self Assert::string($values['title']); $values['description'] ??= null; - Assert::keyExists($values, 'description'); Assert::nullOrString($values['description']); return new self( diff --git a/src/Analysis/CategoryResult.php b/src/Analysis/CategoryResult.php index f4c3226..f0260c5 100644 --- a/src/Analysis/CategoryResult.php +++ b/src/Analysis/CategoryResult.php @@ -46,7 +46,7 @@ public static function create(array $values): self Assert::keyExists($values, 'auditRefs'); Assert::isArray($values['auditRefs']); - Assert::allIsArray($values['auditRefs']); + Assert::allIsMap($values['auditRefs']); $values['description'] ??= null; Assert::nullOrString($values['description']); diff --git a/src/Analysis/LighthouseResult.php b/src/Analysis/LighthouseResult.php index 7f44bc8..ccf9933 100644 --- a/src/Analysis/LighthouseResult.php +++ b/src/Analysis/LighthouseResult.php @@ -49,23 +49,25 @@ public static function create(array $values): self Assert::string($values['lighthouseVersion']); Assert::keyExists($values, 'categoryGroups'); - Assert::isArray($values['categoryGroups']); + Assert::isMap($values['categoryGroups']); Assert::keyExists($values, 'categories'); - Assert::isArray($values['categories']); + Assert::isMap($values['categories']); + Assert::allIsMap($values['categories']); Assert::keyExists($values, 'audits'); - Assert::isArray($values['audits']); + Assert::isMap($values['audits']); + Assert::allIsMap($values['audits']); Assert::keyExists($values, 'environment'); - Assert::isArray($values['environment']); + Assert::isMap($values['environment']); Assert::keyExists($values, 'configSettings'); - Assert::isArray($values['configSettings']); + Assert::isMap($values['configSettings']); $categoryGroups = []; foreach ($values['categoryGroups'] as $id => $group) { - Assert::isArray($group); + Assert::isMap($group); $categoryGroups[$id] = ['id' => $id, ...$group]; } diff --git a/src/Analysis/LoadingExperience.php b/src/Analysis/LoadingExperience.php index 4c67a7b..12c84b0 100644 --- a/src/Analysis/LoadingExperience.php +++ b/src/Analysis/LoadingExperience.php @@ -38,7 +38,7 @@ public static function create(array $values): self Assert::string($values['id']); Assert::keyExists($values, 'metrics'); - Assert::isArray($values['metrics']); + Assert::isMap($values['metrics']); Assert::keyExists($values, 'overall_category'); Assert::string($values['overall_category']); @@ -47,12 +47,11 @@ public static function create(array $values): self Assert::string($values['initial_url']); $values['origin_fallback'] ??= null; - Assert::keyExists($values, 'origin_fallback'); Assert::nullOrBoolean($values['origin_fallback']); $metrics = []; foreach ($values['metrics'] as $id => $metric) { - Assert::isArray($metric); + Assert::isMap($metric); $metrics[$id] = ['id' => $id, ...$metric]; } diff --git a/src/Analysis/Metric.php b/src/Analysis/Metric.php index fd00bea..cc36cfd 100644 --- a/src/Analysis/Metric.php +++ b/src/Analysis/Metric.php @@ -18,7 +18,7 @@ final readonly class Metric { /** - * @param array $distributions + * @param array $distributions */ public function __construct( public string $id, diff --git a/src/PageSpeedApi.php b/src/PageSpeedApi.php index 9b91fed..1996d0a 100644 --- a/src/PageSpeedApi.php +++ b/src/PageSpeedApi.php @@ -18,6 +18,7 @@ use SensitiveParameter; use Symfony\Component\HttpClient\HttpClient; use Symfony\Contracts\HttpClient\HttpClientInterface; +use Webmozart\Assert\Assert; final readonly class PageSpeedApi implements PageSpeedApiInterface { @@ -104,6 +105,9 @@ private function get(string $uri, array $parameters = []): array throw new \RuntimeException(sprintf('Unexpected response code "%s" returned by PageSpeed Api.', $response->getStatusCode())); } - return $response->toArray(); + $data = $response->toArray(); + Assert::isMap($data); + + return $data; } } diff --git a/tests/Analysis/CategoryTest.php b/tests/Analysis/CategoryTest.php index f96cf4b..068b926 100644 --- a/tests/Analysis/CategoryTest.php +++ b/tests/Analysis/CategoryTest.php @@ -23,7 +23,6 @@ class CategoryTest extends TestCase public function testValues(): void { $values = Category::values(); - self::assertIsArray($values); self::assertContains('accessibility', $values); self::assertContains('best-practices', $values); self::assertContains('performance', $values); diff --git a/tests/Analysis/StrategyTest.php b/tests/Analysis/StrategyTest.php index 052da14..7895ffe 100644 --- a/tests/Analysis/StrategyTest.php +++ b/tests/Analysis/StrategyTest.php @@ -23,7 +23,6 @@ class StrategyTest extends TestCase public function testValues(): void { $values = Strategy::values(); - self::assertIsArray($values); self::assertContains('desktop', $values); self::assertContains('mobile', $values); } diff --git a/tests/Fixtures/Factory/AnalysisFactory.php b/tests/Fixtures/Factory/AnalysisFactory.php index 9f68769..af1684d 100644 --- a/tests/Fixtures/Factory/AnalysisFactory.php +++ b/tests/Fixtures/Factory/AnalysisFactory.php @@ -13,6 +13,8 @@ namespace PageSpeed\Api\Tests\Fixtures\Factory; +use Webmozart\Assert\Assert; + class AnalysisFactory { /** @@ -29,6 +31,9 @@ public static function createData(array $parameters): array 'lighthouseResult' => LighthouseResultFactory::createData([]), ]; - return array_replace_recursive($data, $parameters); + $data = array_replace_recursive($data, $parameters); + Assert::isMap($data); + + return $data; } } diff --git a/tests/Fixtures/Factory/ConfigSettingsFactory.php b/tests/Fixtures/Factory/ConfigSettingsFactory.php index bc04d32..917649f 100644 --- a/tests/Fixtures/Factory/ConfigSettingsFactory.php +++ b/tests/Fixtures/Factory/ConfigSettingsFactory.php @@ -13,6 +13,8 @@ namespace PageSpeed\Api\Tests\Fixtures\Factory; +use Webmozart\Assert\Assert; + class ConfigSettingsFactory { /** @@ -31,6 +33,9 @@ public static function createData(array $parameters): array 'channel' => 'foo', ]; - return array_replace_recursive($data, $parameters); + $data = array_replace_recursive($data, $parameters); + Assert::isMap($data); + + return $data; } } diff --git a/tests/Fixtures/Factory/EnvironmentFactory.php b/tests/Fixtures/Factory/EnvironmentFactory.php index 267d448..14473d5 100644 --- a/tests/Fixtures/Factory/EnvironmentFactory.php +++ b/tests/Fixtures/Factory/EnvironmentFactory.php @@ -13,6 +13,8 @@ namespace PageSpeed\Api\Tests\Fixtures\Factory; +use Webmozart\Assert\Assert; + class EnvironmentFactory { /** @@ -27,6 +29,9 @@ public static function createData(array $parameters): array 'benchmarkIndex' => 42, ]; - return array_replace_recursive($data, $parameters); + $data = array_replace_recursive($data, $parameters); + Assert::isMap($data); + + return $data; } } diff --git a/tests/Fixtures/Factory/LighthouseCategoryResultFactory.php b/tests/Fixtures/Factory/LighthouseCategoryResultFactory.php index aa07a22..098f9fd 100644 --- a/tests/Fixtures/Factory/LighthouseCategoryResultFactory.php +++ b/tests/Fixtures/Factory/LighthouseCategoryResultFactory.php @@ -13,6 +13,8 @@ namespace PageSpeed\Api\Tests\Fixtures\Factory; +use Webmozart\Assert\Assert; + class LighthouseCategoryResultFactory { /** @@ -61,6 +63,9 @@ public static function createData(array $parameters): array 'score' => 0.9, ]; - return array_replace_recursive($data, $parameters); + $data = array_replace_recursive($data, $parameters); + Assert::isMap($data); + + return $data; } } diff --git a/tests/Fixtures/Factory/LighthouseResultFactory.php b/tests/Fixtures/Factory/LighthouseResultFactory.php index a065257..1f49ce7 100644 --- a/tests/Fixtures/Factory/LighthouseResultFactory.php +++ b/tests/Fixtures/Factory/LighthouseResultFactory.php @@ -13,6 +13,8 @@ namespace PageSpeed\Api\Tests\Fixtures\Factory; +use Webmozart\Assert\Assert; + class LighthouseResultFactory { /** @@ -40,6 +42,9 @@ public static function createData(array $parameters): array 'configSettings' => ConfigSettingsFactory::createData([]), ]; - return array_replace_recursive($data, $parameters); + $data = array_replace_recursive($data, $parameters); + Assert::isMap($data); + + return $data; } } diff --git a/tests/Fixtures/Factory/LoadingExperienceFactory.php b/tests/Fixtures/Factory/LoadingExperienceFactory.php index c48f6c0..5683baf 100644 --- a/tests/Fixtures/Factory/LoadingExperienceFactory.php +++ b/tests/Fixtures/Factory/LoadingExperienceFactory.php @@ -13,6 +13,8 @@ namespace PageSpeed\Api\Tests\Fixtures\Factory; +use Webmozart\Assert\Assert; + class LoadingExperienceFactory { /** @@ -54,6 +56,9 @@ public static function createData(array $parameters): array 'origin_fallback' => true, ]; - return array_replace_recursive($data, $parameters); + $data = array_replace_recursive($data, $parameters); + Assert::isMap($data); + + return $data; } } diff --git a/tests/Fixtures/Factory/MetricFactory.php b/tests/Fixtures/Factory/MetricFactory.php index f4ae551..8c30d6f 100644 --- a/tests/Fixtures/Factory/MetricFactory.php +++ b/tests/Fixtures/Factory/MetricFactory.php @@ -13,6 +13,8 @@ namespace PageSpeed\Api\Tests\Fixtures\Factory; +use Webmozart\Assert\Assert; + class MetricFactory { /** @@ -28,6 +30,9 @@ public static function createData(array $parameters): array 'category' => 'good', ]; - return array_replace_recursive($data, $parameters); + $data = array_replace_recursive($data, $parameters); + Assert::isMap($data); + + return $data; } } diff --git a/tests/PageSpeedApiTest.php b/tests/PageSpeedApiTest.php index 86cd115..ba9e213 100644 --- a/tests/PageSpeedApiTest.php +++ b/tests/PageSpeedApiTest.php @@ -134,7 +134,6 @@ public function testAnalyseFailsWithInvalidCategory(): void self::expectException(\InvalidArgumentException::class); self::expectExceptionMessage('Invalid category "invalid" provided.'); - /** @phpstan-ignore-next-line */ $api->analyse('https://example.com', Strategy::Desktop, 'en_US', ['invalid']); }