From 69c482079a5ffea9a95f1e02485a07bad21b2efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marti=CC=81n=20Go=CC=81mez?= Date: Fri, 20 Feb 2026 16:02:22 +0100 Subject: [PATCH 1/5] Add Laravel 12 and PHP 8.4 support, drop legacy versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop support for PHP <8.2, Laravel <11, Symfony <6.1, PHPUnit <10 and spatie/url v1. Update CI matrix to test PHP 8.2–8.4 with PHPUnit 10–11. Bump GitHub Actions to latest major versions. Changelog: changed --- .github/workflows/phpstan.yml | 6 +++--- .github/workflows/pint.yml | 6 +++--- .github/workflows/run-tests.yml | 9 +++------ composer.json | 10 +++++----- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 25daa7f..67b1a43 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -11,16 +11,16 @@ jobs: name: phpstan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.2' coverage: none - name: Install composer dependencies - uses: ramsey/composer-install@v1 + uses: ramsey/composer-install@v2 - name: Run PHPStan run: ./vendor/bin/phpstan analyze -c phpstan.neon.dist --error-format=github diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml index 970633c..ef2fadf 100644 --- a/.github/workflows/pint.yml +++ b/.github/workflows/pint.yml @@ -8,14 +8,14 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} - name: Fix PHP code style issues - uses: aglipanci/laravel-pint-action@1.0.0 + uses: aglipanci/laravel-pint-action@2.5 - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v4 + uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: Fix styling diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 3882faa..9f9f3a4 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,18 +13,15 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest] - php: [8.0, 8.1, 8.2] - phpunit: [^8.5.23, 9.*, 10.*] + php: [8.2, 8.3, 8.4] + phpunit: [10.*, 11.*] stability: [prefer-lowest, prefer-stable] - exclude: - - php: 8.0 - phpunit: 10.* name: P${{ matrix.php }} - PHPUnit ${{ matrix.phpunit }} - ${{ matrix.stability }} - ${{ matrix.os }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/composer.json b/composer.json index b920dad..76833e8 100644 --- a/composer.json +++ b/composer.json @@ -25,11 +25,11 @@ } ], "require": { - "php": "^8.0.2", - "illuminate/macroable": "^7.0|^8.0|^9.0|^10.0|^11.0", - "phpunit/phpunit": "^8.3|^9.0|^10.0", - "spatie/url": "^1.3.4|^2.0", - "symfony/dom-crawler": "^5.4|^6.1" + "php": "^8.2", + "illuminate/macroable": "^11.0|^12.0", + "phpunit/phpunit": "^10.0|^11.0", + "spatie/url": "^2.0", + "symfony/dom-crawler": "^6.1|^7.0" }, "require-dev": { "laravel/pint": "^1.0", From 6a8fba4f423e002281cd629a1d33e0aaeabbd676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marti=CC=81n=20Go=CC=81mez?= Date: Fri, 20 Feb 2026 16:05:31 +0100 Subject: [PATCH 2/5] Fix SimpleSerializer type error and pint workflow permissions Accept Stringable in formatUrl() to handle Spatie\Url\Url objects. Add contents:write permission to pint workflow for auto-commit. Changelog: fixed --- .github/workflows/pint.yml | 3 +++ src/SnapshotFormatters/SimpleSerializer.php | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml index ef2fadf..60d2457 100644 --- a/.github/workflows/pint.yml +++ b/.github/workflows/pint.yml @@ -2,6 +2,9 @@ name: Fix PHP code style issues on: [push] +permissions: + contents: write + jobs: php-code-styling: runs-on: ubuntu-latest diff --git a/src/SnapshotFormatters/SimpleSerializer.php b/src/SnapshotFormatters/SimpleSerializer.php index 4b93c3d..7e4f977 100644 --- a/src/SnapshotFormatters/SimpleSerializer.php +++ b/src/SnapshotFormatters/SimpleSerializer.php @@ -6,6 +6,7 @@ use Raiolanetworks\PluginSEOTest\SEOData; use Raiolanetworks\PluginSEOTest\Tags\TagCollection; +use Stringable; class SimpleSerializer implements SnapshotSerializer { @@ -61,12 +62,12 @@ protected function formatIfUrl(?string $url): ?string return $this->formatUrl($url); } - protected function formatUrl(?string $url): ?string + protected function formatUrl(string|Stringable|null $url): ?string { if (! $url) { return null; } - return preg_replace('/\d+/', '{id}', $url); + return preg_replace('/\d+/', '{id}', (string) $url); } } From ae702f6065e639f7ea1ed1144787d6558aa1eeec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marti=CC=81n=20Go=CC=81mez?= Date: Fri, 20 Feb 2026 16:05:58 +0100 Subject: [PATCH 3/5] Apply pint code style fixes Changelog: fixed --- src/Parser/HTMLParser.php | 10 ++--- src/SnapshotFormatters/SimpleSerializer.php | 18 ++++---- src/Support/ArrayPluck.php | 6 +-- src/Support/Memo.php | 2 +- src/Tags/TagCollection.php | 2 +- src/TestSEO.php | 8 ++-- tests/ArrayPluckTest.php | 14 +++--- tests/AssertionsTest.php | 20 ++++----- tests/SEODataTest.php | 6 +-- tests/SnapshotTest.php | 48 ++++++++++----------- tests/Tags/TagCollectionTest.php | 2 +- 11 files changed, 68 insertions(+), 68 deletions(-) diff --git a/src/Parser/HTMLParser.php b/src/Parser/HTMLParser.php index 378f9a3..f1cd437 100644 --- a/src/Parser/HTMLParser.php +++ b/src/Parser/HTMLParser.php @@ -23,7 +23,7 @@ public function grabTextFrom(string $xpath): ?string } /** - * @param string|array $attributes + * @param string|array $attributes * @return string|array|null */ public function grabAttributeFrom(string $xpath, $attributes) @@ -38,12 +38,12 @@ public function grabAttributeFrom(string $xpath, $attributes) } /** - * @param string|array|null $attribute + * @param string|array|null $attribute */ public function grabMultiple(string $xpath, $attribute = null): array { $result = []; - $nodes = $this->crawler->filterXPath($xpath); + $nodes = $this->crawler->filterXPath($xpath); foreach ($nodes as $node) { $result[] = $attribute !== null ? $this->getArgumentsFromNode($node, $attribute) : $node->textContent; @@ -53,8 +53,8 @@ public function grabMultiple(string $xpath, $attribute = null): array } /** - * @param DOMElement|DOMNode|null $element - * @param string|array $attributes + * @param DOMElement|DOMNode|null $element + * @param string|array $attributes * @return string|array */ private function getArgumentsFromNode($element, $attributes) diff --git a/src/SnapshotFormatters/SimpleSerializer.php b/src/SnapshotFormatters/SimpleSerializer.php index 7e4f977..de890d8 100644 --- a/src/SnapshotFormatters/SimpleSerializer.php +++ b/src/SnapshotFormatters/SimpleSerializer.php @@ -13,24 +13,24 @@ class SimpleSerializer implements SnapshotSerializer public function toArray(SEOData $data): array { return [ - 'title' => $data->title(), - 'description' => $data->description(), - 'robots' => (string) $data->robots(), - 'canonical' => $this->formatUrl($data->canonical()), - 'pagination' => [ + 'title' => $data->title(), + 'description' => $data->description(), + 'robots' => (string) $data->robots(), + 'canonical' => $this->formatUrl($data->canonical()), + 'pagination' => [ 'prev' => $this->formatUrl($data->prev()), 'next' => $this->formatUrl($data->next()), ], 'relAltHreflang' => array_map( fn (array $item) => [ 'hreflang' => $item['hreflang'], - 'href' => $this->formatUrl($item['href']), + 'href' => $this->formatUrl($item['href']), ], $data->alternateHrefLang()->jsonSerialize() ), - 'h1' => $data->h1s(), - 'opengraph' => $this->formatTagCollection($data->openGraph()), - 'twitter' => $this->formatTagCollection($data->twitter()), + 'h1' => $data->h1s(), + 'opengraph' => $this->formatTagCollection($data->openGraph()), + 'twitter' => $this->formatTagCollection($data->twitter()), ]; } diff --git a/src/Support/ArrayPluck.php b/src/Support/ArrayPluck.php index a92d12b..7f82ea1 100644 --- a/src/Support/ArrayPluck.php +++ b/src/Support/ArrayPluck.php @@ -7,7 +7,7 @@ class ArrayPluck { /** - * @param array> $items + * @param array> $items */ public function __construct(private array $items) {} @@ -16,12 +16,12 @@ public function __construct(private array $items) {} */ public function __invoke(string $key, string $value): array { - $array = $this->items; + $array = $this->items; $results = []; foreach ($array as $item) { $itemValue = $item[$value]; - $itemKey = $item[$key]; + $itemKey = $item[$key]; if (! isset($results[$itemKey])) { $results[$itemKey] = $itemValue; diff --git a/src/Support/Memo.php b/src/Support/Memo.php index 2e22cc2..87e3710 100644 --- a/src/Support/Memo.php +++ b/src/Support/Memo.php @@ -11,7 +11,7 @@ trait Memo /** * @template TValue * - * @param callable(): TValue $value + * @param callable(): TValue $value * @return TValue */ protected function memo(string $key, callable $value): mixed diff --git a/src/Tags/TagCollection.php b/src/Tags/TagCollection.php index e389505..f99ef80 100644 --- a/src/Tags/TagCollection.php +++ b/src/Tags/TagCollection.php @@ -18,7 +18,7 @@ public function __construct( public function get(string $property) { // Normalize property - $property = $this->prefix . ltrim($property, $this->prefix); + $property = $this->prefix.ltrim($property, $this->prefix); return $this->metadata[$property] ?? null; } diff --git a/src/TestSEO.php b/src/TestSEO.php index 0b73a53..09c3b77 100644 --- a/src/TestSEO.php +++ b/src/TestSEO.php @@ -18,8 +18,8 @@ class TestSEO implements JsonSerializable public function __construct(string $content, ?SnapshotSerializer $snapshotSerializer = null) { - $html = new HTMLParser($content); - $this->data = new SEOData($html); + $html = new HTMLParser($content); + $this->data = new SEOData($html); $this->snapshotSerializer = $snapshotSerializer ?? new SimpleSerializer; } @@ -52,8 +52,8 @@ public function assertRobotsIsNoIndexNoFollow(): self { $robots = $this->data->robots(); - Assert::assertTrue($robots->noindex(), 'Robots should be noindex and nofollow, but found: ' . (string) $robots); - Assert::assertTrue($robots->nofollow(), 'Robots should be noindex and nofollow, but found: ' . (string) $robots); + Assert::assertTrue($robots->noindex(), 'Robots should be noindex and nofollow, but found: '.(string) $robots); + Assert::assertTrue($robots->nofollow(), 'Robots should be noindex and nofollow, but found: '.(string) $robots); return $this; } diff --git a/tests/ArrayPluckTest.php b/tests/ArrayPluckTest.php index 395778d..b4b93d4 100644 --- a/tests/ArrayPluckTest.php +++ b/tests/ArrayPluckTest.php @@ -23,21 +23,21 @@ public function test_should_pluck_array(array $expected, array $items, string $k public static function pluckDataProvider(): array { return [ - 'It plucks the values' => [ + 'It plucks the values' => [ 'expected' => ['foo' => 'bar', 'foo2' => 'bar2'], - 'items' => [['name' => 'foo', 'content' => 'bar'], ['name' => 'foo2', 'content' => 'bar2']], - 'key' => 'name', - 'value' => 'content', + 'items' => [['name' => 'foo', 'content' => 'bar'], ['name' => 'foo2', 'content' => 'bar2']], + 'key' => 'name', + 'value' => 'content', ], 'It groups results into an array when the keys are the same' => [ 'expected' => ['foo' => ['bar', 'bar2'], 'foo2' => 'bar3'], - 'items' => [ + 'items' => [ ['property' => 'foo', 'content' => 'bar'], ['property' => 'foo', 'content' => 'bar2'], ['property' => 'foo2', 'content' => 'bar3'], ], - 'key' => 'property', - 'value' => 'content', + 'key' => 'property', + 'value' => 'content', ], ]; } diff --git a/tests/AssertionsTest.php b/tests/AssertionsTest.php index 03ecdc4..b53dd4a 100644 --- a/tests/AssertionsTest.php +++ b/tests/AssertionsTest.php @@ -13,7 +13,7 @@ class AssertionsTest extends TestCase public function test_should_pass_assertions(): void { // Arrange - $page = file_get_contents(__DIR__ . '/stubs/test-case-2.html'); + $page = file_get_contents(__DIR__.'/stubs/test-case-2.html'); // Act $testSeo = new TestSEO($page); @@ -44,7 +44,7 @@ public function test_should_break_on_assertions_case_2(callable $evaluation): vo $this->expectException(ExpectationFailedException::class); // Arrange - $page = file_get_contents(__DIR__ . '/stubs/test-case-2.html'); + $page = file_get_contents(__DIR__.'/stubs/test-case-2.html'); // Act $testSeo = new TestSEO($page); @@ -56,11 +56,11 @@ public function test_should_break_on_assertions_case_2(callable $evaluation): vo public static function breakAssertionsCase2DataProvider(): array { return [ - 'Empty canonical' => [fn (TestSEO $testSEO) => $testSEO->assertCanonicalIsEmpty()], - 'Wrong canonical' => [fn (TestSEO $testSEO) => $testSEO->assertCanonicalIs('https://testpage.com/en/product/44?asd=1')], - 'Empty Pagination' => [fn (TestSEO $testSEO) => $testSEO->assertPaginationIsEmpty()], - 'Empty Robots' => [fn (TestSEO $testSEO) => $testSEO->assertRobotsIsEmpty()], - 'Wrong Title' => [fn (TestSEO $testSEO) => $testSEO->assertTitleIs('This is my test title')], + 'Empty canonical' => [fn (TestSEO $testSEO) => $testSEO->assertCanonicalIsEmpty()], + 'Wrong canonical' => [fn (TestSEO $testSEO) => $testSEO->assertCanonicalIs('https://testpage.com/en/product/44?asd=1')], + 'Empty Pagination' => [fn (TestSEO $testSEO) => $testSEO->assertPaginationIsEmpty()], + 'Empty Robots' => [fn (TestSEO $testSEO) => $testSEO->assertRobotsIsEmpty()], + 'Wrong Title' => [fn (TestSEO $testSEO) => $testSEO->assertTitleIs('This is my test title')], 'Empty AlternateHreflang' => [fn (TestSEO $testSEO) => $testSEO->assertAlternateHrefLangIsEmpty()], ]; } @@ -68,7 +68,7 @@ public static function breakAssertionsCase2DataProvider(): array public function test_should_pass_assertions_on_empty_case_3(): void { // Arrange - $page = file_get_contents(__DIR__ . '/stubs/test-case-3.html'); + $page = file_get_contents(__DIR__.'/stubs/test-case-3.html'); // Act $testSeo = new TestSEO($page); @@ -89,7 +89,7 @@ public function test_should_break_on_assertions_case_3(callable $evaluation): vo $this->expectException(ExpectationFailedException::class); // Arrange - $page = file_get_contents(__DIR__ . '/stubs/test-case-3.html'); + $page = file_get_contents(__DIR__.'/stubs/test-case-3.html'); // Act $testSeo = new TestSEO($page); @@ -101,7 +101,7 @@ public function test_should_break_on_assertions_case_3(callable $evaluation): vo public static function breakAssertionsCase3DataProvider(): array { return [ - 'More than one h1' => [fn (TestSEO $testSEO) => $testSEO->assertThereIsOnlyOneH1()], + 'More than one h1' => [fn (TestSEO $testSEO) => $testSEO->assertThereIsOnlyOneH1()], 'Has images with no alt' => [fn (TestSEO $testSEO) => $testSEO->assertAllImagesHaveAltText()], ]; } diff --git a/tests/SEODataTest.php b/tests/SEODataTest.php index bc268cc..462c7da 100644 --- a/tests/SEODataTest.php +++ b/tests/SEODataTest.php @@ -23,7 +23,7 @@ public function test_it_parses_html_into_null_instance(): void // Act $parser = new HTMLParser($page); - $seo = new SEOData($parser); + $seo = new SEOData($parser); // Assert $this->assertNull($seo->title()); @@ -45,11 +45,11 @@ public function test_it_parses_html_into_null_instance(): void public function test_it_parses_html_into_instance(): void { // Arrange - $page = file_get_contents(__DIR__ . '/stubs/test.html'); + $page = file_get_contents(__DIR__.'/stubs/test.html'); // Act $parser = new HTMLParser($page); - $seo = new SEOData($parser); + $seo = new SEOData($parser); // Assert $this->assertEquals('This is my test title.', $seo->title()); diff --git a/tests/SnapshotTest.php b/tests/SnapshotTest.php index 8a6dc83..1dc6649 100644 --- a/tests/SnapshotTest.php +++ b/tests/SnapshotTest.php @@ -12,27 +12,27 @@ class SnapshotTest extends TestCase public function test_should_create_the_snapshot(): void { // Arrange - $page = file_get_contents(__DIR__ . '/stubs/test-case-2.html'); + $page = file_get_contents(__DIR__.'/stubs/test-case-2.html'); // Act - $testSeo = new TestSEO($page); + $testSeo = new TestSEO($page); $snapshot = json_decode(json_encode($testSeo), true); // Assert $this->assertEquals([ - 'title' => 'Reviews for product #44 - My Web', - 'canonical' => 'https://testpage.com/en/product/{id}/reviews', - 'description' => 'The product 44 is amazing.', - 'twitter' => [ - 'twitter:card' => 'TwitterFooBar #44', + 'title' => 'Reviews for product #44 - My Web', + 'canonical' => 'https://testpage.com/en/product/{id}/reviews', + 'description' => 'The product 44 is amazing.', + 'twitter' => [ + 'twitter:card' => 'TwitterFooBar #44', 'twitter:image' => 'https://testpage.com/images/products/{id}.jpg', ], - 'opengraph' => [ + 'opengraph' => [ 'og:site_name' => 'OGFooBar', - 'og:image' => 'https://testpage.com/images/products/{id}.jpg', + 'og:image' => 'https://testpage.com/images/products/{id}.jpg', ], - 'robots' => 'nofollow, noindex', - 'pagination' => [ + 'robots' => 'nofollow, noindex', + 'pagination' => [ 'prev' => 'https://testpage.com/en/product/{id}/reviews?page={id}', 'next' => 'https://testpage.com/en/product/{id}/reviews?page={id}', ], @@ -40,43 +40,43 @@ public function test_should_create_the_snapshot(): void ['hreflang' => 'es', 'href' => 'https://testpage.com/es/product/{id}/reviews?page={id}'], ['hreflang' => 'pt', 'href' => 'https://testpage.com/pt/product/{id}/reviews?page={id}'], ], - 'h1' => ['Product #44'], + 'h1' => ['Product #44'], ], $snapshot); } public function test_should_create_the_snapshot_on_empty_data(): void { // Arrange - $page = file_get_contents(__DIR__ . '/stubs/test-case-3.html'); + $page = file_get_contents(__DIR__.'/stubs/test-case-3.html'); // Act - $testSeo = new TestSEO($page); + $testSeo = new TestSEO($page); $snapshot = json_decode(json_encode($testSeo), true); // Assert $this->assertEquals([ - 'title' => 'Update product #44 - My Web', - 'canonical' => null, - 'description' => 'The product 44 is amazing.', - 'twitter' => null, - 'opengraph' => null, - 'robots' => '', - 'pagination' => [ + 'title' => 'Update product #44 - My Web', + 'canonical' => null, + 'description' => 'The product 44 is amazing.', + 'twitter' => null, + 'opengraph' => null, + 'robots' => '', + 'pagination' => [ 'prev' => null, 'next' => null, ], 'relAltHreflang' => [], - 'h1' => ['Update Product #44', 'Second h1. This must be bad.'], + 'h1' => ['Update Product #44', 'Second h1. This must be bad.'], ], $snapshot); } public function test_should_create_the_tag_collection_snapshot_on_array_values(): void { // Arrange - $page = file_get_contents(__DIR__ . '/stubs/test-case-4.html'); + $page = file_get_contents(__DIR__.'/stubs/test-case-4.html'); // Act - $testSeo = new TestSEO($page); + $testSeo = new TestSEO($page); $snapshot = json_decode(json_encode($testSeo), true); // Assert diff --git a/tests/Tags/TagCollectionTest.php b/tests/Tags/TagCollectionTest.php index 95fca0a..47aa27d 100644 --- a/tests/Tags/TagCollectionTest.php +++ b/tests/Tags/TagCollectionTest.php @@ -60,7 +60,7 @@ public function test_it_should_convert_to_array(): void // Assert $this->assertEquals([ - 'twitter:url' => 'https://image.url/', + 'twitter:url' => 'https://image.url/', 'twitter:image' => [ 'https://image.url/here-1.jpg', 'https://image.url/here-2.jpg', From d5f27fc5eef531f5e22a3583f55be18ee153f65d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marti=CC=81n=20Go=CC=81mez?= Date: Fri, 20 Feb 2026 16:07:46 +0100 Subject: [PATCH 4/5] Fix PHPStan baseline namespace and deprecated config Update baseline from old Juampi92\TestSEO namespace to Raiolanetworks\PluginSEOTest. Replace deprecated checkMissingIterableValueType with ignoreErrors identifier. Changelog: fixed --- phpstan-baseline.neon | 11 ++++++++--- phpstan.neon.dist | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 383cc46..d7d958d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,17 +1,17 @@ parameters: ignoreErrors: - - message: "#^Method Juampi92\\\\TestSEO\\\\SEOData\\:\\:charset\\(\\) should return string\\|null but returns array\\\\|string\\|null\\.$#" + message: "#^Method Raiolanetworks\\\\PluginSEOTest\\\\SEOData\\:\\:charset\\(\\) should return string\\|null but returns array\\\\|string\\|null\\.$#" count: 1 path: src/SEOData.php - - message: "#^Method Juampi92\\\\TestSEO\\\\SEOData\\:\\:description\\(\\) should return string\\|null but returns array\\\\|string\\|null\\.$#" + message: "#^Method Raiolanetworks\\\\PluginSEOTest\\\\SEOData\\:\\:description\\(\\) should return string\\|null but returns array\\\\|string\\|null\\.$#" count: 1 path: src/SEOData.php - - message: "#^Parameter \\#1 \\$content of class Juampi92\\\\TestSEO\\\\Tags\\\\Robots constructor expects string, array\\\\|string given\\.$#" + message: "#^Parameter \\#1 \\$content of class Raiolanetworks\\\\PluginSEOTest\\\\Tags\\\\Robots constructor expects string, array\\\\|string given\\.$#" count: 1 path: src/SEOData.php @@ -19,3 +19,8 @@ parameters: message: "#^Parameter \\#1 \\$url of static method Spatie\\\\Url\\\\Url\\:\\:fromString\\(\\) expects string, array\\\\|string given\\.$#" count: 4 path: src/SEOData.php + + - + message: "#^Parameter \\#1 \\$suffix of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringEndsWith\\(\\) expects non\\-empty\\-string, string given\\.$#" + count: 1 + path: src/TestSEO.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 0cb8580..d37add3 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -5,5 +5,6 @@ parameters: - src level: 7 #excludePaths: - checkMissingIterableValueType: false reportUnmatchedIgnoredErrors: false + ignoreErrors: + - identifier: missingType.iterableValue From 4df4c6c03d671c89efe6f43fa204d4fe72ac3304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marti=CC=81n=20Go=CC=81mez?= Date: Fri, 20 Feb 2026 16:12:08 +0100 Subject: [PATCH 5/5] Fix PHPStan baseline, update pint and PHPUnit config - Add conditional return types to HTMLParser::grabAttributeFrom() to properly narrow string vs array returns, eliminating all baseline suppressions - Type assertTitleEndsWith param as non-empty-string - Replace @dataProvider doc-comments with PHP attributes - Update phpunit.xml.dist to PHPUnit 11 schema - Bump laravel/pint dev dependency to ^1.18 Changelog: changed --- composer.json | 2 +- phpstan-baseline.neon | 26 +------------------------- phpunit.xml.dist | 17 +++++++++++------ src/Parser/HTMLParser.php | 17 ++++++++++------- src/TestSEO.php | 3 +++ tests/ArrayPluckTest.php | 5 ++--- tests/AssertionsTest.php | 9 +++------ 7 files changed, 31 insertions(+), 48 deletions(-) diff --git a/composer.json b/composer.json index 76833e8..f61d5a5 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "symfony/dom-crawler": "^6.1|^7.0" }, "require-dev": { - "laravel/pint": "^1.0", + "laravel/pint": "^1.18", "phpstan/phpstan": "^1.8" }, "autoload": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d7d958d..aab4991 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,26 +1,2 @@ parameters: - ignoreErrors: - - - message: "#^Method Raiolanetworks\\\\PluginSEOTest\\\\SEOData\\:\\:charset\\(\\) should return string\\|null but returns array\\\\|string\\|null\\.$#" - count: 1 - path: src/SEOData.php - - - - message: "#^Method Raiolanetworks\\\\PluginSEOTest\\\\SEOData\\:\\:description\\(\\) should return string\\|null but returns array\\\\|string\\|null\\.$#" - count: 1 - path: src/SEOData.php - - - - message: "#^Parameter \\#1 \\$content of class Raiolanetworks\\\\PluginSEOTest\\\\Tags\\\\Robots constructor expects string, array\\\\|string given\\.$#" - count: 1 - path: src/SEOData.php - - - - message: "#^Parameter \\#1 \\$url of static method Spatie\\\\Url\\\\Url\\:\\:fromString\\(\\) expects string, array\\\\|string given\\.$#" - count: 4 - path: src/SEOData.php - - - - message: "#^Parameter \\#1 \\$suffix of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringEndsWith\\(\\) expects non\\-empty\\-string, string given\\.$#" - count: 1 - path: src/TestSEO.php + ignoreErrors: [] diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b222853..1715c3a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,15 +1,20 @@ - - + + src/ - + - + tests - - diff --git a/src/Parser/HTMLParser.php b/src/Parser/HTMLParser.php index f1cd437..bf8eeab 100644 --- a/src/Parser/HTMLParser.php +++ b/src/Parser/HTMLParser.php @@ -23,10 +23,12 @@ public function grabTextFrom(string $xpath): ?string } /** - * @param string|array $attributes - * @return string|array|null + * @template T of string|array + * + * @param T $attributes + * @return (T is string ? string|null : array|null) */ - public function grabAttributeFrom(string $xpath, $attributes) + public function grabAttributeFrom(string $xpath, string|array $attributes) { $nodes = $this->crawler->filterXPath($xpath); @@ -53,11 +55,12 @@ public function grabMultiple(string $xpath, $attribute = null): array } /** - * @param DOMElement|DOMNode|null $element - * @param string|array $attributes - * @return string|array + * @template T of string|array + * + * @param T $attributes + * @return (T is string ? string : array) */ - private function getArgumentsFromNode($element, $attributes) + private function getArgumentsFromNode(DOMElement|DOMNode|null $element, string|array $attributes) { if (! $element || ! ($element instanceof DOMElement)) { return []; diff --git a/src/TestSEO.php b/src/TestSEO.php index 09c3b77..e0310a4 100644 --- a/src/TestSEO.php +++ b/src/TestSEO.php @@ -87,6 +87,9 @@ public function assertTitleContains(string $expected): self return $this; } + /** + * @param non-empty-string $expected + */ public function assertTitleEndsWith(string $expected): self { Assert::assertStringEndsWith($expected, $this->data->title()); diff --git a/tests/ArrayPluckTest.php b/tests/ArrayPluckTest.php index b4b93d4..328ced2 100644 --- a/tests/ArrayPluckTest.php +++ b/tests/ArrayPluckTest.php @@ -4,14 +4,13 @@ namespace Raiolanetworks\PluginSEOTest\Tests; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Raiolanetworks\PluginSEOTest\Support\ArrayPluck; class ArrayPluckTest extends TestCase { - /** - * @dataProvider pluckDataProvider - */ + #[DataProvider('pluckDataProvider')] public function test_should_pluck_array(array $expected, array $items, string $key, string $value): void { $this->assertEqualsCanonicalizing( diff --git a/tests/AssertionsTest.php b/tests/AssertionsTest.php index b53dd4a..b901903 100644 --- a/tests/AssertionsTest.php +++ b/tests/AssertionsTest.php @@ -4,6 +4,7 @@ namespace Raiolanetworks\PluginSEOTest\Tests; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\TestCase; use Raiolanetworks\PluginSEOTest\TestSEO; @@ -36,9 +37,7 @@ public function test_should_pass_assertions(): void $this->assertEquals('https://testpage.com/es/product/44/reviews?page=2', $testSeo->data->alternateHrefLang()->get('es')); } - /** - * @dataProvider breakAssertionsCase2DataProvider - */ + #[DataProvider('breakAssertionsCase2DataProvider')] public function test_should_break_on_assertions_case_2(callable $evaluation): void { $this->expectException(ExpectationFailedException::class); @@ -81,9 +80,7 @@ public function test_should_pass_assertions_on_empty_case_3(): void ->assertAlternateHrefLangIsEmpty(); } - /** - * @dataProvider breakAssertionsCase3DataProvider - */ + #[DataProvider('breakAssertionsCase3DataProvider')] public function test_should_break_on_assertions_case_3(callable $evaluation): void { $this->expectException(ExpectationFailedException::class);