From 04438ddb5fd6a04d6c398ae215cd998e321012db Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Mon, 8 Jun 2026 16:58:34 +0100 Subject: [PATCH 01/12] Update workflows to PHP 8.2+ and Symfony 8 --- .github/workflows/php.yml | 24 +++++++++++++++--------- README.md | 2 +- composer.json | 18 ++++++++++-------- rector.php | 24 +++++++++++++++++------- 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 00bbcc3..97a056e 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -14,11 +14,17 @@ jobs: strategy: matrix: - php: ['8.1', '8.2', '8.3'] - symfony: ['6.4', '7.1'] + php: [ '8.2', '8.3', '8.4', '8.5' ] + symfony: [ '7.4', '8.0', '8.1' ] exclude: - - php: '8.1' - symfony: '7.1' + - php: '8.2' + symfony: '8.0' + - php: '8.2' + symfony: '8.1' + - php: '8.3' + symfony: '8.0' + - php: '8.3' + symfony: '8.1' runs-on: ubuntu-latest @@ -27,7 +33,7 @@ jobs: steps: # https://github.com/marketplace/actions/checkout - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 # https://github.com/marketplace/actions/setup-php-action - name: Setup PHP @@ -48,10 +54,10 @@ jobs: - name: Install Symfony ${{ matrix.symfony }} packages run: | - composer update symfony/http-client:${{ matrix.symfony }} - composer update symfony/cache:${{ matrix.symfony }} - composer update symfony/stopwatch:${{ matrix.symfony }} - composer update symfony/property-access:${{ matrix.symfony }} + composer update symfony/http-client:^${{ matrix.symfony }} + composer update symfony/cache:^${{ matrix.symfony }} + composer update symfony/stopwatch:^${{ matrix.symfony }} + composer update symfony/property-access:^${{ matrix.symfony }} - name: Lint PHP files run: | diff --git a/README.md b/README.md index e26b125..a683afc 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Please note this software is in development, usage may change before the 1.0 rel ## Requirements -* PHP 8.1+ +* PHP 8.2+ * [Composer](https://getcomposer.org/) ## Installation diff --git a/composer.json b/composer.json index 8bb53a8..3554806 100755 --- a/composer.json +++ b/composer.json @@ -10,16 +10,17 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "erusev/parsedown-extra": "^0.8", "laminas/laminas-feed": "^2.22", "league/commonmark": "^2.4", "spatie/yaml-front-matter": "^2.0", - "symfony/http-client": "^6.4|^7.1", - "symfony/cache": "^6.4|^7.1", - "symfony/stopwatch": "^6.4|^7.1", - "symfony/property-access": "^6.4|^7.1", - "symfony/monolog-bundle": "^3.7" + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/cache": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0" }, "autoload": { "psr-4": { @@ -33,7 +34,8 @@ "phpunit/phpunit": "^10.5", "squizlabs/php_codesniffer": "^3.10", "phpstan/phpstan": "^1.11", - "roave/security-advisories": "dev-latest" + "roave/security-advisories": "dev-latest", + "rector/rector": "^1.2" }, "scripts": { "phpcs": [ @@ -59,7 +61,7 @@ "extra": { "symfony": { "allow-contrib": false, - "require": "7.2.*" + "require": "8.0.*" } } } diff --git a/rector.php b/rector.php index caace83..d7ec384 100644 --- a/rector.php +++ b/rector.php @@ -2,21 +2,31 @@ declare(strict_types=1); -use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector; use Rector\Config\RectorConfig; use Rector\Set\ValueObject\LevelSetList; +use Rector\Symfony\Set\SymfonySetList; +return RectorConfig::configure() + ->withPaths([__DIR__ . '/src', __DIR__ . '/tests']) + ->withPhpSets(php74: true); + + +/* return static function (RectorConfig $rectorConfig): void { $rectorConfig->paths([ __DIR__ . '/src', - //__DIR__ . '/tests', + __DIR__ . '/tests', ]); - // register a single rule - $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); - - // define sets of rules $rectorConfig->sets([ - LevelSetList::UP_TO_PHP_81 + LevelSetList::UP_TO_PHP_82, + SymfonySetList::SYMFONY_60, + SymfonySetList::SYMFONY_61, + SymfonySetList::SYMFONY_62, + SymfonySetList::SYMFONY_63, + SymfonySetList::SYMFONY_64, + SymfonySetList::SYMFONY_70, + SymfonySetList::SYMFONY_71, ]); }; +*/ \ No newline at end of file From 34e860ecbd966d65a101fd6d16dcf33bbe000abd Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Mon, 8 Jun 2026 17:02:51 +0100 Subject: [PATCH 02/12] Fix to Symfony Cache/Redis incompability --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 97a056e..278ff16 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -40,7 +40,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: mbstring, intl + extensions: mbstring, intl, redis-6.3 ini-values: post_max_size=256M, max_execution_time=180 - name: Check PHP version From 28bf951651fd645d2b703c26c5db916d5439d74d Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Mon, 8 Jun 2026 17:14:31 +0100 Subject: [PATCH 03/12] Rector updates for PHP7.4 --- src/Collection.php | 2 +- src/Http/Http.php | 5 +--- src/Http/RequestTrace.php | 5 +--- src/Http/Response/DecoratedResponseTrait.php | 5 +--- src/Mapper/MapperAbstract.php | 2 +- src/Transform/Data/Concatenate.php | 2 +- src/Validate/ValidationRules.php | 16 ++++++------ src/Version.php | 2 +- tests/Cache/DataCacheTest.php | 2 +- tests/Cache/DataHistoryTest.php | 2 +- tests/Decode/DecoderFactoryTest.php | 26 ++++++++++---------- tests/Decode/GraphQLDecoderTest.php | 2 +- tests/Decode/RssTest.php | 2 +- tests/Http/GraphQLTest.php | 2 +- tests/Http/HttpTest.php | 6 ++--- tests/Mapper/MapCallableTest.php | 10 +++----- tests/Mapper/MapCollectionTest.php | 14 +++++------ tests/Mapper/MapItemTest.php | 2 +- tests/Pagination/PaginationTest.php | 4 +-- tests/Validate/ValidationRulesTest.php | 2 +- 20 files changed, 50 insertions(+), 63 deletions(-) diff --git a/src/Collection.php b/src/Collection.php index f56e8a0..ffe8191 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -68,7 +68,7 @@ public function offsetExists($offset): bool */ public function offsetGet($offset): mixed { - return isset($this->collection[$offset]) ? $this->collection[$offset] : null; + return $this->collection[$offset] ?? null; } /** diff --git a/src/Http/Http.php b/src/Http/Http.php index 94aa017..32ef295 100644 --- a/src/Http/Http.php +++ b/src/Http/Http.php @@ -456,10 +456,7 @@ public function getTotalHttpRequests(): int */ public function statusMessage(int $code): ?string { - if (isset(Response::$statusTexts[$code])) { - return Response::$statusTexts[$code]; - } - return null; + return Response::$statusTexts[$code] ?? null; } /** diff --git a/src/Http/RequestTrace.php b/src/Http/RequestTrace.php index 8af1881..fc4b5ea 100644 --- a/src/Http/RequestTrace.php +++ b/src/Http/RequestTrace.php @@ -35,10 +35,7 @@ public function addRequest(string $requestId, string $uri, string $method, array */ public function getRequest(string $requestId) { - if (isset($this->requests[$requestId])) { - return $this->requests[$requestId]; - } - return null; + return $this->requests[$requestId] ?? null; } /** diff --git a/src/Http/Response/DecoratedResponseTrait.php b/src/Http/Response/DecoratedResponseTrait.php index 4557aac..1157460 100644 --- a/src/Http/Response/DecoratedResponseTrait.php +++ b/src/Http/Response/DecoratedResponseTrait.php @@ -76,10 +76,7 @@ public function getHeaders(bool $throw = true): array public function getHeader(string $header, bool $throw = true): ?array { $headers = $this->decorated->getHeaders($throw); - if (isset($headers[$header])) { - return $headers[$header]; - } - return null; + return $headers[$header] ?? null; } /** diff --git a/src/Mapper/MapperAbstract.php b/src/Mapper/MapperAbstract.php index 051e2fc..2d0aee5 100644 --- a/src/Mapper/MapperAbstract.php +++ b/src/Mapper/MapperAbstract.php @@ -14,7 +14,7 @@ abstract class MapperAbstract private bool $mapToObject = false; private ?string $className = null; - private string $collectionClass = 'Strata\Data\Collection'; + private string $collectionClass = \Strata\Data\Collection::class; private MappingStrategyInterface $strategy; /** diff --git a/src/Transform/Data/Concatenate.php b/src/Transform/Data/Concatenate.php index d5606d3..9ed707d 100644 --- a/src/Transform/Data/Concatenate.php +++ b/src/Transform/Data/Concatenate.php @@ -48,6 +48,6 @@ public function transform($data) */ public function __invoke($objectOrArray, ...$arguments) { - return $this->transform($objectOrArray, ...$arguments); + return $this->transform($objectOrArray); } } diff --git a/src/Validate/ValidationRules.php b/src/Validate/ValidationRules.php index 32a7f1e..4dcbbf6 100644 --- a/src/Validate/ValidationRules.php +++ b/src/Validate/ValidationRules.php @@ -54,21 +54,21 @@ protected function getRuleClass(string $ruleName): string { switch ($ruleName) { case 'array': - return '\Strata\Data\Validate\Rule\ArrayRule'; + return \Strata\Data\Validate\Rule\ArrayRule::class; case 'boolean': - return '\Strata\Data\Validate\Rule\BooleanRule'; + return \Strata\Data\Validate\Rule\BooleanRule::class; case 'email': - return '\Strata\Data\Validate\Rule\EmailRule'; + return \Strata\Data\Validate\Rule\EmailRule::class; case 'in': - return '\Strata\Data\Validate\Rule\InRule'; + return \Strata\Data\Validate\Rule\InRule::class; case 'number': - return '\Strata\Data\Validate\Rule\NumberRule'; + return \Strata\Data\Validate\Rule\NumberRule::class; case 'image': - return '\Strata\Data\Validate\Rule\ImageRule'; + return \Strata\Data\Validate\Rule\ImageRule::class; case 'required': - return '\Strata\Data\Validate\Rule\RequiredRule'; + return \Strata\Data\Validate\Rule\RequiredRule::class; case 'url': - return '\Strata\Data\Validate\Rule\UrlRule'; + return \Strata\Data\Validate\Rule\UrlRule::class; default: throw new ValidatorRulesException(sprintf('Validation rule not recognised: %s', $ruleName)); } diff --git a/src/Version.php b/src/Version.php index 7d41843..3c6caef 100644 --- a/src/Version.php +++ b/src/Version.php @@ -19,7 +19,7 @@ final class Version */ public static function getVersion(): ?string { - if (class_exists('\Composer\InstalledVersions')) { + if (class_exists(\Composer\InstalledVersions::class)) { if (InstalledVersions::isInstalled(self::PACKAGE)) { return InstalledVersions::getPrettyVersion(self::PACKAGE); } diff --git a/tests/Cache/DataCacheTest.php b/tests/Cache/DataCacheTest.php index 5037d62..7295301 100644 --- a/tests/Cache/DataCacheTest.php +++ b/tests/Cache/DataCacheTest.php @@ -165,7 +165,7 @@ public function testTagsNotSupported() $adapter = new FilesystemAdapter('cache', 0, self::CACHE_DIR); $api->setCache($adapter); - $this->expectException('Strata\Data\Exception\CacheException'); + $this->expectException(\Strata\Data\Exception\CacheException::class); $api->setCacheTags(['test-tag']); } diff --git a/tests/Cache/DataHistoryTest.php b/tests/Cache/DataHistoryTest.php index 69332ee..277815b 100644 --- a/tests/Cache/DataHistoryTest.php +++ b/tests/Cache/DataHistoryTest.php @@ -129,7 +129,7 @@ public function testGetLastItem() $this->assertTrue($interval->format('%s') < 30); // Test invalid item field - $this->expectException('Strata\Data\Exception\CacheException'); + $this->expectException(\Strata\Data\Exception\CacheException::class); $history->getLastItem(123, 'invalid'); } diff --git a/tests/Decode/DecoderFactoryTest.php b/tests/Decode/DecoderFactoryTest.php index fa3bfe5..4611948 100644 --- a/tests/Decode/DecoderFactoryTest.php +++ b/tests/Decode/DecoderFactoryTest.php @@ -13,15 +13,15 @@ final class DecoderFactoryTest extends TestCase { public function testFilenameFactory() { - $this->assertInstanceOf('Strata\Data\Decode\Json', DecoderFactory::fromFilename('example.json')); - $this->assertInstanceOf('Strata\Data\Decode\Markdown', DecoderFactory::fromFilename('example.md')); - $this->assertInstanceOf('Strata\Data\Decode\Markdown', DecoderFactory::fromFilename('example.mkd')); - $this->assertInstanceOf('Strata\Data\Decode\Markdown', DecoderFactory::fromFilename('example.markdown')); - $this->assertInstanceOf('Strata\Data\Decode\Rss', DecoderFactory::fromFilename('example.rss')); - $this->assertInstanceOf('Strata\Data\Decode\Rss', DecoderFactory::fromFilename('example.atom')); + $this->assertInstanceOf(\Strata\Data\Decode\Json::class, DecoderFactory::fromFilename('example.json')); + $this->assertInstanceOf(\Strata\Data\Decode\Markdown::class, DecoderFactory::fromFilename('example.md')); + $this->assertInstanceOf(\Strata\Data\Decode\Markdown::class, DecoderFactory::fromFilename('example.mkd')); + $this->assertInstanceOf(\Strata\Data\Decode\Markdown::class, DecoderFactory::fromFilename('example.markdown')); + $this->assertInstanceOf(\Strata\Data\Decode\Rss::class, DecoderFactory::fromFilename('example.rss')); + $this->assertInstanceOf(\Strata\Data\Decode\Rss::class, DecoderFactory::fromFilename('example.atom')); $this->assertNull(DecoderFactory::fromFilename('example.html')); - $this->assertInstanceOf('Strata\Data\Decode\Rss', DecoderFactory::fromFilename('https://example.com/feed.rss')); + $this->assertInstanceOf(\Strata\Data\Decode\Rss::class, DecoderFactory::fromFilename('https://example.com/feed.rss')); $this->assertNull(DecoderFactory::fromFilename('https://example.com/feed')); } @@ -41,27 +41,27 @@ public static function responseDataProvider() return [ 'Json' => [ 'application/json', - 'Strata\Data\Decode\Json' + \Strata\Data\Decode\Json::class ], 'Markdown1' => [ 'text/markdown', - 'Strata\Data\Decode\Markdown' + \Strata\Data\Decode\Markdown::class ], 'Markdown2' => [ 'text/x-markdown', - 'Strata\Data\Decode\Markdown' + \Strata\Data\Decode\Markdown::class ], 'Rss1' => [ 'application/rss+xml', - 'Strata\Data\Decode\Rss' + \Strata\Data\Decode\Rss::class ], 'Rss2' => [ 'text/rss', - 'Strata\Data\Decode\Rss' + \Strata\Data\Decode\Rss::class ], 'Atom' => [ 'application/atom+xml', - 'Strata\Data\Decode\Rss' + \Strata\Data\Decode\Rss::class ], ]; } diff --git a/tests/Decode/GraphQLDecoderTest.php b/tests/Decode/GraphQLDecoderTest.php index 5e74eb1..fddea30 100644 --- a/tests/Decode/GraphQLDecoderTest.php +++ b/tests/Decode/GraphQLDecoderTest.php @@ -37,7 +37,7 @@ public function testInvalid() } EOD; - $this->expectException('Strata\Data\Exception\DecoderException'); + $this->expectException(\Strata\Data\Exception\DecoderException::class); $data = $decoder->decode($data); } } diff --git a/tests/Decode/RssTest.php b/tests/Decode/RssTest.php index 3ecbfdf..730d171 100644 --- a/tests/Decode/RssTest.php +++ b/tests/Decode/RssTest.php @@ -22,7 +22,7 @@ public function testRss() $decoder = new Rss(); $feed = $decoder->decode($response); - $this->assertInstanceOf('Laminas\Feed\Reader\Feed\FeedInterface', $feed); + $this->assertInstanceOf(\Laminas\Feed\Reader\Feed\FeedInterface::class, $feed); $this->assertEquals('News feed generator', $feed->getTitle()); $x = 0; diff --git a/tests/Http/GraphQLTest.php b/tests/Http/GraphQLTest.php index faa85f9..2eff7a4 100644 --- a/tests/Http/GraphQLTest.php +++ b/tests/Http/GraphQLTest.php @@ -119,7 +119,7 @@ public function testPing() $this->assertTrue($graphQL->ping()); - $this->expectException('\Strata\Data\Exception\HttpNotFoundException'); + $this->expectException(\Strata\Data\Exception\HttpNotFoundException::class); $graphQL->ping(); } diff --git a/tests/Http/HttpTest.php b/tests/Http/HttpTest.php index e67151a..5dfa130 100644 --- a/tests/Http/HttpTest.php +++ b/tests/Http/HttpTest.php @@ -200,7 +200,7 @@ public function testRss() $api->setHttpClient(new MockHttpClient($responses)); $feed = $api->getRss('feed.rss'); - $this->assertInstanceOf('Laminas\Feed\Reader\Feed\FeedInterface', $feed); + $this->assertInstanceOf(\Laminas\Feed\Reader\Feed\FeedInterface::class, $feed); $this->assertEquals('News feed generator', $feed->getTitle()); } @@ -212,7 +212,7 @@ public function testError() $api = new Rest('https://example.com/api/'); $api->setHttpClient(new MockHttpClient($responses)); - $this->expectException('\Strata\Data\Exception\HttpNotFoundException'); + $this->expectException(\Strata\Data\Exception\HttpNotFoundException::class); $response = $api->get('test'); } @@ -225,7 +225,7 @@ public function testQuery() $api->setHttpClient(new MockHttpClient($responses)); $response = $api->get('test'); - $this->assertInstanceOf('Symfony\Contracts\HttpClient\ResponseInterface', $response); + $this->assertInstanceOf(\Symfony\Contracts\HttpClient\ResponseInterface::class, $response); $this->assertEquals(200, $response->getStatusCode()); $item = $api->decode($response); diff --git a/tests/Mapper/MapCallableTest.php b/tests/Mapper/MapCallableTest.php index a33db6c..04ba740 100644 --- a/tests/Mapper/MapCallableTest.php +++ b/tests/Mapper/MapCallableTest.php @@ -36,9 +36,7 @@ public function testClosure() { $mapping = [ '[name]' => '[person_name]', - '[code]' => new CallableData(function ($data) { - return $data['person_town'] . '_' . $data['person_name']; - }), + '[code]' => new CallableData(fn($data) => $data['person_town'] . '_' . $data['person_name']), ]; $mapper = new MapItem($mapping); @@ -54,9 +52,7 @@ public function testClosure() public function testClosureWithValues() { - $function = function ($personName, $personTown) { - return $personName . '_' . $personTown; - }; + $function = fn($personName, $personTown) => $personName . '_' . $personTown; $mapping = [ '[name]' => '[person_name]', @@ -125,7 +121,7 @@ public function populateContent(?string $personName) public function testObjectStaticMethod() { $mapping = [ - '[name]' => new CallableData(['Tests\MapCallableTest', 'staticPopulateContent']), + '[name]' => new CallableData([\Tests\MapCallableTest::class, 'staticPopulateContent']), '[age]' => '[person_age]', ]; $mapper = new MapItem($mapping); diff --git a/tests/Mapper/MapCollectionTest.php b/tests/Mapper/MapCollectionTest.php index bc10cef..7219171 100644 --- a/tests/Mapper/MapCollectionTest.php +++ b/tests/Mapper/MapCollectionTest.php @@ -79,7 +79,7 @@ public function testMapToArray() $collection = $mapper->map($this->data, '[items]'); - $this->assertInstanceOf('Strata\Data\Collection', $collection); + $this->assertInstanceOf(\Strata\Data\Collection::class, $collection); $this->assertEquals(3, count($collection)); $this->assertEquals('Banana', $collection[1]['name']); $this->assertEquals(1, $collection->getPagination()->getPage()); @@ -98,7 +98,7 @@ public function testMapToCollection() $mapper = new MapCollection($mapping); $mapper->setTotalResults('[meta_data][total]') ->setResultsPerPage('[meta_data][per_page]') - ->toObject('Tests\Item'); + ->toObject(\Tests\Item::class); $collection = $mapper->map($this->data, '[items]'); $this->assertTrue($collection instanceof Collection); @@ -119,8 +119,8 @@ public function testMapToCustomClass() $mapper = new MapCollection($mapping); $mapper->setTotalResults('[meta_data][total]') ->setResultsPerPage('[meta_data][per_page]') - ->toObject('Tests\Item') - ->setCollectionClass('Tests\MyCollection'); + ->toObject(\Tests\Item::class) + ->setCollectionClass(\Tests\MyCollection::class); $collection = $mapper->map($this->data, '[items]'); $this->assertTrue($collection instanceof MyCollection); @@ -133,10 +133,10 @@ public function testMapToCustomClass() public function testInvalidCustomClass() { - $this->expectException('Strata\Data\Exception\MapperException'); + $this->expectException(\Strata\Data\Exception\MapperException::class); $mapper = new MapCollection([]); - $mapper->toObject('Tests\Item') - ->setCollectionClass('Tests\InvalidCollection'); + $mapper->toObject(\Tests\Item::class) + ->setCollectionClass(\Tests\InvalidCollection::class); } } diff --git a/tests/Mapper/MapItemTest.php b/tests/Mapper/MapItemTest.php index ebca35e..793b9e6 100644 --- a/tests/Mapper/MapItemTest.php +++ b/tests/Mapper/MapItemTest.php @@ -171,7 +171,7 @@ public function testMapClass() 'job_title' => '[occupation]', ]; $mapper = new MapItem($mapping); - $mapper->toObject('Strata\Data\Tests\Person'); + $mapper->toObject(\Strata\Data\Tests\Person::class); $data = [ 'person_name' => 'Fred Bloggs', diff --git a/tests/Pagination/PaginationTest.php b/tests/Pagination/PaginationTest.php index 31792e1..380ed1a 100755 --- a/tests/Pagination/PaginationTest.php +++ b/tests/Pagination/PaginationTest.php @@ -51,7 +51,7 @@ public function testInvalidPageAccess() $pages = new Pagination(); $pages->setTotalResults(1039); - $this->expectException('Strata\Data\Exception\PaginationException'); + $this->expectException(\Strata\Data\Exception\PaginationException::class); $pages->setPage(100); } @@ -111,7 +111,7 @@ public function testIncorrectOrderResultsPerPage() $pages = new Pagination(); $pages->setTotalResults(37); - $this->expectException('Strata\Data\Exception\PaginationException'); + $this->expectException(\Strata\Data\Exception\PaginationException::class); $pages->setPage(9); $pages->setResultsPerPage(4); diff --git a/tests/Validate/ValidationRulesTest.php b/tests/Validate/ValidationRulesTest.php index 0ceabca..d45a0ca 100755 --- a/tests/Validate/ValidationRulesTest.php +++ b/tests/Validate/ValidationRulesTest.php @@ -19,7 +19,7 @@ public function testRulesClass() { $validator = new ValidationRules(['prop' => 'required']); - $this->expectException('Strata\Data\Exception\ValidatorRulesException'); + $this->expectException(\Strata\Data\Exception\ValidatorRulesException::class); $validator->setRules(['prop' => 'invalidRule']); } From dfb836c76fa891739d4e57b9eebb9871ac6e8ca7 Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Mon, 8 Jun 2026 17:25:04 +0100 Subject: [PATCH 04/12] Rector PHP 8.0 updates --- rector.php | 2 +- src/Cache/DataCache.php | 6 +-- src/Cache/DataHistory.php | 25 ++++------ src/Collection.php | 13 ++---- src/DataProviderCommonTrait.php | 2 +- src/DataProviderInterface.php | 3 +- src/Decode/StringNormalizer.php | 5 +- src/Event/DecodeEvent.php | 5 +- src/Event/FailureEvent.php | 5 +- src/Event/RequestEventAbstract.php | 9 +--- src/Event/Subscriber/LoggerSubscriber.php | 5 +- src/Event/Subscriber/StopwatchSubscriber.php | 5 +- src/Exception/HttpException.php | 48 ++++++-------------- src/Http/GraphQL.php | 2 +- src/Http/Http.php | 2 +- src/Http/Response/DecoratedResponseTrait.php | 2 +- src/Http/Response/SuppressErrorResponse.php | 2 +- src/Mapper/MapArray.php | 5 +- src/Mapper/MappingStrategy.php | 2 +- src/Permissions.php | 22 ++++----- src/Query/BuildQuery/BuildGraphQLQuery.php | 7 +-- src/Query/BuildQuery/BuildQuery.php | 5 +- src/Query/QueryAbstract.php | 5 +- src/Query/QueryManager.php | 6 +-- src/Traits/IterableKeyBasedTrait.php | 3 +- src/Transform/Value/DateTimeValue.php | 2 +- src/Validate/ValidationRules.php | 31 +++++-------- src/Validate/ValidatorInterface.php | 3 +- tests/Decode/StringNormalizerTest.php | 2 +- tests/Query/GraphQLTest.php | 2 +- tests/Query/QueryTest.php | 2 +- 31 files changed, 78 insertions(+), 160 deletions(-) diff --git a/rector.php b/rector.php index d7ec384..a91050a 100644 --- a/rector.php +++ b/rector.php @@ -8,7 +8,7 @@ return RectorConfig::configure() ->withPaths([__DIR__ . '/src', __DIR__ . '/tests']) - ->withPhpSets(php74: true); + ->withPhpSets(php80: true); /* diff --git a/src/Cache/DataCache.php b/src/Cache/DataCache.php index f855f8a..a3e053f 100644 --- a/src/Cache/DataCache.php +++ b/src/Cache/DataCache.php @@ -25,7 +25,6 @@ */ class DataCache implements CacheItemPoolInterface, TagAwareAdapterInterface, PruneableInterface { - private CacheItemPoolInterface $cache; protected array $tags = []; protected int $lifetime; @@ -35,12 +34,11 @@ class DataCache implements CacheItemPoolInterface, TagAwareAdapterInterface, Pru * @param CacheItemPoolInterface $cache * @param ?int $defaultLifetime Default cache lifetime for data cache, defaults to 1 hour if not set */ - public function __construct(CacheItemPoolInterface $cache, ?int $defaultLifetime = null) + public function __construct(private CacheItemPoolInterface $cache, ?int $defaultLifetime = null) { if ($defaultLifetime === null) { $defaultLifetime = CacheLifetime::HOUR; } - $this->cache = $cache; $this->setLifetime($defaultLifetime); } @@ -92,7 +90,7 @@ public function isPruneable(): bool public function setTags(array $tags) { if (!$this->isTaggable()) { - throw new CacheException(sprintf('Tags are not supported by your cache adapter %s', get_class($this->cache))); + throw new CacheException(sprintf('Tags are not supported by your cache adapter %s', $this->cache::class)); } $this->tags = $tags; return $this; diff --git a/src/Cache/DataHistory.php b/src/Cache/DataHistory.php index 9be5926..b0809c5 100644 --- a/src/Cache/DataHistory.php +++ b/src/Cache/DataHistory.php @@ -18,9 +18,6 @@ class DataHistory { const CACHE_KEY_PREFIX = 'history_'; - - private CacheItemPoolInterface $cache; - private int $cacheLifetime; private int $maxHistoryDays = 30; private array $historyItems = []; @@ -28,12 +25,10 @@ class DataHistory * Constructor * * @param CacheItemPoolInterface $cache PSR-6 cache - * @param int $lifetime Default cache lifetime for data cache, defaults to 2 months if not set + * @param int $cacheLifetime Default cache lifetime for data cache, defaults to 2 months if not set */ - public function __construct(CacheItemPoolInterface $cache, int $lifetime = 2 * CacheLifetime::MONTH) + public function __construct(private CacheItemPoolInterface $cache, private int $cacheLifetime = 2 * CacheLifetime::MONTH) { - $this->cache = $cache; - $this->cacheLifetime = $lifetime; } /** @@ -120,16 +115,12 @@ public function getLastItem($key, string $field = null) return $item; } - switch ($field) { - case 'updated': - return $item['updated']; - case 'content_hash': - return $item['content_hash']; - case 'metadata': - return $item['metadata']; - default: - throw new CacheException(sprintf('Cannot return history field "%s" since not set', $field)); - } + return match ($field) { + 'updated' => $item['updated'], + 'content_hash' => $item['content_hash'], + 'metadata' => $item['metadata'], + default => throw new CacheException(sprintf('Cannot return history field "%s" since not set', $field)), + }; } /** diff --git a/src/Collection.php b/src/Collection.php index ffe8191..d7a01a8 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -52,10 +52,9 @@ public function getPagination(): Pagination /** * Whether a offset exists * @link https://php.net/manual/en/arrayaccess.offsetexists.php - * @param mixed $offset * @return bool true on success or false on failure. */ - public function offsetExists($offset): bool + public function offsetExists(mixed $offset): bool { return isset($this->collection[$offset]); } @@ -63,10 +62,9 @@ public function offsetExists($offset): bool /** * Offset to retrieve * @link https://php.net/manual/en/arrayaccess.offsetget.php - * @param mixed $offset * @return mixed Can return all value types. */ - public function offsetGet($offset): mixed + public function offsetGet(mixed $offset): mixed { return $this->collection[$offset] ?? null; } @@ -74,11 +72,9 @@ public function offsetGet($offset): mixed /** * Offset to set * @link https://php.net/manual/en/arrayaccess.offsetset.php - * @param mixed $offset - * @param mixed $value * @return void */ - public function offsetSet($offset, $value): void + public function offsetSet(mixed $offset, mixed $value): void { if (is_null($offset)) { $this->collection[] = $value; @@ -90,10 +86,9 @@ public function offsetSet($offset, $value): void /** * Offset to unset * @link https://php.net/manual/en/arrayaccess.offsetunset.php - * @param mixed $offset * @return void */ - public function offsetUnset($offset): void + public function offsetUnset(mixed $offset): void { unset($this->collection[$offset]); } diff --git a/src/DataProviderCommonTrait.php b/src/DataProviderCommonTrait.php index 52902df..93bdef8 100644 --- a/src/DataProviderCommonTrait.php +++ b/src/DataProviderCommonTrait.php @@ -44,7 +44,7 @@ public function setBaseUri(string $baseUri) public function getBaseUri(): string { if (empty($this->baseUri)) { - throw new BaseUriException(sprintf('Base URI not set, please set via %s::setBaseUri()', get_class($this))); + throw new BaseUriException(sprintf('Base URI not set, please set via %s::setBaseUri()', $this::class)); } return $this->baseUri; diff --git a/src/DataProviderInterface.php b/src/DataProviderInterface.php index 4c42a7e..5e1ba42 100644 --- a/src/DataProviderInterface.php +++ b/src/DataProviderInterface.php @@ -73,11 +73,10 @@ public function getDefaultDecoder(): ?DecoderInterface; /** * Decode response * - * @param mixed $response * @param DecoderInterface|null $decoder Optional decoder, if not set uses getDefaultDecoder() * @return mixed */ - public function decode($response, ?DecoderInterface $decoder = null); + public function decode(mixed $response, ?DecoderInterface $decoder = null); /** * Is the cache enabled? diff --git a/src/Decode/StringNormalizer.php b/src/Decode/StringNormalizer.php index 2bc1598..30af379 100644 --- a/src/Decode/StringNormalizer.php +++ b/src/Decode/StringNormalizer.php @@ -12,11 +12,10 @@ class StringNormalizer /** * Take a string or object, and return a string representing the content * - * @param mixed $data * @return string * @throws DecoderException */ - public static function getString($data): string + public static function getString(mixed $data): string { if (is_string($data)) { return $data; @@ -30,7 +29,7 @@ public static function getString($data): string if (method_exists($data, '__toString')) { return $data->__toString(); } - throw new DecoderException(sprintf('Cannot convert object of type "%s" into a string', get_class($data))); + throw new DecoderException(sprintf('Cannot convert object of type "%s" into a string', $data::class)); } throw new DecoderException(sprintf('Cannot convert %s into a string', gettype($data))); } diff --git a/src/Event/DecodeEvent.php b/src/Event/DecodeEvent.php index 5794ae8..a393be3 100644 --- a/src/Event/DecodeEvent.php +++ b/src/Event/DecodeEvent.php @@ -8,11 +8,8 @@ class DecodeEvent extends RequestEventAbstract { const NAME = 'data.request.decode'; - private $decodedData; - - public function __construct($decodedData, string $requestId, string $uri, array $context = []) + public function __construct(private $decodedData, string $requestId, string $uri, array $context = []) { - $this->decodedData = $decodedData; parent::__construct($requestId, $uri, $context); } diff --git a/src/Event/FailureEvent.php b/src/Event/FailureEvent.php index d4b3add..0febfb0 100644 --- a/src/Event/FailureEvent.php +++ b/src/Event/FailureEvent.php @@ -8,11 +8,8 @@ class FailureEvent extends RequestEventAbstract { const NAME = 'data.request.failure'; - private \Exception $exception; - - public function __construct(\Exception $exception, string $requestId, string $uri, array $context = []) + public function __construct(private \Exception $exception, string $requestId, string $uri, array $context = []) { - $this->exception = $exception; parent::__construct($requestId, $uri, $context); } diff --git a/src/Event/RequestEventAbstract.php b/src/Event/RequestEventAbstract.php index d140254..4c30f01 100644 --- a/src/Event/RequestEventAbstract.php +++ b/src/Event/RequestEventAbstract.php @@ -9,15 +9,8 @@ abstract class RequestEventAbstract extends Event { - private string $requestId; - private string $uri; - private array $context; - - public function __construct(string $requestId, string $uri, array $context = []) + public function __construct(private string $requestId, private string $uri, private array $context = []) { - $this->requestId = $requestId; - $this->uri = $uri; - $this->context = $context; } /** diff --git a/src/Event/Subscriber/LoggerSubscriber.php b/src/Event/Subscriber/LoggerSubscriber.php index ec2ca31..3aaf2a7 100644 --- a/src/Event/Subscriber/LoggerSubscriber.php +++ b/src/Event/Subscriber/LoggerSubscriber.php @@ -14,11 +14,8 @@ class LoggerSubscriber implements EventSubscriberInterface { const PREFIX = '(Strata Data) '; - private LoggerInterface $logger; - - public function __construct(LoggerInterface $logger) + public function __construct(private LoggerInterface $logger) { - $this->logger = $logger; } public static function getSubscribedEvents(): array diff --git a/src/Event/Subscriber/StopwatchSubscriber.php b/src/Event/Subscriber/StopwatchSubscriber.php index 4a9ec04..6d576b7 100644 --- a/src/Event/Subscriber/StopwatchSubscriber.php +++ b/src/Event/Subscriber/StopwatchSubscriber.php @@ -12,11 +12,8 @@ class StopwatchSubscriber implements EventSubscriberInterface { - private Stopwatch $stopwatch; - - public function __construct(Stopwatch $stopwatch) + public function __construct(private Stopwatch $stopwatch) { - $this->stopwatch = $stopwatch; } public static function getSubscribedEvents(): array diff --git a/src/Exception/HttpException.php b/src/Exception/HttpException.php index 696005c..3343c7f 100755 --- a/src/Exception/HttpException.php +++ b/src/Exception/HttpException.php @@ -10,13 +10,7 @@ class HttpException extends \Exception { const INDENT = ' '; - private string $requestUri; - private string $requestMethod; - private array $requestOptions; private ?string $requestBody = null; - private array $responseErrorData; - private array $responseData; - private ResponseInterface $response; private string $requestTrace = ''; /** @@ -25,29 +19,22 @@ class HttpException extends \Exception * Outputs key HTTP request and response data with exception, data can also be accessed via getters * * @param $message - * @param string $uri - * @param string $method - * @param array $options + * @param string $requestUri + * @param string $requestMethod + * @param array $requestOptions * @param ResponseInterface $response - * @param array $errorData + * @param array $responseErrorData * @param array $responseData * @param \Exception|null $previous */ - public function __construct(string $message, string $uri, string $method, array $options, ResponseInterface $response, array $errorData = [], array $responseData = [], \Exception $previous = null) + public function __construct(string $message, private string $requestUri, private string $requestMethod, private array $requestOptions, private ResponseInterface $response, private array $responseErrorData = [], private array $responseData = [], \Exception $previous = null) { - $this->requestUri = $uri; - $this->requestMethod = $method; - $this->requestOptions = $options; - $this->response = $response; - $this->responseErrorData = $errorData; - $this->responseData = $responseData; - // Append error data if set - $message .= $this->getMessageFromErrorData($errorData); + $message .= $this->getMessageFromErrorData($this->responseErrorData); $message = trim($message); // Create request trace to aid debugging - $this->createRequestTrace($uri, $method, $options, $response, $errorData); + $this->createRequestTrace($this->requestUri, $this->requestMethod, $this->requestOptions, $this->response, $this->responseErrorData); parent::__construct($message, 0, $previous); } @@ -60,19 +47,12 @@ public function createRequestTrace(string $uri, string $method, array $options, if (in_array($name, ['body', 'user_data'])) { continue; } - switch ($name) { - case 'query': - $httpInfo .= 'Request query params: ' . $this->expandArrayValues($values); - break; - case 'headers': - $httpInfo .= 'Request headers: ' . $this->expandArrayValues($values); - break; - case 'extra': - $httpInfo .= 'Request extra: ' . $this->expandArrayValues($values); - break; - default: - $httpInfo .= $name . ': ' . $values . PHP_EOL; - } + match ($name) { + 'query' => $httpInfo .= 'Request query params: ' . $this->expandArrayValues($values), + 'headers' => $httpInfo .= 'Request headers: ' . $this->expandArrayValues($values), + 'extra' => $httpInfo .= 'Request extra: ' . $this->expandArrayValues($values), + default => $httpInfo .= $name . ': ' . $values . PHP_EOL, + }; } if (isset($options['body'])) { $httpInfo .= 'Request body: '; @@ -88,7 +68,7 @@ public function createRequestTrace(string $uri, string $method, array $options, if (!empty($headers)) { $httpInfo .= 'Response headers: ' . $this->expandArrayValues($response->getHeaders()); } - } catch (HttpExceptionInterface $e) { + } catch (HttpExceptionInterface) { // continue } $this->requestTrace = $httpInfo; diff --git a/src/Http/GraphQL.php b/src/Http/GraphQL.php index 62d399d..9978677 100644 --- a/src/Http/GraphQL.php +++ b/src/Http/GraphQL.php @@ -89,7 +89,7 @@ public function throwExceptionOnFailedRequest(ResponseInterface $response): void try { $partialData = $this->decode($response); - } catch (DecoderException $e) { + } catch (DecoderException) { $partialData = []; } diff --git a/src/Http/Http.php b/src/Http/Http.php index 32ef295..e2d725f 100644 --- a/src/Http/Http.php +++ b/src/Http/Http.php @@ -628,7 +628,7 @@ public function runRequest(CacheableResponse $response): CacheableResponse ]), FailureEvent::NAME); if (!$this->isSuppressErrors()) { - if (substr((string) $response->getStatusCode(), 0, 3) === '404') { + if (str_starts_with((string) $response->getStatusCode(), '404')) { throw new HttpNotFoundException( 'Not Found HTTP error', $this->requestTrace->getRequestUri($requestId), diff --git a/src/Http/Response/DecoratedResponseTrait.php b/src/Http/Response/DecoratedResponseTrait.php index 1157460..1f8dac1 100644 --- a/src/Http/Response/DecoratedResponseTrait.php +++ b/src/Http/Response/DecoratedResponseTrait.php @@ -164,7 +164,7 @@ public function toStream(bool $throw = true) if (method_exists($this->decorated, 'toStream')) { return $this->decorated->toStream(false); } else { - throw new \Exception('Method toStream does not exist on object ' . get_class($this->decorated)); + throw new \Exception('Method toStream does not exist on object ' . $this->decorated::class); } } } diff --git a/src/Http/Response/SuppressErrorResponse.php b/src/Http/Response/SuppressErrorResponse.php index f2aaea4..81b0079 100644 --- a/src/Http/Response/SuppressErrorResponse.php +++ b/src/Http/Response/SuppressErrorResponse.php @@ -75,7 +75,7 @@ public function toStream(bool $throw = true) if (method_exists($this->decorated, 'toStream')) { return $this->decorated->toStream(false); } else { - throw new \Exception('Method toStream does not exist on object ' . get_class($this->decorated)); + throw new \Exception('Method toStream does not exist on object ' . $this->decorated::class); } } } diff --git a/src/Mapper/MapArray.php b/src/Mapper/MapArray.php index b276d95..6b31bd3 100644 --- a/src/Mapper/MapArray.php +++ b/src/Mapper/MapArray.php @@ -11,8 +11,6 @@ class MapArray implements MapValueInterface { use PropertyAccessorTrait; - - private string $propertyPath; private MapItem $mapItem; /** @@ -21,9 +19,8 @@ class MapArray implements MapValueInterface * @param string $propertyPath Property path to read data from (this must be an array of data, otherwise it is not mapped) * @param array|MappingStrategyInterface $strategy Array of mapping property paths, or MappingStrategy object */ - public function __construct(string $propertyPath, $strategy) + public function __construct(private string $propertyPath, $strategy) { - $this->propertyPath = $propertyPath; $this->mapItem = new MapItem($strategy); } diff --git a/src/Mapper/MappingStrategy.php b/src/Mapper/MappingStrategy.php index 6890106..54d054e 100644 --- a/src/Mapper/MappingStrategy.php +++ b/src/Mapper/MappingStrategy.php @@ -112,7 +112,7 @@ public function mapItem(array $data, array|object $item) if (!is_string($source) && !is_array($source)) { $type = gettype($source); if ($type === 'object') { - $type = get_class($source); + $type = $source::class; } throw new MapperException(sprintf('Source for destination "%s" not a valid type. Must be a string, array, CallableValue, CallableData, or MapValueInterface object. %s passed.', $destination, $type)); } diff --git a/src/Permissions.php b/src/Permissions.php index 7866f32..e54ec6f 100755 --- a/src/Permissions.php +++ b/src/Permissions.php @@ -8,7 +8,7 @@ * Class to manage allowed actions on an API and to protect against misuse * */ -class Permissions +class Permissions implements \Stringable { const READ = 1; const CREATE = 2; @@ -80,17 +80,13 @@ public function delete(): bool */ public function getName(int $action): ?string { - switch ($action) { - case self::READ: - return 'READ'; - case self::CREATE: - return 'CREATE'; - case self::UPDATE: - return 'UPDATE'; - case self::DELETE: - return 'DELETE'; - } - return null; + return match ($action) { + self::READ => 'READ', + self::CREATE => 'CREATE', + self::UPDATE => 'UPDATE', + self::DELETE => 'DELETE', + default => null, + }; } /** @@ -98,7 +94,7 @@ public function getName(int $action): ?string * * @return string */ - public function __toString() + public function __toString(): string { $results = []; if ($this->read()) { diff --git a/src/Query/BuildQuery/BuildGraphQLQuery.php b/src/Query/BuildQuery/BuildGraphQLQuery.php index 879bb83..7764d6e 100644 --- a/src/Query/BuildQuery/BuildGraphQLQuery.php +++ b/src/Query/BuildQuery/BuildGraphQLQuery.php @@ -17,15 +17,12 @@ */ class BuildGraphQLQuery implements BuildQueryInterface { - private GraphQL $dataProvider; - /** * Constructor * @param GraphQL $dataProvider Data provider to use to build this query */ - public function __construct(GraphQL $dataProvider) + public function __construct(private GraphQL $dataProvider) { - $this->dataProvider = $dataProvider; } /** @@ -63,7 +60,7 @@ public function getGraphQLParameters(GraphQLQueryInterface $query): string break; default: // If a variable do not quote - if (is_string($value) && strpos($value, '$') === 0) { + if (is_string($value) && str_starts_with($value, '$')) { $paramValue = $value; break; } diff --git a/src/Query/BuildQuery/BuildQuery.php b/src/Query/BuildQuery/BuildQuery.php index 65f79ab..1d98b7d 100644 --- a/src/Query/BuildQuery/BuildQuery.php +++ b/src/Query/BuildQuery/BuildQuery.php @@ -14,15 +14,12 @@ */ class BuildQuery implements BuildQueryInterface { - private Http $dataProvider; - /** * Constructor * @param Http $dataProvider Data provider to use to build this query */ - public function __construct(Http $dataProvider) + public function __construct(private Http $dataProvider) { - $this->dataProvider = $dataProvider; } /** diff --git a/src/Query/QueryAbstract.php b/src/Query/QueryAbstract.php index 4f95591..2c20985 100644 --- a/src/Query/QueryAbstract.php +++ b/src/Query/QueryAbstract.php @@ -61,7 +61,7 @@ public function setDataProvider(DataProviderInterface $dataProvider): self { $class = $this->getRequiredDataProviderClass(); if (!($dataProvider instanceof $class)) { - throw new QueryException(sprintf('Cannot set data provider of type %s to this query, type %s required', get_class($dataProvider), $class)); + throw new QueryException(sprintf('Cannot set data provider of type %s to this query, type %s required', $dataProvider::class, $class)); } $this->dataProvider = $dataProvider; return $this; @@ -430,10 +430,9 @@ public function setParams(array $params): self /** * Add one parameter to apply to this query * @param string $key - * @param mixed $value * @return $this Fluent interface */ - public function addParam(string $key, $value): self + public function addParam(string $key, mixed $value): self { $this->params[$key] = $value; return $this; diff --git a/src/Query/QueryManager.php b/src/Query/QueryManager.php index 40449f7..746f229 100644 --- a/src/Query/QueryManager.php +++ b/src/Query/QueryManager.php @@ -108,7 +108,7 @@ public function addDataProvider(string $name, DataProviderInterface $dataProvide $this->dataProviders[$name] = [ self::DATA_PROVIDER_NAME => $name, - self::DATA_PROVIDER_CLASS => get_class($dataProvider), + self::DATA_PROVIDER_CLASS => $dataProvider::class, self::DATA_PROVIDER_OBJECT => $dataProvider, self::DATA_PROVIDER_QUERIES => [], ]; @@ -571,7 +571,7 @@ public function getDataCollector(): array $value = [ 'name' => $dataProviderName, - 'class' => get_class($dataProvider), + 'class' => $dataProvider::class, 'baseUri' => $dataProvider->getBaseUri(), 'cacheEnabled' => $dataProvider->isCacheEnabled(), ]; @@ -581,7 +581,7 @@ public function getDataCollector(): array foreach ($item[self::DATA_PROVIDER_QUERIES] as $queryName => $query) { $value = [ 'name' => $queryName, - 'class' => get_class($query), + 'class' => $query::class, 'type' => null, 'dataProvider' => $dataProviderName, 'hasResponse' => false diff --git a/src/Traits/IterableKeyBasedTrait.php b/src/Traits/IterableKeyBasedTrait.php index c7f8286..609ba1d 100644 --- a/src/Traits/IterableKeyBasedTrait.php +++ b/src/Traits/IterableKeyBasedTrait.php @@ -96,9 +96,8 @@ public function offsetGet($offset) /** * Add item to collection * @param $offset - * @param mixed $value */ - public function offsetSet($offset, $value) + public function offsetSet($offset, mixed $value) { $this->collection[$offset] = $value; } diff --git a/src/Transform/Value/DateTimeValue.php b/src/Transform/Value/DateTimeValue.php index 062b687..d2d0f11 100644 --- a/src/Transform/Value/DateTimeValue.php +++ b/src/Transform/Value/DateTimeValue.php @@ -45,7 +45,7 @@ public function getValue($objectOrArray): ?\DateTime } try { return new \DateTime($value, $this->timezone); - } catch (\Exception $e) { + } catch (\Exception) { return null; } } diff --git a/src/Validate/ValidationRules.php b/src/Validate/ValidationRules.php index 4dcbbf6..a9fc65e 100644 --- a/src/Validate/ValidationRules.php +++ b/src/Validate/ValidationRules.php @@ -52,26 +52,17 @@ public function __construct(array $fieldRules) */ protected function getRuleClass(string $ruleName): string { - switch ($ruleName) { - case 'array': - return \Strata\Data\Validate\Rule\ArrayRule::class; - case 'boolean': - return \Strata\Data\Validate\Rule\BooleanRule::class; - case 'email': - return \Strata\Data\Validate\Rule\EmailRule::class; - case 'in': - return \Strata\Data\Validate\Rule\InRule::class; - case 'number': - return \Strata\Data\Validate\Rule\NumberRule::class; - case 'image': - return \Strata\Data\Validate\Rule\ImageRule::class; - case 'required': - return \Strata\Data\Validate\Rule\RequiredRule::class; - case 'url': - return \Strata\Data\Validate\Rule\UrlRule::class; - default: - throw new ValidatorRulesException(sprintf('Validation rule not recognised: %s', $ruleName)); - } + return match ($ruleName) { + 'array' => \Strata\Data\Validate\Rule\ArrayRule::class, + 'boolean' => \Strata\Data\Validate\Rule\BooleanRule::class, + 'email' => \Strata\Data\Validate\Rule\EmailRule::class, + 'in' => \Strata\Data\Validate\Rule\InRule::class, + 'number' => \Strata\Data\Validate\Rule\NumberRule::class, + 'image' => \Strata\Data\Validate\Rule\ImageRule::class, + 'required' => \Strata\Data\Validate\Rule\RequiredRule::class, + 'url' => \Strata\Data\Validate\Rule\UrlRule::class, + default => throw new ValidatorRulesException(sprintf('Validation rule not recognised: %s', $ruleName)), + }; } /** diff --git a/src/Validate/ValidatorInterface.php b/src/Validate/ValidatorInterface.php index ad2db9d..beef2ee 100644 --- a/src/Validate/ValidatorInterface.php +++ b/src/Validate/ValidatorInterface.php @@ -11,10 +11,9 @@ interface ValidatorInterface extends PropertyAccessorInterface /** * Is the item valid? * - * @param mixed $data * @return bool */ - public function validate($data): bool; + public function validate(mixed $data): bool; /** * Return error message from last validate() call diff --git a/tests/Decode/StringNormalizerTest.php b/tests/Decode/StringNormalizerTest.php index cfa7823..c4b9543 100644 --- a/tests/Decode/StringNormalizerTest.php +++ b/tests/Decode/StringNormalizerTest.php @@ -10,7 +10,7 @@ use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\MockResponse; -class Item +class Item implements \Stringable { public function __toString(): string { diff --git a/tests/Query/GraphQLTest.php b/tests/Query/GraphQLTest.php index ba1a1c8..68bfffb 100644 --- a/tests/Query/GraphQLTest.php +++ b/tests/Query/GraphQLTest.php @@ -16,7 +16,7 @@ public function testSetDataProvider() $this->assertSame(GraphQL::class, $query->getRequiredDataProviderClass()); $query->setDataProvider(new GraphQL('https://example.com')); - $this->assertSame(GraphQL::class, get_class($query->getDataProvider())); + $this->assertSame(GraphQL::class, $query->getDataProvider()::class); $this->expectException(QueryException::class); $query->setDataProvider(new Rest('https://example.com')); diff --git a/tests/Query/QueryTest.php b/tests/Query/QueryTest.php index ee08681..d712d31 100644 --- a/tests/Query/QueryTest.php +++ b/tests/Query/QueryTest.php @@ -37,7 +37,7 @@ public function testSetDataProvider() $this->assertSame(Rest::class, $query->getRequiredDataProviderClass()); $query->setDataProvider(new Rest('https://example.com')); - $this->assertSame(Rest::class, get_class($query->getDataProvider())); + $this->assertSame(Rest::class, $query->getDataProvider()::class); $this->expectException(QueryException::class); $query->setDataProvider(new GraphQL('https://example.com')); From 83a2aea31fc213e8dd59ae1698ed84ca93242f60 Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Mon, 8 Jun 2026 17:40:46 +0100 Subject: [PATCH 05/12] Rector PHP8.1 --- rector.php | 7 +++++-- src/Exception/GraphQLException.php | 2 +- src/Helper/GraphQLTestCase.php | 4 ++-- src/Http/GraphQL.php | 2 +- src/Transform/AllValues/HtmlEntitiesDecode.php | 2 +- src/Transform/AllValues/StripTags.php | 2 +- src/Transform/AllValues/Trim.php | 2 +- src/Transform/Data/MapValues.php | 2 +- src/Transform/Value/BooleanValue.php | 2 +- src/Validate/Rule/ImageRule.php | 2 +- tests/Http/HttpTest.php | 2 +- tests/Mapper/MapCallableTest.php | 6 +++--- tests/Mapper/MapItemTest.php | 2 +- 13 files changed, 20 insertions(+), 17 deletions(-) diff --git a/rector.php b/rector.php index a91050a..439d75b 100644 --- a/rector.php +++ b/rector.php @@ -3,12 +3,15 @@ declare(strict_types=1); use Rector\Config\RectorConfig; -use Rector\Set\ValueObject\LevelSetList; +use Rector\Php81\Rector\Property\ReadOnlyPropertyRector; use Rector\Symfony\Set\SymfonySetList; return RectorConfig::configure() ->withPaths([__DIR__ . '/src', __DIR__ . '/tests']) - ->withPhpSets(php80: true); + ->withPhpSets(php81: true) + ->withSkip([ + ReadOnlyPropertyRector::class + ]); /* diff --git a/src/Exception/GraphQLException.php b/src/Exception/GraphQLException.php index 94d6b0f..31e7f27 100755 --- a/src/Exception/GraphQLException.php +++ b/src/Exception/GraphQLException.php @@ -65,7 +65,7 @@ public function getMessageFromErrorData(array $errorData): string $content = $error['message']; if (isset($error['locations'])) { foreach ($error['locations'] as $location) { - $content = rtrim($content, '.'); + $content = rtrim((string) $content, '.'); $content .= sprintf(' on line %d, column %d', $location['line'], $location['column']); } } diff --git a/src/Helper/GraphQLTestCase.php b/src/Helper/GraphQLTestCase.php index 11243d5..12661dd 100644 --- a/src/Helper/GraphQLTestCase.php +++ b/src/Helper/GraphQLTestCase.php @@ -25,9 +25,9 @@ public function assertGraphQLEquals(string $expected, string $actual) { // Strip line returns $expected = preg_replace('/\s+/', ' ', $expected); - $expected = trim($expected); + $expected = trim((string) $expected); $actual = preg_replace('/\s+/', ' ', $actual); - $actual = trim($actual); + $actual = trim((string) $actual); Assert::assertThat( $actual, diff --git a/src/Http/GraphQL.php b/src/Http/GraphQL.php index 9978677..26a2479 100644 --- a/src/Http/GraphQL.php +++ b/src/Http/GraphQL.php @@ -177,7 +177,7 @@ public function buildQuery(string $query, ?array $variables = [], ?string $opera { // Make sure GraphQL query is on one line so valid JSON $query = preg_replace('/\s+/', ' ', $query); - $query = trim($query); + $query = trim((string) $query); $data = ['query' => $query]; if (!empty($operationName)) { diff --git a/src/Transform/AllValues/HtmlEntitiesDecode.php b/src/Transform/AllValues/HtmlEntitiesDecode.php index 2d0696d..16ccab1 100644 --- a/src/Transform/AllValues/HtmlEntitiesDecode.php +++ b/src/Transform/AllValues/HtmlEntitiesDecode.php @@ -28,6 +28,6 @@ public function canTransform($data): bool */ public function transform($data) { - return htmlspecialchars_decode($data); + return htmlspecialchars_decode((string) $data); } } diff --git a/src/Transform/AllValues/StripTags.php b/src/Transform/AllValues/StripTags.php index eb7697f..48a0f0d 100644 --- a/src/Transform/AllValues/StripTags.php +++ b/src/Transform/AllValues/StripTags.php @@ -28,6 +28,6 @@ public function canTransform($data): bool */ public function transform($data) { - return strip_tags($data); + return strip_tags((string) $data); } } diff --git a/src/Transform/AllValues/Trim.php b/src/Transform/AllValues/Trim.php index de939a8..8abe401 100644 --- a/src/Transform/AllValues/Trim.php +++ b/src/Transform/AllValues/Trim.php @@ -28,6 +28,6 @@ public function canTransform($data): bool */ public function transform($data) { - return trim($data); + return trim((string) $data); } } diff --git a/src/Transform/Data/MapValues.php b/src/Transform/Data/MapValues.php index eb02f8e..1946770 100644 --- a/src/Transform/Data/MapValues.php +++ b/src/Transform/Data/MapValues.php @@ -133,7 +133,7 @@ public function transform($data) */ private function normalize($value) { - $value = strtolower($value); + $value = strtolower((string) $value); $value = trim($value); return $value; } diff --git a/src/Transform/Value/BooleanValue.php b/src/Transform/Value/BooleanValue.php index 9590d2d..ae4ba85 100644 --- a/src/Transform/Value/BooleanValue.php +++ b/src/Transform/Value/BooleanValue.php @@ -40,7 +40,7 @@ public function getValue($objectOrArray) return null; } - $value = strtolower($value); + $value = strtolower((string) $value); if (in_array($value, $this->trueValues, true)) { return true; } diff --git a/src/Validate/Rule/ImageRule.php b/src/Validate/Rule/ImageRule.php index 3544674..72cb864 100644 --- a/src/Validate/Rule/ImageRule.php +++ b/src/Validate/Rule/ImageRule.php @@ -17,7 +17,7 @@ class ImageRule extends ValidatorRuleAbstract public function validate($data): bool { $value = $this->getProperty($data); - $info = pathinfo($value); + $info = pathinfo((string) $value); if (!isset($info['extension'])) { return false; } diff --git a/tests/Http/HttpTest.php b/tests/Http/HttpTest.php index 5dfa130..9dd2fc7 100644 --- a/tests/Http/HttpTest.php +++ b/tests/Http/HttpTest.php @@ -423,7 +423,7 @@ public function testBasicRestApiFunctions() // POST $callback = function ($method, $url, $options) { - parse_str($options['body'], $postData); + parse_str((string) $options['body'], $postData); if (!isset($postData['name'])) { return new MockResponse('Bad request: name not found', ['http_code' => 400]); } diff --git a/tests/Mapper/MapCallableTest.php b/tests/Mapper/MapCallableTest.php index 04ba740..76cd477 100644 --- a/tests/Mapper/MapCallableTest.php +++ b/tests/Mapper/MapCallableTest.php @@ -12,7 +12,7 @@ function user_transform($value): string { - return 'X ' . strtoupper($value); + return 'X ' . strtoupper((string) $value); } final class MapCallableTest extends TestCase @@ -89,7 +89,7 @@ public function testFunction() public function testObjectMethodWithNullData() { $mapping = [ - '[name]' => new CallableData([$this, 'populateContent'], '[person_name]'), + '[name]' => new CallableData($this->populateContent(...), '[person_name]'), '[age]' => '[person_age]', ]; $mapper = new MapItem($mapping); @@ -137,6 +137,6 @@ public function testObjectStaticMethod() public static function staticPopulateContent(array $data) { - return strtolower($data['person_name']); + return strtolower((string) $data['person_name']); } } diff --git a/tests/Mapper/MapItemTest.php b/tests/Mapper/MapItemTest.php index 793b9e6..2049b1f 100644 --- a/tests/Mapper/MapItemTest.php +++ b/tests/Mapper/MapItemTest.php @@ -24,7 +24,7 @@ class Person2 extends Person { public function setName($name) { - $this->name = strtolower($name); + $this->name = strtolower((string) $name); } } From 128d9a62a814d4e6706c060757c679d4cd65b105 Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Tue, 9 Jun 2026 09:55:45 +0100 Subject: [PATCH 06/12] Trying to fix issue with Redis 6.1 / Symfony 7.4 See https://github.com/symfony/symfony/issues/63087 --- .github/workflows/php.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 278ff16..c9bab33 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -40,8 +40,10 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: mbstring, intl, redis-6.3 + extensions: mbstring, intl, :redis, redis-6.3 ini-values: post_max_size=256M, max_execution_time=180 + env: + fail-fast: true - name: Check PHP version run: php -v From 3fc1b76fd4838866b3b68b4575e8314225f11f77 Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Tue, 9 Jun 2026 10:12:51 +0100 Subject: [PATCH 07/12] Testing Redis/Symfony --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index c9bab33..a9863c8 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -40,7 +40,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: mbstring, intl, :redis, redis-6.3 + extensions: mbstring, intl, redis-6.3.0 ini-values: post_max_size=256M, max_execution_time=180 env: fail-fast: true From 001443ac5512c72600ae038532ce8ec53e2eabe8 Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Tue, 9 Jun 2026 10:23:15 +0100 Subject: [PATCH 08/12] Testing --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index a9863c8..4af277e 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -40,7 +40,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: mbstring, intl, redis-6.3.0 + extensions: mbstring, intl, :redis, redis-6.3.0 ini-values: post_max_size=256M, max_execution_time=180 env: fail-fast: true From 42c5d06f20c8119fa176c50b8d29392b00728ca1 Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Tue, 9 Jun 2026 10:25:24 +0100 Subject: [PATCH 09/12] Coding standards --- src/Mapper/MapArray.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mapper/MapArray.php b/src/Mapper/MapArray.php index 6b31bd3..70da25f 100644 --- a/src/Mapper/MapArray.php +++ b/src/Mapper/MapArray.php @@ -11,6 +11,7 @@ class MapArray implements MapValueInterface { use PropertyAccessorTrait; + private MapItem $mapItem; /** From a8b7ab0148cea358754aae5bbddb92be3dcc1fcf Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Tue, 9 Jun 2026 10:38:35 +0100 Subject: [PATCH 10/12] Commit final rector file --- composer.json | 9 +++++---- rector.php | 26 +++----------------------- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/composer.json b/composer.json index 3554806..b7af63d 100755 --- a/composer.json +++ b/composer.json @@ -31,11 +31,12 @@ ] }, "require-dev": { - "phpunit/phpunit": "^10.5", - "squizlabs/php_codesniffer": "^3.10", - "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^11.5", + "squizlabs/php_codesniffer": "^4.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0", "roave/security-advisories": "dev-latest", - "rector/rector": "^1.2" + "rector/rector": "^2.4" }, "scripts": { "phpcs": [ diff --git a/rector.php b/rector.php index 439d75b..deb8615 100644 --- a/rector.php +++ b/rector.php @@ -4,32 +4,12 @@ use Rector\Config\RectorConfig; use Rector\Php81\Rector\Property\ReadOnlyPropertyRector; -use Rector\Symfony\Set\SymfonySetList; return RectorConfig::configure() ->withPaths([__DIR__ . '/src', __DIR__ . '/tests']) - ->withPhpSets(php81: true) + ->withPhpSets(php82: true) + ->withPreparedSets(symfonyCodeQuality: true) + ->withComposerBased(symfony: true) ->withSkip([ ReadOnlyPropertyRector::class ]); - - -/* -return static function (RectorConfig $rectorConfig): void { - $rectorConfig->paths([ - __DIR__ . '/src', - __DIR__ . '/tests', - ]); - - $rectorConfig->sets([ - LevelSetList::UP_TO_PHP_82, - SymfonySetList::SYMFONY_60, - SymfonySetList::SYMFONY_61, - SymfonySetList::SYMFONY_62, - SymfonySetList::SYMFONY_63, - SymfonySetList::SYMFONY_64, - SymfonySetList::SYMFONY_70, - SymfonySetList::SYMFONY_71, - ]); -}; -*/ \ No newline at end of file From 9eb5947805c8e02621d9676b763f6713543c74fe Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Tue, 9 Jun 2026 10:42:15 +0100 Subject: [PATCH 11/12] feat: Symfony 8 support --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a683afc..de98522 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Please note this software is in development, usage may change before the 1.0 rel Install via Composer: ``` -composer require strata/data:^0.9 +composer require strata/data ``` ## Thanks to From 38c0023db8e81aae01517773a621124ccfc2b829 Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Tue, 9 Jun 2026 11:28:45 +0100 Subject: [PATCH 12/12] Add note on Symfony support --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index de98522..c4590e3 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Please note this software is in development, usage may change before the 1.0 rel ## Requirements * PHP 8.2+ +* Supports Symfony 7.4+ * [Composer](https://getcomposer.org/) ## Installation