diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8a60024 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,71 @@ +name: Build + +on: + pull_request: + branches: + - '*' + push: + branches: + - 'master' + +jobs: + tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + platform-reqs: + - use-platform-reqs + php-version: + - "8.1" + - "8.2" + - "8.3" + dependencies: + - lowest + - highest + + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + + - name: Install PHIVE + uses: szepeviktor/phive@v1 + + - name: Install lowest dependencies + if: ${{ matrix.dependencies == 'lowest' && matrix.platform-reqs == 'use-platform-reqs' }} + run: composer update --no-interaction --prefer-lowest + + - name: Install lowest dependencies (ignore platform reqs) + if: ${{ matrix.dependencies == 'lowest' && matrix.platform-reqs == 'ignore-platform-reqs' }} + run: composer update --no-interaction --prefer-lowest --ignore-platform-reqs + + - name: Install highest dependencies + if: ${{ matrix.dependencies == 'highest' && matrix.platform-reqs == 'use-platform-reqs' }} + run: composer update --no-interaction + + - name: Install highest dependencies (ignore platform reqs) + if: ${{ matrix.dependencies == 'highest' && matrix.platform-reqs == 'ignore-platform-reqs' }} + run: composer update --no-interaction --ignore-platform-reqs + + - name: Run tests + run: composer test + + - name: Run infection + run: composer infection + env: + STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} + + - name: Upload coverage to Codecov.io + run: bash <(curl -s https://codecov.io/bash -s "build/logs") + continue-on-error: true + + - name: Upload coverage to Scrutinizer + uses: sudo-bot/action-scrutinizer@latest + with: + cli-args: --format=php-clover build/logs/clover.xml + continue-on-error: true diff --git a/.gitignore b/.gitignore index 04d9ea3..6958379 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ /build/ /vendor/ /composer.lock +/vendor-bin/**/vendor/ +/vendor-bin/**/composer.lock /infection.json /phpunit.xml +/tools/ .phpunit.result.cache diff --git a/.phive/phars.xml b/.phive/phars.xml new file mode 100644 index 0000000..bda539d --- /dev/null +++ b/.phive/phars.xml @@ -0,0 +1,4 @@ + + + + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 47ac2e3..0000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: php - -env: - global: - - XDEBUG=YES - - XDEBUG_MODE=coverage - -matrix: - include: - - php: '7.3' - - php: '7.4' - - php: '8.0' - -install: - - mkdir -p ./build/logs - - composer self-update - - composer install --prefer-source --no-interaction - -script: - - vendor/bin/phpcs -sp --report-junit=build/logs/phpcs.xml - - if [ "$XDEBUG" == "YES" ]; then vendor/bin/phpunit --coverage-clover=build/logs/clover.xml --coverage-xml=build/logs/coverage-xml --log-junit=build/logs/junit.xml; else vendor/bin/phpunit; fi - - if [ "$XDEBUG" == "YES" ]; then vendor/bin/infection --coverage=build/logs --threads=4 --no-progress --skip-initial-tests; fi - -after_success: - - if [ "$XDEBUG" == "YES" ]; then bash <(curl -s https://codecov.io/bash -s "build/logs"); fi - - if [ "$XDEBUG" == "YES" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi - - if [ "$XDEBUG" == "YES" ]; then php ocular.phar code-coverage:upload --access-token=$SCRUTINIZER_TOKEN --format=php-clover build/logs/clover.xml; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e4a84..b76120a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.7.0] - 2024-02-17 +### Added +- Cloning setters added to events to make stream modification easier. + +## [0.6.1] - 2024-02-11 +### Added +- Added PHP 8.3 support. +### Removed +- Removed PHP 8.0 support. + +## [0.6.0] - 2023-06-16 +### Changed +- JSON source structure may contain arbitrary objects, not only `stdClass`. +### Removed +- Dropped PHP 7 support. + ## [0.5.3] - 2021-01-14 ### Added - PHP 8.0 support. diff --git a/LICENSE b/LICENSE index f7a5ce0..9e9e88a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016-2019 Edward Surov +Copyright (c) 2016-2024 Edward Surov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ad3d9fd..413bd30 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,14 @@ [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/remorhaz/php-json-data/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/remorhaz/php-json-data/?branch=master) [![codecov](https://codecov.io/gh/remorhaz/php-json-data/branch/master/graph/badge.svg)](https://codecov.io/gh/remorhaz/php-json-data) [![Total Downloads](https://poser.pugx.org/remorhaz/php-json-data/downloads)](https://packagist.org/packages/remorhaz/php-json-data) -[![Mutation testing badge](https://badge.stryker-mutator.io/github.com/remorhaz/php-json-data/master)](https://stryker-mutator.github.io) +[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fremorhaz%2Fphp-json-data%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/remorhaz/php-json-data/master) [![License](https://poser.pugx.org/remorhaz/php-json-data/license)](https://packagist.org/packages/remorhaz/php-json-data) This library provides infrastructure for JSON documents processing. ## Requirements -- PHP 7.3+ +- PHP 8.1 or newer. - [Internationalization functions](https://www.php.net/manual/en/book.intl.php) (ext-intl) - to compare Unicode strings. - [JSON extension](https://www.php.net/manual/en/book.json.php) (ext-json) - to encode and decode JSON documents. diff --git a/composer.json b/composer.json index 0190d6d..254303b 100644 --- a/composer.json +++ b/composer.json @@ -14,14 +14,13 @@ } ], "require": { - "php": "^7.3 | ^8", + "php": "~8.1.0 || ~8.2.0 || ~8.3.0", "ext-json": "*", "ext-intl": "*" }, "require-dev": { - "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "^3.5", - "infection/infection": "^0.18" + "bamarni/composer-bin-plugin": "^1.8", + "phpunit/phpunit": "^10.1 || ^11" }, "autoload": { "psr-4": { @@ -30,24 +29,46 @@ }, "autoload-dev": { "psr-4": { - "Remorhaz\\JSON\\Test\\Data\\": "tests/" + "Remorhaz\\JSON\\Data\\Test\\": "tests/" } }, "scripts": { + "post-update-cmd": ["@phive-install"], + "post-install-cmd": ["@phive-install"], + "phive-install": [ + "`if [ -f tools/phive ]; then echo 'tools/'; fi`phive install --trust-gpg-keys C5095986493B4AA0" + ], "test-cs": [ - "vendor/bin/phpcs -sp" + "vendor-bin/cs/vendor/bin/phpcs -sp" ], "test-unit": [ - "vendor/bin/phpunit --coverage-xml=build/log/coverage-xml --log-junit=build/log/junit.xml" + "vendor/bin/phpunit --coverage-xml=build/log/coverage-xml --coverage-clover=build/log/clover.xml --log-junit=build/log/junit.xml" ], + "test-psalm": "vendor-bin/psalm/vendor/bin/psalm --threads=4 --shepherd", "test": [ - "@test-sp", - "@test-unit" + "@test-cs", + "@test-unit", + "@test-psalm" ], "infection": [ - "@test-unit", "mkdir -p build/log/infection", - "vendor/bin/infection --threads=4 --coverage=build/log --no-progress --skip-initial-tests" + "tools/infection --threads=4 --coverage=build/log --no-progress --skip-initial-tests" + ], + "test-infection": [ + "@test-unit", + "@infection" ] + }, + "config": { + "allow-plugins": { + "bamarni/composer-bin-plugin": true + }, + "sort-packages": true + }, + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": true + } } } diff --git a/docker-compose.yml b/docker-compose.yml index fe68e84..a3e8597 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,21 +4,21 @@ services: php: build: context: . - dockerfile: php-7.3.Dockerfile + dockerfile: php-8.1.Dockerfile volumes: - .:/app working_dir: /app - php7.4: + php8.2: build: context: . - dockerfile: php-7.4.Dockerfile + dockerfile: php-8.2.Dockerfile volumes: - .:/app working_dir: /app - php8.0: + php8.3: build: context: . - dockerfile: php-8.0.Dockerfile + dockerfile: php-8.3.Dockerfile volumes: - .:/app working_dir: /app diff --git a/infection.json.dist b/infection.json.dist index b9398ec..ab00190 100644 --- a/infection.json.dist +++ b/infection.json.dist @@ -9,8 +9,9 @@ "text": "build/logs/infection/infection.log", "summary": "build/logs/infection/summary.log", "perMutator": "build/logs/infection/per-mutator.md", - "badge": { - "branch": "master" + "github": true, + "stryker": { + "badge": "/^master$/" } } } \ No newline at end of file diff --git a/php-7.3.Dockerfile b/php-7.3.Dockerfile deleted file mode 100644 index d0837cb..0000000 --- a/php-7.3.Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM php:7.3-cli - -RUN apt-get update && apt-get install -y \ - zip \ - git \ - libicu-dev && \ - pecl install xdebug && \ - docker-php-ext-enable xdebug && \ - docker-php-ext-configure intl --enable-intl && \ - docker-php-ext-install intl && \ - echo "xdebug.mode = develop,coverage,debug" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini" - -ENV COMPOSER_ALLOW_SUPERUSER=1 \ - COMPOSER_PROCESS_TIMEOUT=1200 - -RUN curl --silent --show-error https://getcomposer.org/installer | php -- \ - --install-dir=/usr/bin --filename=composer diff --git a/php-7.4.Dockerfile b/php-7.4.Dockerfile deleted file mode 100644 index 5d769fa..0000000 --- a/php-7.4.Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM php:7.4-cli - -RUN apt-get update && apt-get install -y \ - zip \ - git \ - libicu-dev && \ - docker-php-ext-configure intl --enable-intl && \ - docker-php-ext-install intl && \ - echo "xdebug.mode = develop,coverage,debug" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini" - -ENV COMPOSER_ALLOW_SUPERUSER=1 \ - COMPOSER_PROCESS_TIMEOUT=1200 - -RUN curl --silent --show-error https://getcomposer.org/installer | php -- \ - --install-dir=/usr/bin --filename=composer diff --git a/php-8.0.Dockerfile b/php-8.0.Dockerfile deleted file mode 100644 index 8dc4ed3..0000000 --- a/php-8.0.Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM php:8.0-cli - -RUN apt-get update && apt-get install -y \ - zip \ - git \ - libicu-dev && \ - docker-php-ext-configure intl --enable-intl && \ - docker-php-ext-install intl && \ - echo "xdebug.mode = develop,coverage,debug" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini" - -ENV COMPOSER_ALLOW_SUPERUSER=1 \ - COMPOSER_PROCESS_TIMEOUT=1200 - -RUN curl --silent --show-error https://getcomposer.org/installer | php -- \ - --install-dir=/usr/bin --filename=composer diff --git a/php-8.1.Dockerfile b/php-8.1.Dockerfile new file mode 100644 index 0000000..5df62cd --- /dev/null +++ b/php-8.1.Dockerfile @@ -0,0 +1,27 @@ +FROM php:8.1-cli + +RUN apt-get update && apt-get install -y \ + zip \ + git \ + wget \ + gpg \ + libicu-dev && \ + pecl install xdebug && \ + docker-php-ext-enable xdebug && \ + docker-php-ext-configure intl --enable-intl && \ + docker-php-ext-install intl pcntl && \ + echo "xdebug.mode = develop,coverage,debug" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini" + +ENV COMPOSER_ALLOW_SUPERUSER=1 \ + COMPOSER_PROCESS_TIMEOUT=1200 + +RUN curl --silent --show-error https://getcomposer.org/installer | php -- \ + --install-dir=/usr/bin --filename=composer && \ + git config --global --add safe.directory "*" + +RUN wget -O phive.phar https://phar.io/releases/phive.phar && \ + wget -O phive.phar.asc https://phar.io/releases/phive.phar.asc && \ + gpg --keyserver hkps://keys.openpgp.org --recv-keys 0x9D8A98B29B2D5D79 && \ + gpg --verify phive.phar.asc phive.phar && \ + chmod +x phive.phar && \ + mv phive.phar /usr/local/bin/phive \ diff --git a/php-8.2.Dockerfile b/php-8.2.Dockerfile new file mode 100644 index 0000000..5794eeb --- /dev/null +++ b/php-8.2.Dockerfile @@ -0,0 +1,27 @@ +FROM php:8.2-cli + +RUN apt-get update && apt-get install -y \ + zip \ + git \ + wget \ + gpg \ + libicu-dev && \ + pecl install xdebug && \ + docker-php-ext-enable xdebug && \ + docker-php-ext-configure intl --enable-intl && \ + docker-php-ext-install intl pcntl && \ + echo "xdebug.mode = develop,coverage,debug" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini" + +ENV COMPOSER_ALLOW_SUPERUSER=1 \ + COMPOSER_PROCESS_TIMEOUT=1200 + +RUN curl --silent --show-error https://getcomposer.org/installer | php -- \ + --install-dir=/usr/bin --filename=composer && \ + git config --global --add safe.directory "*" + +RUN wget -O phive.phar https://phar.io/releases/phive.phar && \ + wget -O phive.phar.asc https://phar.io/releases/phive.phar.asc && \ + gpg --keyserver hkps://keys.openpgp.org --recv-keys 0x9D8A98B29B2D5D79 && \ + gpg --verify phive.phar.asc phive.phar && \ + chmod +x phive.phar && \ + mv phive.phar /usr/local/bin/phive \ diff --git a/php-8.3.Dockerfile b/php-8.3.Dockerfile new file mode 100644 index 0000000..6a6bc0d --- /dev/null +++ b/php-8.3.Dockerfile @@ -0,0 +1,27 @@ +FROM php:8.3-cli + +RUN apt-get update && apt-get install -y \ + zip \ + git \ + wget \ + gpg \ + libicu-dev && \ + pecl install xdebug && \ + docker-php-ext-enable xdebug && \ + docker-php-ext-configure intl --enable-intl && \ + docker-php-ext-install intl pcntl && \ + echo "xdebug.mode = develop,coverage,debug" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini" + +ENV COMPOSER_ALLOW_SUPERUSER=1 \ + COMPOSER_PROCESS_TIMEOUT=1200 + +RUN curl --silent --show-error https://getcomposer.org/installer | php -- \ + --install-dir=/usr/bin --filename=composer && \ + git config --global --add safe.directory "*" + +RUN wget -O phive.phar https://phar.io/releases/phive.phar && \ + wget -O phive.phar.asc https://phar.io/releases/phive.phar.asc && \ + gpg --keyserver hkps://keys.openpgp.org --recv-keys 0x9D8A98B29B2D5D79 && \ + gpg --verify phive.phar.asc phive.phar && \ + chmod +x phive.phar && \ + mv phive.phar /usr/local/bin/phive \ diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f9a7b24..df2a9ff 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,18 +1,19 @@ - + - - - tests/ - - - - - src/ - - + colors="true" + cacheDirectory="build/.phpunit.cache" + requireCoverageMetadata="true"> + + + tests/ + + + + + src/ + + diff --git a/psalm.xml.dist b/psalm.xml.dist new file mode 100644 index 0000000..efca3b8 --- /dev/null +++ b/psalm.xml.dist @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/src/Comparator/ComparatorInterface.php b/src/Comparator/ComparatorInterface.php index c285afc..5810d64 100644 --- a/src/Comparator/ComparatorInterface.php +++ b/src/Comparator/ComparatorInterface.php @@ -8,6 +8,5 @@ interface ComparatorInterface { - public function compare(ValueInterface $leftValue, ValueInterface $rightValue): bool; } diff --git a/src/Comparator/ContainsValueComparator.php b/src/Comparator/ContainsValueComparator.php index acc5b1a..c235f70 100644 --- a/src/Comparator/ContainsValueComparator.php +++ b/src/Comparator/ContainsValueComparator.php @@ -7,14 +7,14 @@ use Collator; use Iterator; use Remorhaz\JSON\Data\Value\ArrayValueInterface; +use Remorhaz\JSON\Data\Value\NodeValueInterface; use Remorhaz\JSON\Data\Value\ObjectValueInterface; use Remorhaz\JSON\Data\Value\ScalarValueInterface; use Remorhaz\JSON\Data\Value\ValueInterface; final class ContainsValueComparator implements ComparatorInterface { - - private $equalComparator; + private EqualValueComparator $equalComparator; public function __construct(Collator $collator) { @@ -23,17 +23,14 @@ public function __construct(Collator $collator) public function compare(ValueInterface $leftValue, ValueInterface $rightValue): bool { - if ($leftValue instanceof ScalarValueInterface && $rightValue instanceof ScalarValueInterface) { - return $this->equalComparator->compare($leftValue, $rightValue); - } - if ($leftValue instanceof ArrayValueInterface && $rightValue instanceof ArrayValueInterface) { - return $this->equalComparator->compare($leftValue, $rightValue); - } - if ($leftValue instanceof ObjectValueInterface && $rightValue instanceof ObjectValueInterface) { - return $this->objectContains($leftValue, $rightValue); - } - - return false; + return match (true) { + $leftValue instanceof ScalarValueInterface && $rightValue instanceof ScalarValueInterface, + $leftValue instanceof ArrayValueInterface && $rightValue instanceof ArrayValueInterface => + $this->equalComparator->compare($leftValue, $rightValue), + $leftValue instanceof ObjectValueInterface && $rightValue instanceof ObjectValueInterface => + $this->objectContains($leftValue, $rightValue), + default => false, + }; } private function objectContains(ObjectValueInterface $leftValue, ObjectValueInterface $rightValue): bool @@ -58,6 +55,10 @@ private function objectContains(ObjectValueInterface $leftValue, ObjectValueInte return true; } + /** + * @param Iterator $valueIterator + * @return null|array + */ private function getPropertiesWithoutDuplicates(Iterator $valueIterator): ?array { $valuesByProperty = []; diff --git a/src/Comparator/EqualValueComparator.php b/src/Comparator/EqualValueComparator.php index 850fbe6..aa7f1e4 100644 --- a/src/Comparator/EqualValueComparator.php +++ b/src/Comparator/EqualValueComparator.php @@ -7,6 +7,7 @@ use Collator; use Iterator; use Remorhaz\JSON\Data\Value\ArrayValueInterface; +use Remorhaz\JSON\Data\Value\NodeValueInterface; use Remorhaz\JSON\Data\Value\ObjectValueInterface; use Remorhaz\JSON\Data\Value\ScalarValueInterface; use Remorhaz\JSON\Data\Value\ValueInterface; @@ -15,29 +16,22 @@ final class EqualValueComparator implements ComparatorInterface { - - private $collator; - - public function __construct(Collator $collator) - { - $this->collator = $collator; + public function __construct( + private readonly Collator $collator, + ) { } public function compare(ValueInterface $leftValue, ValueInterface $rightValue): bool { - if ($leftValue instanceof ScalarValueInterface && $rightValue instanceof ScalarValueInterface) { - return $this->isScalarEqual($leftValue, $rightValue); - } - - if ($leftValue instanceof ArrayValueInterface && $rightValue instanceof ArrayValueInterface) { - return $this->isArrayEqual($leftValue, $rightValue); - } - - if ($leftValue instanceof ObjectValueInterface && $rightValue instanceof ObjectValueInterface) { - return $this->isObjectEqual($leftValue, $rightValue); - } - - return false; + return match (true) { + $leftValue instanceof ScalarValueInterface && $rightValue instanceof ScalarValueInterface => + $this->isScalarEqual($leftValue, $rightValue), + $leftValue instanceof ArrayValueInterface && $rightValue instanceof ArrayValueInterface => + $this->isArrayEqual($leftValue, $rightValue), + $leftValue instanceof ObjectValueInterface && $rightValue instanceof ObjectValueInterface => + $this->isObjectEqual($leftValue, $rightValue), + default => false, + }; } private function isScalarEqual(ScalarValueInterface $leftValue, ScalarValueInterface $rightValue): bool @@ -45,11 +39,9 @@ private function isScalarEqual(ScalarValueInterface $leftValue, ScalarValueInter $leftData = $leftValue->getData(); $rightData = $rightValue->getData(); - if (is_string($leftData) && is_string($rightData)) { - return 0 === $this->collator->compare($leftData, $rightData); - } - - return $leftData === $rightData; + return is_string($leftData) && is_string($rightData) + ? 0 === $this->collator->compare($leftData, $rightData) + : $leftData === $rightData; } private function isArrayEqual(ArrayValueInterface $leftValue, ArrayValueInterface $rightValue): bool @@ -94,6 +86,10 @@ private function isObjectEqual(ObjectValueInterface $leftValue, ObjectValueInter return empty($leftProperties); } + /** + * @param Iterator $valueIterator + * @return null|array + */ private function getPropertiesWithoutDuplicates(Iterator $valueIterator): ?array { $valuesByProperty = []; diff --git a/src/Comparator/GreaterValueComparator.php b/src/Comparator/GreaterValueComparator.php index e24f50f..02dc616 100644 --- a/src/Comparator/GreaterValueComparator.php +++ b/src/Comparator/GreaterValueComparator.php @@ -14,21 +14,17 @@ final class GreaterValueComparator implements ComparatorInterface { - - private $collator; - - public function __construct(Collator $collator) - { - $this->collator = $collator; + public function __construct( + private readonly Collator $collator, + ) { } public function compare(ValueInterface $leftValue, ValueInterface $rightValue): bool { - if ($leftValue instanceof ScalarValueInterface && $rightValue instanceof ScalarValueInterface) { - return $this->isScalarGreater($leftValue, $rightValue); - } - - return false; + return + $leftValue instanceof ScalarValueInterface && + $rightValue instanceof ScalarValueInterface && + $this->isScalarGreater($leftValue, $rightValue); } private function isScalarGreater(ScalarValueInterface $leftValue, ScalarValueInterface $rightValue): bool @@ -39,10 +35,6 @@ private function isScalarGreater(ScalarValueInterface $leftValue, ScalarValueInt return $leftData > $rightData; } - if (is_string($leftData) && is_string($rightData)) { - return 1 === $this->collator->compare($leftData, $rightData); - } - - return false; + return is_string($leftData) && is_string($rightData) && 1 === $this->collator->compare($leftData, $rightData); } } diff --git a/src/Event/AfterArrayEvent.php b/src/Event/AfterArrayEvent.php index 37aef89..ae8b627 100644 --- a/src/Event/AfterArrayEvent.php +++ b/src/Event/AfterArrayEvent.php @@ -8,16 +8,20 @@ final class AfterArrayEvent implements AfterArrayEventInterface { - - private $path; - - public function __construct(PathInterface $path) - { - $this->path = $path; + public function __construct( + private readonly PathInterface $path, + ) { } public function getPath(): PathInterface { return $this->path; } + + public function with(?PathInterface $path = null): AfterArrayEventInterface + { + return new self( + path: $path ?? $this->path, + ); + } } diff --git a/src/Event/AfterArrayEventInterface.php b/src/Event/AfterArrayEventInterface.php index e2b12ed..aeda584 100644 --- a/src/Event/AfterArrayEventInterface.php +++ b/src/Event/AfterArrayEventInterface.php @@ -4,6 +4,9 @@ namespace Remorhaz\JSON\Data\Event; +use Remorhaz\JSON\Data\Path\PathInterface; + interface AfterArrayEventInterface extends EventInterface { + public function with(?PathInterface $path = null): AfterArrayEventInterface; } diff --git a/src/Event/AfterElementEvent.php b/src/Event/AfterElementEvent.php index f5e1a7c..ee5eed1 100644 --- a/src/Event/AfterElementEvent.php +++ b/src/Event/AfterElementEvent.php @@ -8,15 +8,10 @@ final class AfterElementEvent implements AfterElementEventInterface { - - private $index; - - private $path; - - public function __construct(int $index, PathInterface $path) - { - $this->index = $index; - $this->path = $path; + public function __construct( + private readonly int $index, + private readonly PathInterface $path, + ) { } public function getPath(): PathInterface @@ -28,4 +23,12 @@ public function getIndex(): int { return $this->index; } + + public function with(?PathInterface $path = null, ?int $index = null): AfterElementEventInterface + { + return new self( + index: $index ?? $this->index, + path: $path ?? $this->path, + ); + } } diff --git a/src/Event/AfterElementEventInterface.php b/src/Event/AfterElementEventInterface.php index 73b1a6d..e131250 100644 --- a/src/Event/AfterElementEventInterface.php +++ b/src/Event/AfterElementEventInterface.php @@ -4,6 +4,9 @@ namespace Remorhaz\JSON\Data\Event; +use Remorhaz\JSON\Data\Path\PathInterface; + interface AfterElementEventInterface extends ElementEventInterface { + public function with(?PathInterface $path = null, ?int $index = null,): AfterElementEventInterface; } diff --git a/src/Event/AfterObjectEvent.php b/src/Event/AfterObjectEvent.php index dba4afa..965b099 100644 --- a/src/Event/AfterObjectEvent.php +++ b/src/Event/AfterObjectEvent.php @@ -8,16 +8,20 @@ final class AfterObjectEvent implements AfterObjectEventInterface { - - private $path; - - public function __construct(PathInterface $path) - { - $this->path = $path; + public function __construct( + private readonly PathInterface $path, + ) { } public function getPath(): PathInterface { return $this->path; } + + public function with(?PathInterface $path = null): AfterObjectEventInterface + { + return new self( + path: $path ?? $this->path, + ); + } } diff --git a/src/Event/AfterObjectEventInterface.php b/src/Event/AfterObjectEventInterface.php index bff27f1..f0b6964 100644 --- a/src/Event/AfterObjectEventInterface.php +++ b/src/Event/AfterObjectEventInterface.php @@ -4,6 +4,9 @@ namespace Remorhaz\JSON\Data\Event; +use Remorhaz\JSON\Data\Path\PathInterface; + interface AfterObjectEventInterface extends EventInterface { + public function with(?PathInterface $path = null): AfterObjectEventInterface; } diff --git a/src/Event/AfterPropertyEvent.php b/src/Event/AfterPropertyEvent.php index e5ddb06..6d1526d 100644 --- a/src/Event/AfterPropertyEvent.php +++ b/src/Event/AfterPropertyEvent.php @@ -8,15 +8,10 @@ final class AfterPropertyEvent implements AfterPropertyEventInterface { - - private $name; - - private $path; - - public function __construct(string $name, PathInterface $path) - { - $this->name = $name; - $this->path = $path; + public function __construct( + private readonly string $name, + private readonly PathInterface $path, + ) { } public function getName(): string @@ -28,4 +23,12 @@ public function getPath(): PathInterface { return $this->path; } + + public function with(?PathInterface $path = null, ?string $name = null): AfterPropertyEventInterface + { + return new self( + name: $name ?? $this->name, + path: $path ?? $this->path, + ); + } } diff --git a/src/Event/AfterPropertyEventInterface.php b/src/Event/AfterPropertyEventInterface.php index a022948..e8b6eb6 100644 --- a/src/Event/AfterPropertyEventInterface.php +++ b/src/Event/AfterPropertyEventInterface.php @@ -4,6 +4,9 @@ namespace Remorhaz\JSON\Data\Event; +use Remorhaz\JSON\Data\Path\PathInterface; + interface AfterPropertyEventInterface extends PropertyEventInterface { + public function with(?PathInterface $path = null, ?string $name = null): AfterPropertyEventInterface; } diff --git a/src/Event/BeforeArrayEvent.php b/src/Event/BeforeArrayEvent.php index 846620e..cc723e9 100644 --- a/src/Event/BeforeArrayEvent.php +++ b/src/Event/BeforeArrayEvent.php @@ -8,16 +8,20 @@ final class BeforeArrayEvent implements BeforeArrayEventInterface { - - private $path; - - public function __construct(PathInterface $path) - { - $this->path = $path; + public function __construct( + private readonly PathInterface $path, + ) { } public function getPath(): PathInterface { return $this->path; } + + public function with(?PathInterface $path = null): BeforeArrayEventInterface + { + return new self( + path: $path ?? $this->path, + ); + } } diff --git a/src/Event/BeforeArrayEventInterface.php b/src/Event/BeforeArrayEventInterface.php index dd32c4c..8ee5700 100644 --- a/src/Event/BeforeArrayEventInterface.php +++ b/src/Event/BeforeArrayEventInterface.php @@ -4,6 +4,9 @@ namespace Remorhaz\JSON\Data\Event; +use Remorhaz\JSON\Data\Path\PathInterface; + interface BeforeArrayEventInterface extends EventInterface { + public function with(?PathInterface $path = null): BeforeArrayEventInterface; } diff --git a/src/Event/BeforeElementEvent.php b/src/Event/BeforeElementEvent.php index f7bb7df..bc4e53a 100644 --- a/src/Event/BeforeElementEvent.php +++ b/src/Event/BeforeElementEvent.php @@ -8,15 +8,10 @@ final class BeforeElementEvent implements BeforeElementEventInterface { - - private $index; - - private $path; - - public function __construct(int $index, PathInterface $path) - { - $this->index = $index; - $this->path = $path; + public function __construct( + private readonly int $index, + private readonly PathInterface $path, + ) { } public function getIndex(): int @@ -24,9 +19,16 @@ public function getIndex(): int return $this->index; } - public function getPath(): PathInterface { return $this->path; } + + public function with(?PathInterface $path = null, ?int $index = null): BeforeElementEventInterface + { + return new self( + index: $index ?? $this->index, + path: $path ?? $this->path, + ); + } } diff --git a/src/Event/BeforeElementEventInterface.php b/src/Event/BeforeElementEventInterface.php index 86328ba..eb634fc 100644 --- a/src/Event/BeforeElementEventInterface.php +++ b/src/Event/BeforeElementEventInterface.php @@ -4,6 +4,9 @@ namespace Remorhaz\JSON\Data\Event; +use Remorhaz\JSON\Data\Path\PathInterface; + interface BeforeElementEventInterface extends ElementEventInterface { + public function with(?PathInterface $path = null, ?int $index = null,): BeforeElementEventInterface; } diff --git a/src/Event/BeforeObjectEvent.php b/src/Event/BeforeObjectEvent.php index 61b3d2b..a0f86ac 100644 --- a/src/Event/BeforeObjectEvent.php +++ b/src/Event/BeforeObjectEvent.php @@ -8,16 +8,20 @@ final class BeforeObjectEvent implements BeforeObjectEventInterface { - - private $path; - - public function __construct(PathInterface $path) - { - $this->path = $path; + public function __construct( + private readonly PathInterface $path, + ) { } public function getPath(): PathInterface { return $this->path; } + + public function with(?PathInterface $path = null): BeforeObjectEventInterface + { + return new self( + path: $path ?? $this->path, + ); + } } diff --git a/src/Event/BeforeObjectEventInterface.php b/src/Event/BeforeObjectEventInterface.php index 5f5cc7c..3d3b24d 100644 --- a/src/Event/BeforeObjectEventInterface.php +++ b/src/Event/BeforeObjectEventInterface.php @@ -4,6 +4,9 @@ namespace Remorhaz\JSON\Data\Event; +use Remorhaz\JSON\Data\Path\PathInterface; + interface BeforeObjectEventInterface extends EventInterface { + public function with(?PathInterface $path = null): BeforeObjectEventInterface; } diff --git a/src/Event/BeforePropertyEvent.php b/src/Event/BeforePropertyEvent.php index c805799..987896f 100644 --- a/src/Event/BeforePropertyEvent.php +++ b/src/Event/BeforePropertyEvent.php @@ -8,15 +8,10 @@ final class BeforePropertyEvent implements BeforePropertyEventInterface { - - private $name; - - private $path; - - public function __construct(string $name, PathInterface $path) - { - $this->name = $name; - $this->path = $path; + public function __construct( + private readonly string $name, + private readonly PathInterface $path, + ) { } public function getName(): string @@ -28,4 +23,12 @@ public function getPath(): PathInterface { return $this->path; } + + public function with(?PathInterface $path = null, ?string $name = null): BeforePropertyEventInterface + { + return new self( + name: $name ?? $this->name, + path: $path ?? $this->path, + ); + } } diff --git a/src/Event/BeforePropertyEventInterface.php b/src/Event/BeforePropertyEventInterface.php index 2b62982..904b708 100644 --- a/src/Event/BeforePropertyEventInterface.php +++ b/src/Event/BeforePropertyEventInterface.php @@ -4,6 +4,9 @@ namespace Remorhaz\JSON\Data\Event; +use Remorhaz\JSON\Data\Path\PathInterface; + interface BeforePropertyEventInterface extends PropertyEventInterface { + public function with(?PathInterface $path = null, ?string $name = null): BeforePropertyEventInterface; } diff --git a/src/Event/ElementEventInterface.php b/src/Event/ElementEventInterface.php index 3dc624d..261adaf 100644 --- a/src/Event/ElementEventInterface.php +++ b/src/Event/ElementEventInterface.php @@ -4,8 +4,11 @@ namespace Remorhaz\JSON\Data\Event; +use Remorhaz\JSON\Data\Path\PathInterface; + interface ElementEventInterface extends EventInterface { - public function getIndex(): int; + + public function with(?PathInterface $path = null, ?int $index = null,): ElementEventInterface; } diff --git a/src/Event/EventInterface.php b/src/Event/EventInterface.php index 05dc43a..a491bfd 100644 --- a/src/Event/EventInterface.php +++ b/src/Event/EventInterface.php @@ -8,6 +8,7 @@ interface EventInterface { - public function getPath(): PathInterface; + + public function with(?PathInterface $path = null): EventInterface; } diff --git a/src/Event/Exception/InvalidScalarDataException.php b/src/Event/Exception/InvalidScalarDataException.php index 5dd3474..8aec0b0 100644 --- a/src/Event/Exception/InvalidScalarDataException.php +++ b/src/Event/Exception/InvalidScalarDataException.php @@ -10,16 +10,14 @@ final class InvalidScalarDataException extends LogicException implements ExceptionInterface, DataAwareInterface { - - private $data; - - public function __construct($data, Throwable $previous = null) - { - $this->data = $data; - parent::__construct("Invalid scalar data", 0, $previous); + public function __construct( + private readonly mixed $data, + ?Throwable $previous = null, + ) { + parent::__construct("Invalid scalar data", previous: $previous); } - public function getData() + public function getData(): mixed { return $this->data; } diff --git a/src/Event/PropertyEventInterface.php b/src/Event/PropertyEventInterface.php index 13be97b..a68afd4 100644 --- a/src/Event/PropertyEventInterface.php +++ b/src/Event/PropertyEventInterface.php @@ -4,8 +4,11 @@ namespace Remorhaz\JSON\Data\Event; +use Remorhaz\JSON\Data\Path\PathInterface; + interface PropertyEventInterface extends EventInterface { - public function getName(): string; + + public function with(?PathInterface $path = null, ?string $name = null): PropertyEventInterface; } diff --git a/src/Event/ScalarEvent.php b/src/Event/ScalarEvent.php index f46c7f0..934648d 100644 --- a/src/Event/ScalarEvent.php +++ b/src/Event/ScalarEvent.php @@ -6,23 +6,22 @@ use Remorhaz\JSON\Data\Path\PathInterface; +use function is_scalar; + final class ScalarEvent implements ScalarEventInterface { - - private $data; - - private $path; - - public function __construct($data, PathInterface $path) - { - if (null !== $data && !is_scalar($data)) { - throw new Exception\InvalidScalarDataException($data); - } - $this->data = $data; - $this->path = $path; + private int|string|float|bool|null $data; + + public function __construct( + mixed $data, + private readonly PathInterface $path, + ) { + $this->data = null === $data || is_scalar($data) + ? $data + : throw new Exception\InvalidScalarDataException($data); } - public function getData() + public function getData(): int|string|float|bool|null { return $this->data; } @@ -31,4 +30,15 @@ public function getPath(): PathInterface { return $this->path; } + + public function with( + ?PathInterface $path = null, + float|bool|int|string|null $data = null, + bool $forceData = false, + ): ScalarEventInterface { + return new self( + data: $forceData ? $data : ($data ?? $this->data), + path: $path ?? $this->path, + ); + } } diff --git a/src/Event/ScalarEventInterface.php b/src/Event/ScalarEventInterface.php index 5452228..7fabd38 100644 --- a/src/Event/ScalarEventInterface.php +++ b/src/Event/ScalarEventInterface.php @@ -4,8 +4,15 @@ namespace Remorhaz\JSON\Data\Event; +use Remorhaz\JSON\Data\Path\PathInterface; + interface ScalarEventInterface extends EventInterface { + public function getData(): int|string|float|bool|null; - public function getData(); + public function with( + ?PathInterface $path = null, + int|string|float|bool|null $data = null, + bool $forceData = false, + ): ScalarEventInterface; } diff --git a/src/Export/EventDecoder.php b/src/Export/EventDecoder.php index e97ddd1..7f248e1 100644 --- a/src/Export/EventDecoder.php +++ b/src/Export/EventDecoder.php @@ -20,9 +20,8 @@ final class EventDecoder implements EventDecoderInterface { - /** - * @param Iterator|EventInterface[] $events + * @param Iterator $events * @return NodeValueInterface|null */ public function exportEvents(Iterator $events): ?NodeValueInterface @@ -76,13 +75,12 @@ public function exportEvents(Iterator $events): ?NodeValueInterface return (new NodeValueFactory())->createValue($data); } + /** + * @param Iterator $events + * @return NodeValueInterface + */ public function exportExistingEvents(Iterator $events): NodeValueInterface { - $value = $this->exportEvents($events); - if (isset($value)) { - return $value; - } - - throw new Exception\NoValueToExportException(); + return $this->exportEvents($events) ?? throw new Exception\NoValueToExportException(); } } diff --git a/src/Export/EventExporterInterface.php b/src/Export/EventExporterInterface.php index 4f7053b..31b6a91 100644 --- a/src/Export/EventExporterInterface.php +++ b/src/Export/EventExporterInterface.php @@ -10,12 +10,15 @@ interface EventExporterInterface { - /** - * @param Iterator|EventInterface[] $events + * @param Iterator $events * @return NodeValueInterface|null */ public function exportEvents(Iterator $events): ?NodeValueInterface; + /** + * @param Iterator $events + * @return NodeValueInterface + */ public function exportExistingEvents(Iterator $events): NodeValueInterface; } diff --git a/src/Export/Exception/EncodingFailedException.php b/src/Export/Exception/EncodingFailedException.php index 36ed8bf..3e784f2 100644 --- a/src/Export/Exception/EncodingFailedException.php +++ b/src/Export/Exception/EncodingFailedException.php @@ -10,16 +10,14 @@ final class EncodingFailedException extends LogicException implements ExceptionInterface, DataAwareInterface { - - private $data; - - public function __construct($data, Throwable $previous = null) - { - $this->data = $data; - parent::__construct("Failed to encode data to JSON", 0, $previous); + public function __construct( + private readonly mixed $data, + ?Throwable $previous = null, + ) { + parent::__construct("Failed to encode data to JSON", previous: $previous); } - public function getData() + public function getData(): mixed { return $this->data; } diff --git a/src/Export/Exception/NoValueToExportException.php b/src/Export/Exception/NoValueToExportException.php index 07281a2..4ca6c88 100644 --- a/src/Export/Exception/NoValueToExportException.php +++ b/src/Export/Exception/NoValueToExportException.php @@ -9,9 +9,8 @@ final class NoValueToExportException extends LogicException implements ExceptionInterface { - - public function __construct(Throwable $previous = null) + public function __construct(?Throwable $previous = null) { - parent::__construct("No value to export", 0, $previous); + parent::__construct("No value to export", previous: $previous); } } diff --git a/src/Export/Exception/UnexpectedValueException.php b/src/Export/Exception/UnexpectedValueException.php index 1cd38b6..3d2d783 100644 --- a/src/Export/Exception/UnexpectedValueException.php +++ b/src/Export/Exception/UnexpectedValueException.php @@ -10,13 +10,11 @@ final class UnexpectedValueException extends DomainException implements ExceptionInterface { - - private $value; - - public function __construct(ValueInterface $value, Throwable $previous = null) - { - $this->value = $value; - parent::__construct("Unexpected value", 0, $previous); + public function __construct( + private ValueInterface $value, + ?Throwable $previous = null, + ) { + parent::__construct("Unexpected value", previous: $previous); } public function getValue(): ValueInterface diff --git a/src/Export/Exception/UnknownEventException.php b/src/Export/Exception/UnknownEventException.php index c99b1ed..ba254c7 100644 --- a/src/Export/Exception/UnknownEventException.php +++ b/src/Export/Exception/UnknownEventException.php @@ -10,13 +10,11 @@ final class UnknownEventException extends LogicException implements ExceptionInterface { - - private $event; - - public function __construct(EventInterface $event, Throwable $previous = null) - { - $this->event = $event; - parent::__construct("Unknown event", 0, $previous); + public function __construct( + private readonly EventInterface $event, + ?Throwable $previous = null, + ) { + parent::__construct("Unknown event", previous: $previous); } public function getEvent(): EventInterface diff --git a/src/Export/ValueDecoder.php b/src/Export/ValueDecoder.php index be200e4..0367d13 100644 --- a/src/Export/ValueDecoder.php +++ b/src/Export/ValueDecoder.php @@ -11,31 +11,34 @@ final class ValueDecoder implements ValueDecoderInterface { - - public function exportValue(ValueInterface $value) + public function exportValue(ValueInterface $value): mixed { - if ($value instanceof ScalarValueInterface) { - return $value->getData(); - } - - if ($value instanceof ArrayValueInterface) { - $result = []; - foreach ($value->createChildIterator() as $index => $element) { - $result[$index] = $this->exportValue($element); - } + return match (true) { + $value instanceof ScalarValueInterface => $value->getData(), + $value instanceof ArrayValueInterface => $this->exportArrayValue($value), + $value instanceof ObjectValueInterface => $this->exportObjectValue($value), + default => throw new Exception\UnexpectedValueException($value), + }; + } - return $result; + private function exportArrayValue(ArrayValueInterface $value): array + { + $result = []; + foreach ($value->createChildIterator() as $index => $element) { + /** @psalm-var mixed */ + $result[$index] = $this->exportValue($element); } - if ($value instanceof ObjectValueInterface) { - $result = (object) []; - foreach ($value->createChildIterator() as $name => $property) { - $result->{$name} = $this->exportValue($property); - } + return $result; + } - return $result; + private function exportObjectValue(ObjectValueInterface $value): object + { + $result = (object) []; + foreach ($value->createChildIterator() as $name => $property) { + $result->{$name} = $this->exportValue($property); } - throw new Exception\UnexpectedValueException($value); + return $result; } } diff --git a/src/Export/ValueEncoder.php b/src/Export/ValueEncoder.php index 1337c5e..06d82cb 100644 --- a/src/Export/ValueEncoder.php +++ b/src/Export/ValueEncoder.php @@ -18,23 +18,21 @@ */ final class ValueEncoder implements ValueEncoderInterface { - - private $decoder; - - public function __construct(ValueDecoderInterface $decoder) - { - $this->decoder = $decoder; + public function __construct( + private readonly ValueDecoderInterface $decoder, + ) { } public function exportValue(ValueInterface $value): string { + /** @psalm-var mixed $decodedValue */ $decodedValue = $this ->decoder ->exportValue($value); try { return json_encode( $decodedValue, - JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR + JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR, ); } catch (Throwable $e) { throw new Exception\EncodingFailedException($decodedValue, $e); diff --git a/src/Export/ValueEncoderInterface.php b/src/Export/ValueEncoderInterface.php index 965cb1b..c851e79 100644 --- a/src/Export/ValueEncoderInterface.php +++ b/src/Export/ValueEncoderInterface.php @@ -8,6 +8,5 @@ interface ValueEncoderInterface extends ValueExporterInterface { - public function exportValue(ValueInterface $value): string; } diff --git a/src/Export/ValueExporterInterface.php b/src/Export/ValueExporterInterface.php index 719cd75..87175f0 100644 --- a/src/Export/ValueExporterInterface.php +++ b/src/Export/ValueExporterInterface.php @@ -8,6 +8,5 @@ interface ValueExporterInterface { - - public function exportValue(ValueInterface $value); + public function exportValue(ValueInterface $value): mixed; } diff --git a/src/Path/Exception/ParentNotFoundException.php b/src/Path/Exception/ParentNotFoundException.php index 7395ab5..4ff498e 100644 --- a/src/Path/Exception/ParentNotFoundException.php +++ b/src/Path/Exception/ParentNotFoundException.php @@ -13,13 +13,11 @@ final class ParentNotFoundException extends LogicException implements ExceptionInterface, PathAwareInterface { - - private $path; - - public function __construct(PathInterface $path, Throwable $previous = null) - { - $this->path = $path; - parent::__construct("Parent not found in path {$this->buildPath()}", 0, $previous); + public function __construct( + private readonly PathInterface $path, + ?Throwable $previous = null, + ) { + parent::__construct("Parent not found in path {$this->buildPath()}", previous: $previous); } public function getPath(): PathInterface diff --git a/src/Path/Path.php b/src/Path/Path.php index 3416ab4..468121d 100644 --- a/src/Path/Path.php +++ b/src/Path/Path.php @@ -5,16 +5,19 @@ namespace Remorhaz\JSON\Data\Path; use function array_slice; +use function array_values; use function count; final class Path implements PathInterface { + /** + * @var list + */ + private array $elements; - private $elements; - - public function __construct(...$elements) + public function __construct(int|string ...$elements) { - $this->elements = $elements; + $this->elements = array_values($elements); } public function copyWithElement(int $index): PathInterface @@ -29,13 +32,14 @@ public function copyWithProperty(string $name): PathInterface public function copyParent(): PathInterface { - if (empty($this->elements)) { - throw new Exception\ParentNotFoundException($this); - } - - return new self(...array_slice($this->elements, 0, -1)); + return empty($this->elements) + ? throw new Exception\ParentNotFoundException($this) + : new self(...array_slice($this->elements, 0, -1)); } + /** + * @return list + */ public function getElements(): array { return $this->elements; diff --git a/src/Path/PathAwareInterface.php b/src/Path/PathAwareInterface.php index ddb8545..4fbac95 100644 --- a/src/Path/PathAwareInterface.php +++ b/src/Path/PathAwareInterface.php @@ -6,6 +6,5 @@ interface PathAwareInterface { - public function getPath(): PathInterface; } diff --git a/src/Path/PathInterface.php b/src/Path/PathInterface.php index 8da0f6f..f548e1a 100644 --- a/src/Path/PathInterface.php +++ b/src/Path/PathInterface.php @@ -6,13 +6,15 @@ interface PathInterface { - public function copyWithElement(int $index): PathInterface; public function copyWithProperty(string $name): PathInterface; public function copyParent(): PathInterface; + /** + * @return list + */ public function getElements(): array; public function equals(PathInterface $path): bool; diff --git a/src/Value/ArrayValueInterface.php b/src/Value/ArrayValueInterface.php index 44e26bc..288e3c9 100644 --- a/src/Value/ArrayValueInterface.php +++ b/src/Value/ArrayValueInterface.php @@ -4,6 +4,12 @@ namespace Remorhaz\JSON\Data\Value; +use Iterator; + interface ArrayValueInterface extends StructValueInterface { + /** + * @return Iterator + */ + public function createChildIterator(): Iterator; } diff --git a/src/Value/DataAwareInterface.php b/src/Value/DataAwareInterface.php index 28d619c..9906e0c 100644 --- a/src/Value/DataAwareInterface.php +++ b/src/Value/DataAwareInterface.php @@ -6,6 +6,5 @@ interface DataAwareInterface { - - public function getData(); + public function getData(): mixed; } diff --git a/src/Value/DecodedJson/Exception/InvalidElementKeyException.php b/src/Value/DecodedJson/Exception/InvalidElementKeyException.php index 6be1bb6..8159108 100644 --- a/src/Value/DecodedJson/Exception/InvalidElementKeyException.php +++ b/src/Value/DecodedJson/Exception/InvalidElementKeyException.php @@ -16,21 +16,12 @@ class InvalidElementKeyException extends RuntimeException implements ExceptionInterface, PathAwareInterface { - - private $key; - - private $path; - - /** - * @param mixed $key - * @param PathInterface $path - * @param Throwable|null $previous - */ - public function __construct($key, PathInterface $path, Throwable $previous = null) - { - $this->key = $key; - $this->path = $path; - parent::__construct($this->buildMessage(), 0, $previous); + public function __construct( + private readonly mixed $key, + private readonly PathInterface $path, + ?Throwable $previous = null, + ) { + parent::__construct($this->buildMessage(), previous: $previous); } private function buildMessage(): string @@ -38,7 +29,7 @@ private function buildMessage(): string return "Invalid element key in decoded JSON: {$this->buildKey()} at {$this->buildPath()}"; } - public function getKey() + public function getKey(): mixed { return $this->key; } diff --git a/src/Value/DecodedJson/Exception/InvalidNodeDataException.php b/src/Value/DecodedJson/Exception/InvalidNodeDataException.php index 6701bd4..f6809ca 100644 --- a/src/Value/DecodedJson/Exception/InvalidNodeDataException.php +++ b/src/Value/DecodedJson/Exception/InvalidNodeDataException.php @@ -16,16 +16,12 @@ class InvalidNodeDataException extends RuntimeException implements PathAwareInterface, DataAwareInterface { - - private $data; - - private $path; - - public function __construct($data, PathInterface $path, Throwable $previous = null) - { - $this->data = $data; - $this->path = $path; - parent::__construct($this->buildMessage(), 0, $previous); + public function __construct( + private readonly mixed $data, + private readonly PathInterface $path, + ?Throwable $previous = null, + ) { + parent::__construct($this->buildMessage(), previous: $previous); } private function buildMessage(): string @@ -33,7 +29,7 @@ private function buildMessage(): string return "Invalid data in decoded JSON at {$this->buildPath()}"; } - public function getData() + public function getData(): mixed { return $this->data; } diff --git a/src/Value/DecodedJson/NodeArrayValue.php b/src/Value/DecodedJson/NodeArrayValue.php index 9d8fd4b..5add4aa 100644 --- a/src/Value/DecodedJson/NodeArrayValue.php +++ b/src/Value/DecodedJson/NodeArrayValue.php @@ -9,30 +9,26 @@ use Remorhaz\JSON\Data\Path\PathInterface; use Remorhaz\JSON\Data\Value\NodeValueInterface; +use function is_int; + final class NodeArrayValue implements NodeValueInterface, ArrayValueInterface { - - private $data; - - private $path; - - private $valueFactory; - public function __construct( - array $data, - PathInterface $path, - NodeValueFactoryInterface $valueFactory + private readonly array $data, + private readonly PathInterface $path, + private readonly NodeValueFactoryInterface $valueFactory, ) { - $this->data = $data; - $this->path = $path; - $this->valueFactory = $valueFactory; } + /** + * @return Iterator + */ public function createChildIterator(): Iterator { $validIndex = 0; + /** @psalm-var mixed $element */ foreach ($this->data as $index => $element) { - if ($index !== $validIndex++) { + if (!is_int($index) || $index != $validIndex++) { throw new Exception\InvalidElementKeyException($index, $this->path); } yield $index => $this diff --git a/src/Value/DecodedJson/NodeObjectValue.php b/src/Value/DecodedJson/NodeObjectValue.php index 318b3c5..2b56a2f 100644 --- a/src/Value/DecodedJson/NodeObjectValue.php +++ b/src/Value/DecodedJson/NodeObjectValue.php @@ -4,40 +4,28 @@ namespace Remorhaz\JSON\Data\Value\DecodedJson; -use Generator; use Iterator; use Remorhaz\JSON\Data\Value\ObjectValueInterface; use Remorhaz\JSON\Data\Path\PathInterface; use Remorhaz\JSON\Data\Value\NodeValueInterface; -use stdClass; final class NodeObjectValue implements NodeValueInterface, ObjectValueInterface { - - private $data; - - private $path; - - private $valueFactory; - public function __construct( - stdClass $data, - PathInterface $path, - NodeValueFactoryInterface $valueFactory + private readonly object $data, + private readonly PathInterface $path, + private readonly NodeValueFactoryInterface $valueFactory, ) { - $this->data = $data; - $this->path = $path; - $this->valueFactory = $valueFactory; } + /** + * @return Iterator + */ public function createChildIterator(): Iterator { - return $this->createChildGenerator(); - } - - private function createChildGenerator(): Generator - { + /** @psalm-var mixed $property */ foreach (get_object_vars($this->data) as $name => $property) { + /** @psalm-suppress RedundantCast */ $stringName = (string) $name; yield $stringName => $this ->valueFactory diff --git a/src/Value/DecodedJson/NodeScalarValue.php b/src/Value/DecodedJson/NodeScalarValue.php index b106765..04347de 100644 --- a/src/Value/DecodedJson/NodeScalarValue.php +++ b/src/Value/DecodedJson/NodeScalarValue.php @@ -8,23 +8,22 @@ use Remorhaz\JSON\Data\Value\NodeValueInterface; use Remorhaz\JSON\Data\Value\ScalarValueInterface; +use function is_scalar; + final class NodeScalarValue implements NodeValueInterface, ScalarValueInterface { - - private $data; - - private $path; - - public function __construct($data, PathInterface $path) - { - if (null !== $data && !is_scalar($data)) { - throw new Exception\InvalidNodeDataException($data, $path); - } - $this->data = $data; - $this->path = $path; + private int|float|string|bool|null $data; + + public function __construct( + mixed $data, + private readonly PathInterface $path, + ) { + $this->data = null === $data || is_scalar($data) + ? $data + : throw new Exception\InvalidNodeDataException($data, $path); } - public function getData() + public function getData(): int|float|string|bool|null { return $this->data; } diff --git a/src/Value/DecodedJson/NodeValueFactory.php b/src/Value/DecodedJson/NodeValueFactory.php index 5853b50..33f7c15 100644 --- a/src/Value/DecodedJson/NodeValueFactory.php +++ b/src/Value/DecodedJson/NodeValueFactory.php @@ -7,14 +7,13 @@ use Remorhaz\JSON\Data\Path\Path; use Remorhaz\JSON\Data\Value\NodeValueInterface; use Remorhaz\JSON\Data\Path\PathInterface; -use stdClass; use function is_array; +use function is_object; use function is_scalar; final class NodeValueFactory implements NodeValueFactoryInterface { - public static function create(): NodeValueFactoryInterface { return new self(); @@ -22,28 +21,17 @@ public static function create(): NodeValueFactoryInterface /** * {@inheritDoc} - * - * @param array|bool|float|int|stdClass|string|null $data - * @param PathInterface|null $path - * @return NodeValueInterface */ - public function createValue($data, ?PathInterface $path = null): NodeValueInterface + public function createValue(mixed $data, ?PathInterface $path = null): NodeValueInterface { - if (!isset($path)) { - $path = new Path(); - } - if (null === $data || is_scalar($data)) { - return new NodeScalarValue($data, $path); - } - - if (is_array($data)) { - return new NodeArrayValue($data, $path, $this); - } - - if ($data instanceof stdClass) { - return new NodeObjectValue($data, $path, $this); - } - - throw new Exception\InvalidNodeDataException($data, $path); + $path ??= new Path(); + + return match (true) { + null === $data, + is_scalar($data) => new NodeScalarValue($data, $path), + is_array($data) => new NodeArrayValue($data, $path, $this), + is_object($data) => new NodeObjectValue($data, $path, $this), + default => throw new Exception\InvalidNodeDataException($data, $path), + }; } } diff --git a/src/Value/DecodedJson/NodeValueFactoryInterface.php b/src/Value/DecodedJson/NodeValueFactoryInterface.php index 8b6af20..cc83459 100644 --- a/src/Value/DecodedJson/NodeValueFactoryInterface.php +++ b/src/Value/DecodedJson/NodeValueFactoryInterface.php @@ -6,17 +6,11 @@ use Remorhaz\JSON\Data\Value\NodeValueInterface; use Remorhaz\JSON\Data\Path\PathInterface; -use stdClass; interface NodeValueFactoryInterface { - /** * Converts decoded JSON to JSON node value. - * - * @param array|bool|float|int|stdClass|string|null $data - * @param PathInterface|null $path - * @return NodeValueInterface */ - public function createValue($data, ?PathInterface $path = null): NodeValueInterface; + public function createValue(mixed $data, ?PathInterface $path = null): NodeValueInterface; } diff --git a/src/Value/EncodedJson/Exception/JsonNotDecodedException.php b/src/Value/EncodedJson/Exception/JsonNotDecodedException.php index 0cf8fe1..5d71077 100644 --- a/src/Value/EncodedJson/Exception/JsonNotDecodedException.php +++ b/src/Value/EncodedJson/Exception/JsonNotDecodedException.php @@ -9,13 +9,11 @@ final class JsonNotDecodedException extends RuntimeException implements ExceptionInterface { - - private $json; - - public function __construct(string $json, Throwable $previous = null) - { - $this->json = $json; - parent::__construct("Failed to decode JSON", 0, $previous); + public function __construct( + private readonly string $json, + ?Throwable $previous = null, + ) { + parent::__construct("Failed to decode JSON", previous: $previous); } public function getJson(): string diff --git a/src/Value/EncodedJson/NodeValueFactory.php b/src/Value/EncodedJson/NodeValueFactory.php index aca7845..1b8a1d8 100644 --- a/src/Value/EncodedJson/NodeValueFactory.php +++ b/src/Value/EncodedJson/NodeValueFactory.php @@ -5,8 +5,7 @@ namespace Remorhaz\JSON\Data\Value\EncodedJson; use Remorhaz\JSON\Data\Path\PathInterface; -use Remorhaz\JSON\Data\Value\DecodedJson\NodeValueFactory as DecodedJsonNodeValueFactory; -use Remorhaz\JSON\Data\Value\DecodedJson\NodeValueFactoryInterface as DecodedJsonNodeValueFactoryInterface; +use Remorhaz\JSON\Data\Value\DecodedJson; use Remorhaz\JSON\Data\Value\NodeValueInterface; use Throwable; @@ -16,22 +15,20 @@ final class NodeValueFactory implements NodeValueFactoryInterface { - - private $decodedJsonNodeValueFactory; - public static function create(): NodeValueFactoryInterface { - return new self(DecodedJsonNodeValueFactory::create()); + return new self(DecodedJson\NodeValueFactory::create()); } - public function __construct(DecodedJsonNodeValueFactoryInterface $decodedJsonNodeValueFactory) - { - $this->decodedJsonNodeValueFactory = $decodedJsonNodeValueFactory; + public function __construct( + private readonly DecodedJson\NodeValueFactoryInterface $decodedJsonNodeValueFactory, + ) { } public function createValue(string $json, ?PathInterface $path = null): NodeValueInterface { try { + /** @psalm-var mixed $decodedData */ $decodedData = json_decode($json, false, 512, JSON_THROW_ON_ERROR); } catch (Throwable $e) { throw new Exception\JsonNotDecodedException($json, $e); diff --git a/src/Value/EncodedJson/NodeValueFactoryInterface.php b/src/Value/EncodedJson/NodeValueFactoryInterface.php index fe5713a..7fa0097 100644 --- a/src/Value/EncodedJson/NodeValueFactoryInterface.php +++ b/src/Value/EncodedJson/NodeValueFactoryInterface.php @@ -9,6 +9,5 @@ interface NodeValueFactoryInterface { - public function createValue(string $json, ?PathInterface $path = null): NodeValueInterface; } diff --git a/src/Value/ObjectValueInterface.php b/src/Value/ObjectValueInterface.php index 20d62c1..b637ae4 100644 --- a/src/Value/ObjectValueInterface.php +++ b/src/Value/ObjectValueInterface.php @@ -4,6 +4,12 @@ namespace Remorhaz\JSON\Data\Value; +use Iterator; + interface ObjectValueInterface extends StructValueInterface { + /** + * @return Iterator + */ + public function createChildIterator(): Iterator; } diff --git a/src/Value/ScalarValueInterface.php b/src/Value/ScalarValueInterface.php index 8abcef1..95e0e57 100644 --- a/src/Value/ScalarValueInterface.php +++ b/src/Value/ScalarValueInterface.php @@ -6,4 +6,5 @@ interface ScalarValueInterface extends ValueInterface, DataAwareInterface { + public function getData(): int|float|string|bool|null; } diff --git a/src/Value/StructValueInterface.php b/src/Value/StructValueInterface.php index 4a82bec..d51d686 100644 --- a/src/Value/StructValueInterface.php +++ b/src/Value/StructValueInterface.php @@ -8,6 +8,8 @@ interface StructValueInterface extends ValueInterface { - + /** + * @return Iterator + */ public function createChildIterator(): Iterator; } diff --git a/src/Walker/EventGenerator.php b/src/Walker/EventGenerator.php index 2b65efe..543c277 100644 --- a/src/Walker/EventGenerator.php +++ b/src/Walker/EventGenerator.php @@ -4,7 +4,7 @@ namespace Remorhaz\JSON\Data\Walker; -use Generator; +use Iterator; use Remorhaz\JSON\Data\Event\AfterArrayEvent; use Remorhaz\JSON\Data\Event\AfterElementEvent; use Remorhaz\JSON\Data\Event\AfterElementEventInterface; @@ -27,18 +27,22 @@ final class EventGenerator { - - private $stack; - - private $path; - - public function __construct(NodeValueInterface $value, PathInterface $path) - { + /** + * @var list + */ + private array $stack; + + public function __construct( + NodeValueInterface $value, + private PathInterface $path, + ) { $this->stack = [$value]; - $this->path = $path; } - public function __invoke(): Generator + /** + * @return Iterator + */ + public function __invoke(): Iterator { while (true) { if (empty($this->stack)) { @@ -68,7 +72,11 @@ public function __invoke(): Generator } } - private function onEvent(EventInterface $event): Generator + /** + * @param EventInterface $event + * @return Iterator + */ + private function onEvent(EventInterface $event): Iterator { switch (true) { case $event instanceof BeforeElementEventInterface: @@ -93,12 +101,20 @@ private function onEvent(EventInterface $event): Generator yield $event; } - private function onScalarValue(ScalarValueInterface $value): Generator + /** + * @param ScalarValueInterface $value + * @return Iterator + */ + private function onScalarValue(ScalarValueInterface $value): Iterator { yield new ScalarEvent($value->getData(), $this->path); } - private function onArrayValue(ArrayValueInterface $value): Generator + /** + * @param ArrayValueInterface $value + * @return Iterator + */ + private function onArrayValue(ArrayValueInterface $value): Iterator { $localStack = []; foreach ($value->createChildIterator() as $index => $child) { @@ -109,18 +125,22 @@ private function onArrayValue(ArrayValueInterface $value): Generator $localStack, new BeforeElementEvent($index, $elementPath), $child, - new AfterElementEvent($index, $elementPath) + new AfterElementEvent($index, $elementPath), ); } array_push( $this->stack, new AfterArrayEvent($this->path), - ...array_reverse($localStack) + ...array_reverse($localStack), ); yield new BeforeArrayEvent($this->path); } - private function onObjectValue(ObjectValueInterface $value): Generator + /** + * @param ObjectValueInterface $value + * @return Iterator + */ + private function onObjectValue(ObjectValueInterface $value): Iterator { $localStack = []; foreach ($value->createChildIterator() as $name => $child) { @@ -131,13 +151,13 @@ private function onObjectValue(ObjectValueInterface $value): Generator $localStack, new BeforePropertyEvent($name, $elementPath), $child, - new AfterPropertyEvent($name, $elementPath) + new AfterPropertyEvent($name, $elementPath), ); } array_push( $this->stack, new AfterObjectEvent($this->path), - ...array_reverse($localStack) + ...array_reverse($localStack), ); yield new BeforeObjectEvent($this->path); } diff --git a/src/Walker/Exception/UnexpectedEntityException.php b/src/Walker/Exception/UnexpectedEntityException.php index 30da8ab..15daff8 100644 --- a/src/Walker/Exception/UnexpectedEntityException.php +++ b/src/Walker/Exception/UnexpectedEntityException.php @@ -9,16 +9,14 @@ final class UnexpectedEntityException extends LogicException implements ExceptionInterface { - - private $entity; - - public function __construct($entity, Throwable $previous = null) - { - $this->entity = $entity; - parent::__construct("Invalid entity", 0, $previous); + public function __construct( + private readonly mixed $entity, + ?Throwable $previous = null, + ) { + parent::__construct("Invalid entity", previous: $previous); } - public function getEntity() + public function getEntity(): mixed { return $this->entity; } diff --git a/src/Walker/MutationInterface.php b/src/Walker/MutationInterface.php index c88fd6e..c8d3e36 100644 --- a/src/Walker/MutationInterface.php +++ b/src/Walker/MutationInterface.php @@ -4,12 +4,17 @@ namespace Remorhaz\JSON\Data\Walker; +use Iterator; use Remorhaz\JSON\Data\Event\EventInterface; interface MutationInterface { - - public function __invoke(EventInterface $event, ValueWalkerInterface $valueWalker); + /** + * @param EventInterface $event + * @param ValueWalkerInterface $valueWalker + * @return Iterator + */ + public function __invoke(EventInterface $event, ValueWalkerInterface $valueWalker): Iterator; public function reset(): void; } diff --git a/src/Walker/ValueWalker.php b/src/Walker/ValueWalker.php index b0f0e9e..4923a49 100644 --- a/src/Walker/ValueWalker.php +++ b/src/Walker/ValueWalker.php @@ -5,21 +5,32 @@ namespace Remorhaz\JSON\Data\Walker; use Iterator; +use Remorhaz\JSON\Data\Event\EventInterface; use Remorhaz\JSON\Data\Path\PathInterface; use Remorhaz\JSON\Data\Value\NodeValueInterface; final class ValueWalker implements ValueWalkerInterface { - + /** + * @param NodeValueInterface $value + * @param PathInterface $path + * @return Iterator + */ public function createEventIterator(NodeValueInterface $value, PathInterface $path): Iterator { return (new EventGenerator($value, $path))(); } + /** + * @param NodeValueInterface $value + * @param PathInterface $path + * @param MutationInterface $modifier + * @return Iterator + */ public function createMutableEventIterator( NodeValueInterface $value, PathInterface $path, - MutationInterface $modifier + MutationInterface $modifier, ): Iterator { $modifier->reset(); foreach ($this->createEventIterator($value, $path) as $event) { diff --git a/src/Walker/ValueWalkerInterface.php b/src/Walker/ValueWalkerInterface.php index 6bbb28c..f9e4af7 100644 --- a/src/Walker/ValueWalkerInterface.php +++ b/src/Walker/ValueWalkerInterface.php @@ -10,7 +10,6 @@ interface ValueWalkerInterface { - public function createEventIterator(NodeValueInterface $value, PathInterface $path): Iterator; public function createMutableEventIterator( diff --git a/tests/Comparator/ContainsValueComparatorTest.php b/tests/Comparator/ContainsValueComparatorTest.php index f8bd7c4..53c064e 100644 --- a/tests/Comparator/ContainsValueComparatorTest.php +++ b/tests/Comparator/ContainsValueComparatorTest.php @@ -6,6 +6,8 @@ use Collator; use Generator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Comparator\ContainsValueComparator; use Remorhaz\JSON\Data\Value\EncodedJson\NodeValueFactory; @@ -13,29 +15,25 @@ use Remorhaz\JSON\Data\Value\ScalarValueInterface; use Remorhaz\JSON\Data\Value\ValueInterface; -/** - * @covers \Remorhaz\JSON\Data\Comparator\ContainsValueComparator - */ +#[CoversClass(ContainsValueComparator::class)] class ContainsValueComparatorTest extends TestCase { - - /** - * @param string $data - * @param string $containedData - * @dataProvider providerMatchingValues - */ + #[DataProvider('providerMatchingValues')] public function testCompare_MatchingValues_ReturnsTrue(string $data, string $containedData): void { $comparator = new ContainsValueComparator(new Collator('UTF-8')); $nodeValueFactory = NodeValueFactory::create(); $actualValue = $comparator->compare( $nodeValueFactory->createValue($data), - $nodeValueFactory->createValue($containedData) + $nodeValueFactory->createValue($containedData), ); self::assertTrue($actualValue); } - public function providerMatchingValues(): array + /** + * @return iterable + */ + public static function providerMatchingValues(): iterable { return [ 'Same string' => ['"a"', '"a"'], @@ -56,23 +54,22 @@ public function providerMatchingValues(): array ]; } - /** - * @param string $data - * @param string $containedData - * @dataProvider providerNonMatchingValues - */ + #[DataProvider('providerNonMatchingValues')] public function testCompare_NonMatchingValues_ReturnsFalse(string $data, string $containedData): void { $comparator = new ContainsValueComparator(new Collator('UTF-8')); $nodeValueFactory = NodeValueFactory::create(); $actualValue = $comparator->compare( $nodeValueFactory->createValue($data), - $nodeValueFactory->createValue($containedData) + $nodeValueFactory->createValue($containedData), ); self::assertFalse($actualValue); } - public function providerNonMatchingValues(): array + /** + * @return iterable + */ + public static function providerNonMatchingValues(): iterable { return [ 'Array and scalar' => ['["a"]', '"a"'], @@ -90,7 +87,7 @@ public function providerNonMatchingValues(): array public function testCompare_DuplicatedPropertyInLeftValue_ReturnsFalse(): void { $comparator = new ContainsValueComparator(new Collator('UTF-8')); - $value = $this->createMock(ScalarValueInterface::class); + $value = self::createStub(ScalarValueInterface::class); $value ->method('getData') ->willReturn('b'); @@ -104,7 +101,7 @@ public function testCompare_DuplicatedPropertyInRightValue_ReturnsFalse(): void { $comparator = new ContainsValueComparator(new Collator('UTF-8')); $leftValue = NodeValueFactory::create()->createValue('{"a":"b"}'); - $value = $this->createMock(ScalarValueInterface::class); + $value = self::createStub(ScalarValueInterface::class); $value ->method('getData') ->willReturn('b'); @@ -115,7 +112,7 @@ public function testCompare_DuplicatedPropertyInRightValue_ReturnsFalse(): void private function createObjectWithDuplicatedProperty(string $name, ValueInterface $value): ValueInterface { - $object = $this->createMock(ObjectValueInterface::class); + $object = self::createStub(ObjectValueInterface::class); $generator = function () use ($name, $value): Generator { yield $name => $value; yield $name => $value; diff --git a/tests/Comparator/EqualValueComparatorTest.php b/tests/Comparator/EqualValueComparatorTest.php index 3daa7bf..850aef9 100644 --- a/tests/Comparator/EqualValueComparatorTest.php +++ b/tests/Comparator/EqualValueComparatorTest.php @@ -6,6 +6,8 @@ use Collator; use Generator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Comparator\EqualValueComparator; use Remorhaz\JSON\Data\Value\EncodedJson\NodeValueFactory; @@ -13,29 +15,25 @@ use Remorhaz\JSON\Data\Value\ScalarValueInterface; use Remorhaz\JSON\Data\Value\ValueInterface; -/** - * @covers \Remorhaz\JSON\Data\Comparator\EqualValueComparator - */ +#[CoversClass(EqualValueComparator::class)] class EqualValueComparatorTest extends TestCase { - - /** - * @param string $data - * @param string $equalData - * @dataProvider providerMatchingValues - */ + #[DataProvider('providerMatchingValues')] public function testCompare_MatchingValues_ReturnsTrue(string $data, string $equalData): void { $comparator = new EqualValueComparator(new Collator('UTF-8')); $nodeValueFactory = NodeValueFactory::create(); $actualValue = $comparator->compare( $nodeValueFactory->createValue($data), - $nodeValueFactory->createValue($equalData) + $nodeValueFactory->createValue($equalData), ); self::assertTrue($actualValue); } - public function providerMatchingValues(): array + /** + * @return iterable + */ + public static function providerMatchingValues(): iterable { return [ 'Same string' => ['"a"', '"a"'], @@ -51,23 +49,22 @@ public function providerMatchingValues(): array ]; } - /** - * @param string $data - * @param string $equalData - * @dataProvider providerNonMatchingValues - */ + #[DataProvider('providerNonMatchingValues')] public function testCompare_NonMatchingValues_ReturnsFalse(string $data, string $equalData): void { $comparator = new EqualValueComparator(new Collator('UTF-8')); $nodeValueFactory = NodeValueFactory::create(); $actualValue = $comparator->compare( $nodeValueFactory->createValue($data), - $nodeValueFactory->createValue($equalData) + $nodeValueFactory->createValue($equalData), ); self::assertFalse($actualValue); } - public function providerNonMatchingValues(): array + /** + * @return iterable + */ + public static function providerNonMatchingValues(): iterable { return [ 'Array and scalar' => ['["a"]', '"a"'], @@ -86,7 +83,7 @@ public function providerNonMatchingValues(): array public function testCompare_DuplicatedPropertyInLeftValue_ReturnsFalse(): void { $comparator = new EqualValueComparator(new Collator('UTF-8')); - $value = $this->createMock(ScalarValueInterface::class); + $value = self::createStub(ScalarValueInterface::class); $value ->method('getData') ->willReturn('b'); @@ -100,7 +97,7 @@ public function testCompare_DuplicatedPropertyInRightValue_ReturnsFalse(): void { $comparator = new EqualValueComparator(new Collator('UTF-8')); $leftValue = NodeValueFactory::create()->createValue('{"a":"b"}'); - $value = $this->createMock(ScalarValueInterface::class); + $value = self::createStub(ScalarValueInterface::class); $value ->method('getData') ->willReturn('b'); @@ -111,7 +108,7 @@ public function testCompare_DuplicatedPropertyInRightValue_ReturnsFalse(): void private function createObjectWithDuplicatedProperty(string $name, ValueInterface $value): ValueInterface { - $object = $this->createMock(ObjectValueInterface::class); + $object = self::createStub(ObjectValueInterface::class); $generator = function () use ($name, $value): Generator { yield $name => $value; yield $name => $value; diff --git a/tests/Comparator/GreaterValueComparatorTest.php b/tests/Comparator/GreaterValueComparatorTest.php index 9f1e2ba..9644d02 100644 --- a/tests/Comparator/GreaterValueComparatorTest.php +++ b/tests/Comparator/GreaterValueComparatorTest.php @@ -5,33 +5,31 @@ namespace Remorhaz\JSON\Data\Test\Comparator; use Collator; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Comparator\GreaterValueComparator; use Remorhaz\JSON\Data\Value\EncodedJson\NodeValueFactory; -/** - * @covers \Remorhaz\JSON\Data\Comparator\GreaterValueComparator - */ +#[CoversClass(GreaterValueComparator::class)] class GreaterValueComparatorTest extends TestCase { - - /** - * @param string $leftData - * @param string $rightData - * @dataProvider providerMatchingValues - */ + #[DataProvider('providerMatchingValues')] public function testCompare_MatchingValues_ReturnsTrue(string $leftData, string $rightData): void { $comparator = new GreaterValueComparator(new Collator('UTF-8')); $nodeValueFactory = NodeValueFactory::create(); $actualValue = $comparator->compare( $nodeValueFactory->createValue($leftData), - $nodeValueFactory->createValue($rightData) + $nodeValueFactory->createValue($rightData), ); self::assertTrue($actualValue); } - public function providerMatchingValues(): array + /** + * @return iterable + */ + public static function providerMatchingValues(): iterable { return [ 'Left string is greater' => ['"b"', '"a"'], @@ -41,23 +39,22 @@ public function providerMatchingValues(): array ]; } - /** - * @param string $leftData - * @param string $rightData - * @dataProvider providerNonMatchingValues - */ + #[DataProvider('providerNonMatchingValues')] public function testCompare_NonMatchingValues_ReturnsFalse(string $leftData, string $rightData): void { $comparator = new GreaterValueComparator(new Collator('UTF-8')); $nodeValueFactory = NodeValueFactory::create(); $actualValue = $comparator->compare( $nodeValueFactory->createValue($leftData), - $nodeValueFactory->createValue($rightData) + $nodeValueFactory->createValue($rightData), ); self::assertFalse($actualValue); } - public function providerNonMatchingValues(): array + /** + * @return iterable + */ + public static function providerNonMatchingValues(): iterable { return [ 'Same string' => ['"a"', '"b"'], diff --git a/tests/Event/AfterArrayEventTest.php b/tests/Event/AfterArrayEventTest.php index 987d23e..6e0e31e 100644 --- a/tests/Event/AfterArrayEventTest.php +++ b/tests/Event/AfterArrayEventTest.php @@ -4,20 +4,41 @@ namespace Remorhaz\JSON\Data\Test\Event; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Event\AfterArrayEvent; use Remorhaz\JSON\Data\Path\Path; -/** - * @covers \Remorhaz\JSON\Data\Event\AfterArrayEvent - */ +#[CoversClass(AfterArrayEvent::class)] class AfterArrayEventTest extends TestCase { - public function testGetPath_ConstructedWithPath_ReturnsSameInstance(): void { $path = new Path(); $event = new AfterArrayEvent($path); self::assertSame($path, $event->getPath()); } + + public function testWith_GivenNoPath_ResultHasOldPath(): void + { + $path = new Path(); + $event = new AfterArrayEvent($path); + $clone = $event->with(); + self::assertSame($path, $clone->getPath()); + } + + public function testWith_GivenNewPath_ResultHasNewPath(): void + { + $oldPath = new Path(); + $event = new AfterArrayEvent($oldPath); + $newPath = new Path(); + $clone = $event->with(path: $newPath); + self::assertSame($newPath, $clone->getPath()); + } + + public function testWith_Called_ResultIsNewInstance(): void + { + $event = new AfterArrayEvent(new Path()); + self::assertNotSame($event, $event->with()); + } } diff --git a/tests/Event/AfterElementEventTest.php b/tests/Event/AfterElementEventTest.php index 49d9a29..37ddbf1 100644 --- a/tests/Event/AfterElementEventTest.php +++ b/tests/Event/AfterElementEventTest.php @@ -4,16 +4,14 @@ namespace Remorhaz\JSON\Data\Test\Event; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Event\AfterElementEvent; use Remorhaz\JSON\Data\Path\Path; -/** - * @covers \Remorhaz\JSON\Data\Event\AfterElementEvent - */ +#[CoversClass(AfterElementEvent::class)] class AfterElementEventTest extends TestCase { - public function testGetIndex_ConstructedWithIndex_ReturnsSameIndex(): void { $event = new AfterElementEvent(1, new Path()); @@ -26,4 +24,41 @@ public function testGetPath_ConstructedWithPath_ReturnsSameInstance(): void $event = new AfterElementEvent(1, $path); self::assertSame($path, $event->getPath()); } + + public function testWith_GivenNoPath_ResultHasOldPath(): void + { + $path = new Path(); + $event = new AfterElementEvent(1, $path); + $clone = $event->with(); + self::assertSame($path, $clone->getPath()); + } + + public function testWith_GivenNewPath_ResultHasNewPath(): void + { + $oldPath = new Path(); + $event = new AfterElementEvent(1, $oldPath); + $newPath = new Path(); + $clone = $event->with(path: $newPath); + self::assertSame($newPath, $clone->getPath()); + } + + public function testWith_Called_ResultIsNewInstance(): void + { + $event = new AfterElementEvent(1, new Path()); + self::assertNotSame($event, $event->with()); + } + + public function testWith_GivenNoIndex_ResultHasOldIndex(): void + { + $event = new AfterElementEvent(1, new Path()); + $clone = $event->with(); + self::assertSame(1, $clone->getIndex()); + } + + public function testWith_GivenNewIndex_ResultHasNewIndex(): void + { + $event = new AfterElementEvent(1, new Path()); + $clone = $event->with(index: 2); + self::assertSame(2, $clone->getIndex()); + } } diff --git a/tests/Event/AfterObjectEventTest.php b/tests/Event/AfterObjectEventTest.php index 08e25b2..a02f878 100644 --- a/tests/Event/AfterObjectEventTest.php +++ b/tests/Event/AfterObjectEventTest.php @@ -4,20 +4,41 @@ namespace Remorhaz\JSON\Data\Test\Event; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Event\AfterObjectEvent; use Remorhaz\JSON\Data\Path\Path; -/** - * @covers \Remorhaz\JSON\Data\Event\AfterObjectEvent - */ +#[CoversClass(AfterObjectEvent::class)] class AfterObjectEventTest extends TestCase { - public function testGetPath_ConstructedWithPath_ReturnsSameInstance(): void { $path = new Path(); $event = new AfterObjectEvent($path); self::assertSame($path, $event->getPath()); } + + public function testWith_GivenNoPath_ResultHasOldPath(): void + { + $path = new Path(); + $event = new AfterObjectEvent($path); + $clone = $event->with(); + self::assertSame($path, $clone->getPath()); + } + + public function testWith_GivenNewPath_ResultHasNewPath(): void + { + $oldPath = new Path(); + $event = new AfterObjectEvent($oldPath); + $newPath = new Path(); + $clone = $event->with(path: $newPath); + self::assertSame($newPath, $clone->getPath()); + } + + public function testWith_Called_ResultIsNewInstance(): void + { + $event = new AfterObjectEvent(new Path()); + self::assertNotSame($event, $event->with()); + } } diff --git a/tests/Event/AfterPropertyEventTest.php b/tests/Event/AfterPropertyEventTest.php index 102f8f7..f404fc6 100644 --- a/tests/Event/AfterPropertyEventTest.php +++ b/tests/Event/AfterPropertyEventTest.php @@ -4,16 +4,14 @@ namespace Remorhaz\JSON\Data\Test\Event; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Event\AfterPropertyEvent; use Remorhaz\JSON\Data\Path\Path; -/** - * @covers \Remorhaz\JSON\Data\Event\AfterPropertyEvent - */ +#[CoversClass(AfterPropertyEvent::class)] class AfterPropertyEventTest extends TestCase { - public function testGetName_ConstructedWithName_ReturnsSameIndex(): void { $event = new AfterPropertyEvent('a', new Path()); @@ -26,4 +24,41 @@ public function testGetPath_ConstructedWithPath_ReturnsSameInstance(): void $event = new AfterPropertyEvent('a', $path); self::assertSame($path, $event->getPath()); } + + public function testWith_GivenNoPath_ResultHasOldPath(): void + { + $path = new Path(); + $event = new AfterPropertyEvent('a', $path); + $clone = $event->with(); + self::assertSame($path, $clone->getPath()); + } + + public function testWith_GivenNewPath_ResultHasNewPath(): void + { + $oldPath = new Path(); + $event = new AfterPropertyEvent('a', $oldPath); + $newPath = new Path(); + $clone = $event->with(path: $newPath); + self::assertSame($newPath, $clone->getPath()); + } + + public function testWith_Called_ResultIsNewInstance(): void + { + $event = new AfterPropertyEvent('a', new Path()); + self::assertNotSame($event, $event->with()); + } + + public function testWith_GivenNoName_ResultHasOldName(): void + { + $event = new AfterPropertyEvent('a', new Path()); + $clone = $event->with(); + self::assertSame('a', $clone->getName()); + } + + public function testWith_GivenNewName_ResultHasNewName(): void + { + $event = new AfterPropertyEvent('a', new Path()); + $clone = $event->with(name: 'b'); + self::assertSame('b', $clone->getName()); + } } diff --git a/tests/Event/BeforeArrayEventTest.php b/tests/Event/BeforeArrayEventTest.php index 32cce57..f20ed9d 100644 --- a/tests/Event/BeforeArrayEventTest.php +++ b/tests/Event/BeforeArrayEventTest.php @@ -4,20 +4,42 @@ namespace Remorhaz\JSON\Data\Test\Event; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Event\BeforeArrayEvent; use Remorhaz\JSON\Data\Path\Path; -/** - * @covers \Remorhaz\JSON\Data\Event\BeforeArrayEvent - */ +#[CoversClass(BeforeArrayEvent::class)] class BeforeArrayEventTest extends TestCase { - public function testGetPath_ConstructedWithPath_ReturnsSameInstance(): void { $path = new Path(); $event = new BeforeArrayEvent($path); self::assertSame($path, $event->getPath()); } + + + public function testWith_GivenNoPath_ResultHasOldPath(): void + { + $path = new Path(); + $event = new BeforeArrayEvent($path); + $clone = $event->with(); + self::assertSame($path, $clone->getPath()); + } + + public function testWith_GivenNewPath_ResultHasNewPath(): void + { + $oldPath = new Path(); + $event = new BeforeArrayEvent($oldPath); + $newPath = new Path(); + $clone = $event->with(path: $newPath); + self::assertSame($newPath, $clone->getPath()); + } + + public function testWith_Called_ResultIsNewInstance(): void + { + $event = new BeforeArrayEvent(new Path()); + self::assertNotSame($event, $event->with()); + } } diff --git a/tests/Event/BeforeElementEventTest.php b/tests/Event/BeforeElementEventTest.php index 7ed6b55..cfd90c3 100644 --- a/tests/Event/BeforeElementEventTest.php +++ b/tests/Event/BeforeElementEventTest.php @@ -4,16 +4,14 @@ namespace Remorhaz\JSON\Data\Test\Event; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Event\BeforeElementEvent; use Remorhaz\JSON\Data\Path\Path; -/** - * @covers \Remorhaz\JSON\Data\Event\BeforeElementEvent - */ +#[CoversClass(BeforeElementEvent::class)] class BeforeElementEventTest extends TestCase { - public function testGetIndex_ConstructedWithIndex_ReturnsSameIndex(): void { $event = new BeforeElementEvent(1, new Path()); @@ -26,4 +24,41 @@ public function testGetPath_ConstructedWithPath_ReturnsSameInstance(): void $event = new BeforeElementEvent(1, $path); self::assertSame($path, $event->getPath()); } + + public function testWith_GivenNoPath_ResultHasOldPath(): void + { + $path = new Path(); + $event = new BeforeElementEvent(1, $path); + $clone = $event->with(); + self::assertSame($path, $clone->getPath()); + } + + public function testWith_GivenNewPath_ResultHasNewPath(): void + { + $oldPath = new Path(); + $event = new BeforeElementEvent(1, $oldPath); + $newPath = new Path(); + $clone = $event->with(path: $newPath); + self::assertSame($newPath, $clone->getPath()); + } + + public function testWith_Called_ResultIsNewInstance(): void + { + $event = new BeforeElementEvent(1, new Path()); + self::assertNotSame($event, $event->with()); + } + + public function testWith_GivenNoIndex_ResultHasOldIndex(): void + { + $event = new BeforeElementEvent(1, new Path()); + $clone = $event->with(); + self::assertSame(1, $clone->getIndex()); + } + + public function testWith_GivenNewIndex_ResultHasNewIndex(): void + { + $event = new BeforeElementEvent(1, new Path()); + $clone = $event->with(index: 2); + self::assertSame(2, $clone->getIndex()); + } } diff --git a/tests/Event/BeforeObjectEventTest.php b/tests/Event/BeforeObjectEventTest.php index 451f3f9..239c47e 100644 --- a/tests/Event/BeforeObjectEventTest.php +++ b/tests/Event/BeforeObjectEventTest.php @@ -4,20 +4,41 @@ namespace Remorhaz\JSON\Data\Test\Event; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Event\BeforeObjectEvent; use Remorhaz\JSON\Data\Path\Path; -/** - * @covers \Remorhaz\JSON\Data\Event\BeforeObjectEvent - */ +#[CoversClass(BeforeObjectEvent::class)] class BeforeObjectEventTest extends TestCase { - public function testGetPath_ConstructedWithPath_ReturnsSameInstance(): void { $path = new Path(); $event = new BeforeObjectEvent($path); self::assertSame($path, $event->getPath()); } + + public function testWith_GivenNoPath_ResultHasOldPath(): void + { + $path = new Path(); + $event = new BeforeObjectEvent($path); + $clone = $event->with(); + self::assertSame($path, $clone->getPath()); + } + + public function testWith_GivenNewPath_ResultHasNewPath(): void + { + $oldPath = new Path(); + $event = new BeforeObjectEvent($oldPath); + $newPath = new Path(); + $clone = $event->with(path: $newPath); + self::assertSame($newPath, $clone->getPath()); + } + + public function testWith_Called_ResultIsNewInstance(): void + { + $event = new BeforeObjectEvent(new Path()); + self::assertNotSame($event, $event->with()); + } } diff --git a/tests/Event/BeforePropertyEventTest.php b/tests/Event/BeforePropertyEventTest.php index 48ef459..e6c011e 100644 --- a/tests/Event/BeforePropertyEventTest.php +++ b/tests/Event/BeforePropertyEventTest.php @@ -4,16 +4,14 @@ namespace Remorhaz\JSON\Data\Test\Event; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Event\BeforePropertyEvent; use Remorhaz\JSON\Data\Path\Path; -/** - * @covers \Remorhaz\JSON\Data\Event\BeforePropertyEvent - */ +#[CoversClass(BeforePropertyEvent::class)] class BeforePropertyEventTest extends TestCase { - public function testGetName_ConstructedWithName_ReturnsSameIndex(): void { $event = new BeforePropertyEvent('a', new Path()); @@ -26,4 +24,41 @@ public function testGetPath_ConstructedWithPath_ReturnsSameInstance(): void $event = new BeforePropertyEvent('a', $path); self::assertSame($path, $event->getPath()); } + + public function testWith_GivenNoPath_ResultHasOldPath(): void + { + $path = new Path(); + $event = new BeforePropertyEvent('a', $path); + $clone = $event->with(); + self::assertSame($path, $clone->getPath()); + } + + public function testWith_GivenNewPath_ResultHasNewPath(): void + { + $oldPath = new Path(); + $event = new BeforePropertyEvent('a', $oldPath); + $newPath = new Path(); + $clone = $event->with(path: $newPath); + self::assertSame($newPath, $clone->getPath()); + } + + public function testWith_Called_ResultIsNewInstance(): void + { + $event = new BeforePropertyEvent('a', new Path()); + self::assertNotSame($event, $event->with()); + } + + public function testWith_GivenNoName_ResultHasOldName(): void + { + $event = new BeforePropertyEvent('a', new Path()); + $clone = $event->with(); + self::assertSame('a', $clone->getName()); + } + + public function testWith_GivenNewName_ResultHasNewName(): void + { + $event = new BeforePropertyEvent('a', new Path()); + $clone = $event->with(name: 'b'); + self::assertSame('b', $clone->getName()); + } } diff --git a/tests/Event/Exception/InvalidScalarDataExceptionTest.php b/tests/Event/Exception/InvalidScalarDataExceptionTest.php index ff50a5d..3922fc4 100644 --- a/tests/Event/Exception/InvalidScalarDataExceptionTest.php +++ b/tests/Event/Exception/InvalidScalarDataExceptionTest.php @@ -5,15 +5,13 @@ namespace Remorhaz\JSON\Data\Test\Event\Exception; use Exception; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Event\Exception\InvalidScalarDataException; -/** - * @covers \Remorhaz\JSON\Data\Event\Exception\InvalidScalarDataException - */ +#[CoversClass(InvalidScalarDataException::class)] class InvalidScalarDataExceptionTest extends TestCase { - public function testGetMessage_Constructed_ReturnsMatchingValue(): void { $exception = new InvalidScalarDataException(null); @@ -26,15 +24,9 @@ public function testGetData_ConstructedWithData_ReturnsSameValue(): void self::assertSame('a', $exception->getData()); } - public function testGetCode_Always_ReturnsZero(): void - { - $exception = new InvalidScalarDataException(null); - self::assertSame(0, $exception->getCode()); - } - public function testGetPrevious_ConstructedWithoutPrevious_ReturnsNull(): void { - $exception = new InvalidScalarDataException(null); + $exception = new InvalidScalarDataException(1); self::assertNull($exception->getPrevious()); } diff --git a/tests/Event/ScalarEventTest.php b/tests/Event/ScalarEventTest.php index 5c32bed..0d48ff1 100644 --- a/tests/Event/ScalarEventTest.php +++ b/tests/Event/ScalarEventTest.php @@ -4,35 +4,33 @@ namespace Remorhaz\JSON\Data\Test\Event; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Event\Exception\InvalidScalarDataException; use Remorhaz\JSON\Data\Event\ScalarEvent; use Remorhaz\JSON\Data\Path\Path; -/** - * @covers \Remorhaz\JSON\Data\Event\ScalarEvent - */ +#[CoversClass(ScalarEvent::class)] class ScalarEventTest extends TestCase { - public function testConstruct_NonScalarData_ThrowsException(): void { $this->expectException(InvalidScalarDataException::class); new ScalarEvent([], new Path()); } - /** - * @param $data - * @param $expectedValue - * @dataProvider providerGetData - */ - public function testGetData_ConstructedWithScalarData_ReturnsSameValue($data, $expectedValue): void + #[DataProvider('providerGetData')] + public function testGetData_ConstructedWithScalarData_ReturnsSameValue(mixed $data, mixed $expectedValue): void { $event = new ScalarEvent($data, new Path()); self::assertSame($expectedValue, $event->getData()); } - public function providerGetData(): array + /** + * @return iterable + */ + public static function providerGetData(): iterable { return [ 'Null' => [null, null], @@ -49,4 +47,55 @@ public function testGetPath_ConstructedWithPath_ReturnsSameInstance(): void $event = new ScalarEvent(null, $path); self::assertSame($path, $event->getPath()); } + + public function testWith_GivenNoPath_ResultHasOldPath(): void + { + $path = new Path(); + $event = new ScalarEvent(null, $path); + $clone = $event->with(); + self::assertSame($path, $clone->getPath()); + } + + public function testWith_GivenNewPath_ResultHasNewPath(): void + { + $oldPath = new Path(); + $event = new ScalarEvent(null, $oldPath); + $newPath = new Path(); + $clone = $event->with(path: $newPath); + self::assertSame($newPath, $clone->getPath()); + } + + public function testWith_Called_ResultIsNewInstance(): void + { + $event = new ScalarEvent(null, new Path()); + self::assertNotSame($event, $event->with()); + } + + public function testWith_GivenNoData_ResultHasOldData(): void + { + $event = new ScalarEvent('a', new Path()); + $clone = $event->with(); + self::assertSame('a', $clone->getData()); + } + + public function testWith_GivenNewNonNullData_ResultHasNewData(): void + { + $event = new ScalarEvent('a', new Path()); + $clone = $event->with(data: 'b'); + self::assertSame('b', $clone->getData()); + } + + public function testWith_GivenNewNullDataWithoutForceFlag_ResultHasOldData(): void + { + $event = new ScalarEvent('a', new Path()); + $clone = $event->with(data: null); + self::assertSame('a', $clone->getData()); + } + + public function testWith_GivenNewNullDataWithForceFlag_ResultHasNewData(): void + { + $event = new ScalarEvent('a', new Path()); + $clone = $event->with(data: null, forceData: true); + self::assertSame(null, $clone->getData()); + } } diff --git a/tests/Export/Exception/EncodingFailedExceptionTest.php b/tests/Export/Exception/EncodingFailedExceptionTest.php index f4c74bf..1244da1 100644 --- a/tests/Export/Exception/EncodingFailedExceptionTest.php +++ b/tests/Export/Exception/EncodingFailedExceptionTest.php @@ -5,15 +5,13 @@ namespace Remorhaz\JSON\Data\Test\Export\Exception; use Exception; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Export\Exception\EncodingFailedException; -/** - * @covers \Remorhaz\JSON\Data\Export\Exception\EncodingFailedException - */ +#[CoversClass(EncodingFailedException::class)] class EncodingFailedExceptionTest extends TestCase { - public function testGetMessage_Constructed_ReturnsMatchingValue(): void { $exception = new EncodingFailedException('a'); @@ -26,12 +24,6 @@ public function testGetData_ConstructedWithData_ReturnsSameValue(): void self::assertSame('a', $exception->getData()); } - public function testGetCode_Always_ReturnsZero(): void - { - $exception = new EncodingFailedException('a'); - self::assertSame(0, $exception->getCode()); - } - public function testGetPrevious_ConstructedWithoutPrevious_ReturnsNull(): void { $exception = new EncodingFailedException('a'); diff --git a/tests/Export/Exception/NoValueToExportExceptionTest.php b/tests/Export/Exception/NoValueToExportExceptionTest.php index 4f595b5..fe1aa0d 100644 --- a/tests/Export/Exception/NoValueToExportExceptionTest.php +++ b/tests/Export/Exception/NoValueToExportExceptionTest.php @@ -5,15 +5,13 @@ namespace Remorhaz\JSON\Data\Test\Export\Exception; use Exception; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Export\Exception\NoValueToExportException; -/** - * @covers \Remorhaz\JSON\Data\Export\Exception\NoValueToExportException - */ +#[CoversClass(NoValueToExportException::class)] class NoValueToExportExceptionTest extends TestCase { - public function testGetMessage_Constructed_ReturnsMatchingValue(): void { $exception = new NoValueToExportException(); diff --git a/tests/Export/Exception/UnexpectedValueExceptionTest.php b/tests/Export/Exception/UnexpectedValueExceptionTest.php index da3ecdd..51ecbce 100644 --- a/tests/Export/Exception/UnexpectedValueExceptionTest.php +++ b/tests/Export/Exception/UnexpectedValueExceptionTest.php @@ -5,38 +5,30 @@ namespace Remorhaz\JSON\Data\Test\Export\Exception; use Exception; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Export\Exception\UnexpectedValueException; use Remorhaz\JSON\Data\Value\ValueInterface; -/** - * @covers \Remorhaz\JSON\Data\Export\Exception\UnexpectedValueException - */ +#[CoversClass(UnexpectedValueException::class)] class UnexpectedValueExceptionTest extends TestCase { - public function testGetMessage_Constructed_ReturnsMatchingValue(): void { - $exception = new UnexpectedValueException($this->createMock(ValueInterface::class)); + $exception = new UnexpectedValueException(self::createStub(ValueInterface::class)); self::assertSame('Unexpected value', $exception->getMessage()); } public function testGetValue_ConstructedWithData_ReturnsSameValue(): void { - $value = $this->createMock(ValueInterface::class); + $value = self::createStub(ValueInterface::class); $exception = new UnexpectedValueException($value); self::assertSame($value, $exception->getValue()); } - public function testGetCode_Always_ReturnsZero(): void - { - $exception = new UnexpectedValueException($this->createMock(ValueInterface::class)); - self::assertSame(0, $exception->getCode()); - } - public function testGetPrevious_ConstructedWithoutPrevious_ReturnsNull(): void { - $exception = new UnexpectedValueException($this->createMock(ValueInterface::class)); + $exception = new UnexpectedValueException(self::createStub(ValueInterface::class)); self::assertNull($exception->getPrevious()); } @@ -44,7 +36,7 @@ public function testGetPrevious_ConstructedWithPrevious_ReturnsSameInstance(): v { $previous = new Exception(); $exception = new UnexpectedValueException( - $this->createMock(ValueInterface::class), + self::createStub(ValueInterface::class), $previous, ); self::assertSame($previous, $exception->getPrevious()); diff --git a/tests/Export/Exception/UnknownEventExceptionTest.php b/tests/Export/Exception/UnknownEventExceptionTest.php index cb29689..24cd99b 100644 --- a/tests/Export/Exception/UnknownEventExceptionTest.php +++ b/tests/Export/Exception/UnknownEventExceptionTest.php @@ -5,38 +5,30 @@ namespace Remorhaz\JSON\Data\Test\Export\Exception; use Exception; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Event\EventInterface; use Remorhaz\JSON\Data\Export\Exception\UnknownEventException; -/** - * @covers \Remorhaz\JSON\Data\Export\Exception\UnknownEventException - */ +#[CoversClass(UnknownEventException::class)] class UnknownEventExceptionTest extends TestCase { - public function testGetMessage_Constructed_ReturnsMatchingValue(): void { - $exception = new UnknownEventException($this->createMock(EventInterface::class)); + $exception = new UnknownEventException(self::createStub(EventInterface::class)); self::assertSame('Unknown event', $exception->getMessage()); } public function testGetEvent_ConstructedWithData_ReturnsSameValue(): void { - $event = $this->createMock(EventInterface::class); + $event = self::createStub(EventInterface::class); $exception = new UnknownEventException($event); self::assertSame($event, $exception->getEvent()); } - public function testGetCode_Always_ReturnsZero(): void - { - $exception = new UnknownEventException($this->createMock(EventInterface::class)); - self::assertSame(0, $exception->getCode()); - } - public function testGetPrevious_ConstructedWithoutPrevious_ReturnsNull(): void { - $exception = new UnknownEventException($this->createMock(EventInterface::class)); + $exception = new UnknownEventException(self::createStub(EventInterface::class)); self::assertNull($exception->getPrevious()); } @@ -44,7 +36,7 @@ public function testGetPrevious_ConstructedWithPrevious_ReturnsSameInstance(): v { $previous = new Exception(); $exception = new UnknownEventException( - $this->createMock(EventInterface::class), + self::createStub(EventInterface::class), $previous, ); self::assertSame($previous, $exception->getPrevious()); diff --git a/tests/Path/Exception/ParentNotFoundExceptionTest.php b/tests/Path/Exception/ParentNotFoundExceptionTest.php index 2fac766..481558f 100644 --- a/tests/Path/Exception/ParentNotFoundExceptionTest.php +++ b/tests/Path/Exception/ParentNotFoundExceptionTest.php @@ -5,16 +5,15 @@ namespace Remorhaz\JSON\Data\Test\Path\Exception; use Exception; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Path\Exception\ParentNotFoundException; use Remorhaz\JSON\Data\Path\Path; -/** - * @covers \Remorhaz\JSON\Data\Path\Exception\ParentNotFoundException - */ +#[CoversClass(ParentNotFoundException::class)] class ParentNotFoundExceptionTest extends TestCase { - public function testGetPath_ConstructedWithPath_ReturnsSameInstance(): void { $path = new Path(); @@ -23,10 +22,10 @@ public function testGetPath_ConstructedWithPath_ReturnsSameInstance(): void } /** - * @param array $pathElements - * @param string $expectedValue - * @dataProvider providerGetMessage + * @param list $pathElements + * @param string $expectedValue */ + #[DataProvider('providerGetMessage')] public function testGetMessage(array $pathElements, string $expectedValue): void { $path = new Path(...$pathElements); @@ -34,7 +33,10 @@ public function testGetMessage(array $pathElements, string $expectedValue): void self::assertSame($expectedValue, $exception->getMessage()); } - public function providerGetMessage(): array + /** + * @return iterable, string}> + */ + public static function providerGetMessage(): iterable { return [ 'Empty path' => [[], "Parent not found in path /"], @@ -42,12 +44,6 @@ public function providerGetMessage(): array ]; } - public function testGetCode_Always_ReturnsZero(): void - { - $exception = new ParentNotFoundException(new Path()); - self::assertSame(0, $exception->getCode()); - } - public function testGetPrevious_ConstructedWithoutPrevious_ReturnsNull(): void { $exception = new ParentNotFoundException(new Path()); diff --git a/tests/Path/PathTest.php b/tests/Path/PathTest.php index 6a59d0e..1bbd1e9 100644 --- a/tests/Path/PathTest.php +++ b/tests/Path/PathTest.php @@ -4,16 +4,15 @@ namespace Remorhaz\JSON\Data\Test\Path; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Path\Exception\ParentNotFoundException; use Remorhaz\JSON\Data\Path\Path; -/** - * @covers \Remorhaz\JSON\Data\Path\Path - */ +#[CoversClass(Path::class)] class PathTest extends TestCase { - public function testGetElements_ConstructedWithGivenElements_ReturnsSameValues(): void { $path = new Path('a', 1); @@ -45,10 +44,10 @@ public function testCopyWithProperty_Constructed_ResultContainsMatchingElements( } /** - * @param array $firstElements - * @param array $secondElements - * @dataProvider providerEquals + * @param list $firstElements + * @param list $secondElements */ + #[DataProvider('providerEquals')] public function testEquals_EqualPath_ReturnsTrue(array $firstElements, array $secondElements): void { $firstPath = new Path(...$firstElements); @@ -57,7 +56,10 @@ public function testEquals_EqualPath_ReturnsTrue(array $firstElements, array $se self::assertTrue($firstPath->equals($secondPath)); } - public function providerEquals(): array + /** + * @return iterable, list}> + */ + public static function providerEquals(): iterable { return [ 'Empty paths' => [[], []], @@ -66,10 +68,10 @@ public function providerEquals(): array } /** - * @param array $firstElements - * @param array $secondElements - * @dataProvider providerNotEquals + * @param list $firstElements + * @param list $secondElements */ + #[DataProvider('providerNotEquals')] public function testEquals_NotEqualPath_ReturnsFalse(array $firstElements, array $secondElements): void { $firstPath = new Path(...$firstElements); @@ -78,7 +80,10 @@ public function testEquals_NotEqualPath_ReturnsFalse(array $firstElements, array self::assertFalse($firstPath->equals($secondPath)); } - public function providerNotEquals(): array + /** + * @return iterable, list}> + */ + public static function providerNotEquals(): iterable { return [ 'Nested paths' => [['a', 1], ['a']], @@ -87,10 +92,10 @@ public function providerNotEquals(): array } /** - * @param array $pathElements - * @param array $containedPathElements - * @dataProvider providerContains + * @param list $pathElements + * @param list $containedPathElements */ + #[DataProvider('providerContains')] public function testContains_ContainedPath_ReturnsTrue(array $pathElements, array $containedPathElements): void { $path = new Path(...$pathElements); @@ -98,7 +103,10 @@ public function testContains_ContainedPath_ReturnsTrue(array $pathElements, arra self::assertTrue($path->contains($containedPath)); } - public function providerContains(): array + /** + * @return iterable, list}> + */ + public static function providerContains(): iterable { return [ 'Same path' => [['a', 1], ['a', 1]], @@ -114,10 +122,10 @@ public function testCopyParent_NonEmptyPath_ReturnsNewInstance(): void } /** - * @dataProvider providerCopyExistingParent - * @param array $pathElements - * @param array $expectedElements + * @param list $pathElements + * @param list $expectedElements */ + #[DataProvider('providerCopyExistingParent')] public function testCopyParent_NonEmptyPath_ReturnsMatchingPath(array $pathElements, array $expectedElements): void { $path = new Path(...$pathElements); @@ -125,7 +133,10 @@ public function testCopyParent_NonEmptyPath_ReturnsMatchingPath(array $pathEleme self::assertSame($expectedElements, $pathCopy->getElements()); } - public function providerCopyExistingParent(): array + /** + * @return iterable, list}> + */ + public static function providerCopyExistingParent(): iterable { return [ 'Single element path' => [['a'], []], diff --git a/tests/Value/DecodedJson/Exception/InvalidElementKeyExceptionTest.php b/tests/Value/DecodedJson/Exception/InvalidElementKeyExceptionTest.php index 0ffd3cf..8221dbb 100644 --- a/tests/Value/DecodedJson/Exception/InvalidElementKeyExceptionTest.php +++ b/tests/Value/DecodedJson/Exception/InvalidElementKeyExceptionTest.php @@ -5,29 +5,34 @@ namespace Remorhaz\JSON\Data\Test\Value\DecodedJson; use Exception; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Value\DecodedJson\Exception\InvalidElementKeyException; use Remorhaz\JSON\Data\Path\Path; -/** - * @covers \Remorhaz\JSON\Data\Value\DecodedJson\Exception\InvalidElementKeyException - */ +#[CoversClass(InvalidElementKeyException::class)] class InvalidElementKeyExceptionTest extends TestCase { - /** - * @param $key - * @param array $pathElements - * @param $expectedValue - * @dataProvider providerKeyMessage + * @param mixed $key + * @param list $pathElements + * @param string $expectedValue */ - public function testGetMessage_Constructed_ReturnsMatchingValue($key, array $pathElements, $expectedValue): void - { + #[DataProvider('providerKeyMessage')] + public function testGetMessage_Constructed_ReturnsMatchingValue( + mixed $key, + array $pathElements, + string $expectedValue, + ): void { $exception = new InvalidElementKeyException($key, new Path(...$pathElements)); self::assertSame($expectedValue, $exception->getMessage()); } - public function providerKeyMessage(): array + /** + * @return iterable, string}> + */ + public static function providerKeyMessage(): iterable { /** @noinspection HtmlUnknownTag */ return [ @@ -44,18 +49,17 @@ public function testGetPath_ConstructedWithGivenPath_ReturnsSameInstance(): void self::assertSame($path, $exception->getPath()); } - /** - * @param $key - * @param $expectedValue - * @dataProvider providerKey - */ - public function testGetKey_ConstructedWithGivenKey_ReturnsSameValue($key, $expectedValue): void + #[DataProvider('providerKey')] + public function testGetKey_ConstructedWithGivenKey_ReturnsSameValue(mixed $key, mixed $expectedValue): void { $exception = new InvalidElementKeyException($key, new Path()); self::assertSame($expectedValue, $exception->getKey()); } - public function providerKey(): array + /** + * @return iterable + */ + public static function providerKey(): iterable { return [ 'Integer' => [1, 1], @@ -63,12 +67,6 @@ public function providerKey(): array ]; } - public function testGetCode_Always_ReturnZero(): void - { - $exception = new InvalidElementKeyException(0, new Path()); - self::assertSame(0, $exception->getCode()); - } - public function testGetPrevious_ConstructedWithoutPrevious_ReturnsNull(): void { $exception = new InvalidElementKeyException(0, new Path()); diff --git a/tests/Value/DecodedJson/Exception/InvalidNodeDataExceptionTest.php b/tests/Value/DecodedJson/Exception/InvalidNodeDataExceptionTest.php index a64e475..36304f6 100644 --- a/tests/Value/DecodedJson/Exception/InvalidNodeDataExceptionTest.php +++ b/tests/Value/DecodedJson/Exception/InvalidNodeDataExceptionTest.php @@ -5,28 +5,30 @@ namespace Remorhaz\JSON\Data\Test\Value\DecodedJson\Exception; use Exception; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Value\DecodedJson\Exception\InvalidNodeDataException; use Remorhaz\JSON\Data\Path\Path; -/** - * @covers \Remorhaz\JSON\Data\Value\DecodedJson\Exception\InvalidNodeDataException - */ +#[CoversClass(InvalidNodeDataException::class)] class InvalidNodeDataExceptionTest extends TestCase { - /** - * @param array $elements - * @param $expectedValue - * @dataProvider providerGetMessage + * @param list $elements + * @param string $expectedValue */ - public function testGetMessage_Constructed_ReturnsMatchingValue(array $elements, $expectedValue): void + #[DataProvider('providerGetMessage')] + public function testGetMessage_Constructed_ReturnsMatchingValue(array $elements, string $expectedValue): void { $exception = new InvalidNodeDataException(null, new Path(...$elements)); self::assertSame($expectedValue, $exception->getMessage()); } - public function providerGetMessage(): array + /** + * @return iterable, string}> + */ + public static function providerGetMessage(): iterable { return [ 'Empty path' => [[], 'Invalid data in decoded JSON at /'], @@ -48,12 +50,6 @@ public function testGetPath_ConstructedWithGivenPath_ReturnsSameInstance(): void self::assertSame($path, $exception->getPath()); } - public function testGetCode_Always_ReturnZero(): void - { - $exception = new InvalidNodeDataException(0, new Path()); - self::assertSame(0, $exception->getCode()); - } - public function testGetPrevious_ConstructedWithoutPrevious_ReturnsNull(): void { $exception = new InvalidNodeDataException(0, new Path()); diff --git a/tests/Value/DecodedJson/NodeArrayValueTest.php b/tests/Value/DecodedJson/NodeArrayValueTest.php index 3c1e1ba..a3a35ef 100644 --- a/tests/Value/DecodedJson/NodeArrayValueTest.php +++ b/tests/Value/DecodedJson/NodeArrayValueTest.php @@ -4,7 +4,8 @@ namespace Remorhaz\JSON\Data\Test\Value\DecodedJson; -use PHPUnit\Framework\Constraint\Callback; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use Remorhaz\JSON\Data\Path\PathInterface; use Remorhaz\JSON\Data\Value\DecodedJson\NodeValueFactoryInterface; use Remorhaz\JSON\Data\Value\NodeValueInterface; @@ -16,16 +17,10 @@ use function iterator_to_array; -/** - * @covers \Remorhaz\JSON\Data\Value\DecodedJson\NodeArrayValue - */ +#[CoversClass(NodeArrayValue::class)] class NodeArrayValueTest extends TestCase { - - /** - * @param array $data - * @dataProvider providerArrayWithInvalidIndex - */ + #[DataProvider('providerArrayWithInvalidIndex')] public function testCreateChildIterator_ArrayDataWithInvalidIndex_ThrowsException(array $data): void { $value = new NodeArrayValue($data, new Path(), NodeValueFactory::create()); @@ -34,7 +29,10 @@ public function testCreateChildIterator_ArrayDataWithInvalidIndex_ThrowsExceptio iterator_to_array($value->createChildIterator(), true); } - public function providerArrayWithInvalidIndex(): array + /** + * @return iterable + */ + public static function providerArrayWithInvalidIndex(): iterable { return [ 'Non-zero first index' => [[1 => 'a']], @@ -51,35 +49,35 @@ public function testCreateChildIterator_EmptyArrayData_ReturnsEmptyIterator(): v public function testCreateChildIterator_NotEmptyArrayData_CallsFactoryForEachElement(): void { - $nodeValueFactory = $this->createMock(NodeValueFactoryInterface::class); + $nodeValueFactory = self::createStub(NodeValueFactoryInterface::class); $value = new NodeArrayValue(['a', 1], new Path('b'), $nodeValueFactory); + $interceptedArguments = []; $nodeValueFactory - ->expects(self::exactly(2)) ->method('createValue') - ->withConsecutive( - [self::identicalTo('a'), $this->isArgEqualPath('b', 0)], - [self::identicalTo(1), $this->isArgEqualPath('b', 1)], + ->willReturnCallback( + function (mixed $data, ?PathInterface $path) use (&$interceptedArguments): NodeValueInterface { + /** @psalm-var array $interceptedArguments */ + $interceptedArguments[] = [$data, $path?->getElements() ?? []]; + + return self::createStub(NodeValueInterface::class); + } ); iterator_to_array($value->createChildIterator(), true); - } - - private function isArgEqualPath(...$elements): Callback - { - $callback = function (PathInterface $path) use ($elements): bool { - return $path->equals(new Path(...$elements)); - }; - - return self::callback($callback); + $expectedValue = [ + ['a', ['b', 0]], + [1, ['b', 1]], + ]; + self::assertSame($expectedValue, $interceptedArguments); } public function testCreateChildIterator_NodeFactoryReturnsValues_ReturnsSameValuesWithMatchingIndexes(): void { - $nodeValueFactory = $this->createMock(NodeValueFactoryInterface::class); + $nodeValueFactory = self::createStub(NodeValueFactoryInterface::class); $value = new NodeArrayValue(['a', 1], new Path('b'), $nodeValueFactory); - $firstNode = $this->createMock(NodeValueInterface::class); - $secondNode = $this->createMock(NodeValueInterface::class); + $firstNode = self::createStub(NodeValueInterface::class); + $secondNode = self::createStub(NodeValueInterface::class); $nodeValueFactory ->method('createValue') ->willReturnOnConsecutiveCalls($firstNode, $secondNode); diff --git a/tests/Value/DecodedJson/NodeObjectValueTest.php b/tests/Value/DecodedJson/NodeObjectValueTest.php index 55bae3c..829f0c9 100644 --- a/tests/Value/DecodedJson/NodeObjectValueTest.php +++ b/tests/Value/DecodedJson/NodeObjectValueTest.php @@ -4,7 +4,7 @@ namespace Remorhaz\JSON\Data\Test\Value\DecodedJson; -use PHPUnit\Framework\Constraint\Callback; +use PHPUnit\Framework\Attributes\CoversClass; use Remorhaz\JSON\Data\Path\PathInterface; use Remorhaz\JSON\Data\Value\DecodedJson\NodeObjectValue; use Remorhaz\JSON\Data\Value\DecodedJson\NodeValueFactoryInterface; @@ -15,12 +15,9 @@ use function iterator_to_array; -/** - * @covers \Remorhaz\JSON\Data\Value\DecodedJson\NodeObjectValue - */ +#[CoversClass(NodeObjectValue::class)] class NodeObjectValueTest extends TestCase { - public function testCreateChildIterator_EmptyObjectData_ReturnsEmptyIterator(): void { $value = new NodeObjectValue((object) [], new Path(), NodeValueFactory::create()); @@ -30,35 +27,35 @@ public function testCreateChildIterator_EmptyObjectData_ReturnsEmptyIterator(): public function testCreateChildIterator_NotEmptyObjectData_CallsFactoryForEachElement(): void { - $nodeValueFactory = $this->createMock(NodeValueFactoryInterface::class); + $nodeValueFactory = self::createStub(NodeValueFactoryInterface::class); $value = new NodeObjectValue((object) ['a' => 'b', 'c' => 1], new Path('d'), $nodeValueFactory); + $interceptedArgs = []; $nodeValueFactory - ->expects(self::exactly(2)) ->method('createValue') - ->withConsecutive( - [self::identicalTo('b'), $this->isArgEqualPath('d', 'a')], - [self::identicalTo(1), $this->isArgEqualPath('d', 'c')], + ->willReturnCallback( + function (mixed $data, ?PathInterface $path) use (&$interceptedArgs): NodeValueInterface { + /** @psalm-var array $interceptedArgs */ + $interceptedArgs[] = [$data, $path?->getElements() ?? []]; + + return self::createStub(NodeValueInterface::class); + }, ); iterator_to_array($value->createChildIterator(), true); - } - - private function isArgEqualPath(...$elements): Callback - { - $callback = function (PathInterface $path) use ($elements): bool { - return $path->equals(new Path(...$elements)); - }; - - return self::callback($callback); + $expectedValue = [ + ['b', ['d', 'a']], + [1, ['d', 'c']], + ]; + self::assertSame($expectedValue, $interceptedArgs); } public function testCreateChildIterator_NodeFactoryReturnsValues_ReturnsSameValuesWithMatchingIndexes(): void { - $nodeValueFactory = $this->createMock(NodeValueFactoryInterface::class); + $nodeValueFactory = self::createStub(NodeValueFactoryInterface::class); $value = new NodeObjectValue((object) ['a' => 'b', 'c' => 1], new Path('d'), $nodeValueFactory); - $firstNode = $this->createMock(NodeValueInterface::class); - $secondNode = $this->createMock(NodeValueInterface::class); + $firstNode = self::createStub(NodeValueInterface::class); + $secondNode = self::createStub(NodeValueInterface::class); $nodeValueFactory ->method('createValue') ->willReturnOnConsecutiveCalls($firstNode, $secondNode); diff --git a/tests/Value/DecodedJson/NodeScalarValueTest.php b/tests/Value/DecodedJson/NodeScalarValueTest.php index e0b7239..6e5d993 100644 --- a/tests/Value/DecodedJson/NodeScalarValueTest.php +++ b/tests/Value/DecodedJson/NodeScalarValueTest.php @@ -4,28 +4,27 @@ namespace Remorhaz\JSON\Data\Test\Value\DecodedJson; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Value\DecodedJson\NodeScalarValue; use Remorhaz\JSON\Data\Value\DecodedJson\Exception\InvalidNodeDataException; use Remorhaz\JSON\Data\Path\Path; -/** - * @covers \Remorhaz\JSON\Data\Value\DecodedJson\NodeScalarValue - */ +#[CoversClass(NodeScalarValue::class)] class NodeScalarValueTest extends TestCase { - - /** - * @param $data - * @dataProvider providerInvalidData - */ - public function testConstruct_InvalidData_ThrowsMatchingException($data): void + #[DataProvider('providerInvalidData')] + public function testConstruct_InvalidData_ThrowsMatchingException(mixed $data): void { $this->expectException(InvalidNodeDataException::class); new NodeScalarValue($data, new Path()); } - public function providerInvalidData(): array + /** + * @return iterable + */ + public static function providerInvalidData(): iterable { return [ 'Resource' => [STDERR], diff --git a/tests/Value/DecodedJson/NodeValueFactoryTest.php b/tests/Value/DecodedJson/NodeValueFactoryTest.php index 3f21594..8385f6d 100644 --- a/tests/Value/DecodedJson/NodeValueFactoryTest.php +++ b/tests/Value/DecodedJson/NodeValueFactoryTest.php @@ -4,6 +4,8 @@ namespace Remorhaz\JSON\Data\Test\Value\DecodedJson; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Value\DecodedJson\Exception\InvalidNodeDataException; use Remorhaz\JSON\Data\Value\DecodedJson\NodeArrayValue; @@ -15,23 +17,20 @@ use const STDOUT; -/** - * @covers \Remorhaz\JSON\Data\Value\DecodedJson\NodeValueFactory - */ +#[CoversClass(NodeValueFactory::class)] class NodeValueFactoryTest extends TestCase { - - /** - * @param mixed $data - * @dataProvider providerScalarDataOrNull - */ - public function testCreateValue_ScalarDataOrNull_ReturnsNodeScalarValue($data): void + #[DataProvider('providerScalarDataOrNull')] + public function testCreateValue_ScalarDataOrNull_ReturnsNodeScalarValue(mixed $data): void { $actualValue = NodeValueFactory::create()->createValue($data, new Path()); self::assertInstanceOf(NodeScalarValue::class, $actualValue); } - public function providerScalarDataOrNull(): array + /** + * @return iterable + */ + public static function providerScalarDataOrNull(): iterable { return [ 'Null' => [null, null], @@ -42,33 +41,23 @@ public function providerScalarDataOrNull(): array ]; } - /** - * @param mixed $data - * @dataProvider providerScalarDataOrNull - */ - public function testCreateValue_ScalarValueAndGivenPath_ResultHasSamePathInstance($data): void + #[DataProvider('providerScalarDataOrNull')] + public function testCreateValue_ScalarValueAndGivenPath_ResultHasSamePathInstance(mixed $data): void { $path = new Path(); $actualValue = NodeValueFactory::create()->createValue($data, $path); self::assertSame($path, $actualValue->getPath()); } - /** - * @param mixed $data - * @dataProvider providerScalarDataOrNull - */ - public function testCreateValue_ScalarValueAndNoPath_ResultHasEmptyPath($data): void + #[DataProvider('providerScalarDataOrNull')] + public function testCreateValue_ScalarValueAndNoPath_ResultHasEmptyPath(mixed $data): void { $actualValue = NodeValueFactory::create()->createValue($data); self::assertEmpty($actualValue->getPath()->getElements()); } - /** - * @param mixed $data - * @param $expectedValue - * @dataProvider providerScalarDataOrNull - */ - public function testCreateValue_ScalarValue_ResultHasMatchingData($data, $expectedValue): void + #[DataProvider('providerScalarDataOrNull')] + public function testCreateValue_ScalarValue_ResultHasMatchingData(mixed $data, mixed $expectedValue): void { $path = new Path(); /** @var ScalarValueInterface $actualValue */ @@ -120,11 +109,4 @@ public function testCreateValue_NonScalarValue_ThrowsException(): void $this->expectException(InvalidNodeDataException::class); $factory->createValue(STDOUT, new Path()); } - - public function testCreateValue_NonMatchingObject_ThrowsException(): void - { - $factory = NodeValueFactory::create(); - $this->expectException(InvalidNodeDataException::class); - $factory->createValue(new Path(), new Path()); - } } diff --git a/tests/Value/EncodedJson/Exception/JsonNotDecodedExceptionTest.php b/tests/Value/EncodedJson/Exception/JsonNotDecodedExceptionTest.php index b70331a..c339188 100644 --- a/tests/Value/EncodedJson/Exception/JsonNotDecodedExceptionTest.php +++ b/tests/Value/EncodedJson/Exception/JsonNotDecodedExceptionTest.php @@ -5,15 +5,13 @@ namespace Remorhaz\JSON\Data\Test\Value\EncodedJson\Exception; use Exception; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Value\EncodedJson\Exception\JsonNotDecodedException; -/** - * @covers \Remorhaz\JSON\Data\Value\EncodedJson\Exception\JsonNotDecodedException - */ +#[CoversClass(JsonNotDecodedException::class)] class JsonNotDecodedExceptionTest extends TestCase { - public function testGetMessage_Constructed_ReturnsMatchingValue(): void { $exception = new JsonNotDecodedException('a'); @@ -26,12 +24,6 @@ public function testGetJson_ConstructedWithJson_ReturnsSameValue(): void self::assertSame('a', $exception->getJson()); } - public function testGetCode_Always_ReturnsZero(): void - { - $exception = new JsonNotDecodedException('a'); - self::assertSame(0, $exception->getCode()); - } - public function testGetPrevious_ConstructedWithoutPrevious_ReturnsNull(): void { $exception = new JsonNotDecodedException('a'); diff --git a/tests/Value/EncodedJson/NodeValueFactoryTest.php b/tests/Value/EncodedJson/NodeValueFactoryTest.php index c5d4d8d..27a3edf 100644 --- a/tests/Value/EncodedJson/NodeValueFactoryTest.php +++ b/tests/Value/EncodedJson/NodeValueFactoryTest.php @@ -4,6 +4,7 @@ namespace Remorhaz\JSON\Data\Test\Value\EncodedJson; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Path\Path; use Remorhaz\JSON\Data\Value\DecodedJson\NodeValueFactoryInterface; @@ -11,12 +12,9 @@ use Remorhaz\JSON\Data\Value\EncodedJson\NodeValueFactory; use Remorhaz\JSON\Data\Value\NodeValueInterface; -/** - * @covers \Remorhaz\JSON\Data\Value\EncodedJson\NodeValueFactory - */ +#[CoversClass(NodeValueFactory::class)] class NodeValueFactoryTest extends TestCase { - public function testCreate_Always_ReturnsNodeValueInstance(): void { self::assertInstanceOf(NodeValueFactory::class, NodeValueFactory::create()); @@ -31,7 +29,7 @@ public function testCreateValue_InvalidJson_ThrowsException(): void public function testCreateValue_ValidJsonNoPath_PassesDecodedJsonAndNullToDecodedJsonFactory(): void { - $decodedJsonFactory = $this->createMock(NodeValueFactoryInterface::class); + $decodedJsonFactory = self::createMock(NodeValueFactoryInterface::class); $encodedJsonFactory = new NodeValueFactory($decodedJsonFactory); $decodedJsonFactory @@ -43,7 +41,7 @@ public function testCreateValue_ValidJsonNoPath_PassesDecodedJsonAndNullToDecode public function testCreateValue_ValidJsonWithPath_PassesDecodedJsonAndSamePathInstanceToDecodedJsonFactory(): void { - $decodedJsonFactory = $this->createMock(NodeValueFactoryInterface::class); + $decodedJsonFactory = self::createMock(NodeValueFactoryInterface::class); $encodedJsonFactory = new NodeValueFactory($decodedJsonFactory); $path = new Path(); @@ -56,10 +54,10 @@ public function testCreateValue_ValidJsonWithPath_PassesDecodedJsonAndSamePathIn public function testCreateValue_DecodedJsonFactoryReturnsInstanceReturnsSameInstance(): void { - $decodedJsonFactory = $this->createMock(NodeValueFactoryInterface::class); + $decodedJsonFactory = self::createStub(NodeValueFactoryInterface::class); $encodedJsonFactory = new NodeValueFactory($decodedJsonFactory); - $nodeValue = $this->createMock(NodeValueInterface::class); + $nodeValue = self::createStub(NodeValueInterface::class); $decodedJsonFactory ->method('createValue') ->willReturn($nodeValue); diff --git a/tests/Walker/EventGeneratorTest.php b/tests/Walker/EventGeneratorTest.php new file mode 100644 index 0000000..72ad95e --- /dev/null +++ b/tests/Walker/EventGeneratorTest.php @@ -0,0 +1,180 @@ + $expectedEvents + */ + #[DataProvider('providerValueEvents')] + public function testInvoke_ConstructedWithValue_IteratesMatchingEvents(mixed $value, array $expectedEvents): void + { + $nodeFactory = new NodeValueFactory(); + $value = $nodeFactory->createValue($value, new Path(1)); + $generator = new EventGenerator($value, new Path(2)); + $events = iterator_to_array($generator(), false); + self::assertSame($expectedEvents, $this->exportEvents(...$events)); + } + + /** + * @return iterable}> + */ + public static function providerValueEvents(): iterable + { + return [ + 'Scalar value' => [ + 'a', + [ + [ + 'class' => ScalarEvent::class, + 'path' => [2], + 'data' => 'a', + ], + ], + ], + 'Array with two scalars' => [ + ['a', 'b'], + [ + [ + 'class' => BeforeArrayEvent::class, + 'path' => [2], + ], + [ + 'class' => BeforeElementEvent::class, + 'path' => [2, 0], + 'index' => 0, + ], + [ + 'class' => ScalarEvent::class, + 'path' => [2, 0], + 'data' => 'a', + ], + [ + 'class' => AfterElementEvent::class, + 'path' => [2, 0], + 'index' => 0, + ], + [ + 'class' => BeforeElementEvent::class, + 'path' => [2, 1], + 'index' => 1, + ], + [ + 'class' => ScalarEvent::class, + 'path' => [2, 1], + 'data' => 'b', + ], + [ + 'class' => AfterElementEvent::class, + 'path' => [2, 1], + 'index' => 1, + ], + [ + 'class' => AfterArrayEvent::class, + 'path' => [2], + ], + ], + ], + 'Object with two properties' => [ + (object) ['a' => 'b', 'c' => 'd'], + [ + [ + 'class' => BeforeObjectEvent::class, + 'path' => [2], + ], + [ + 'class' => BeforePropertyEvent::class, + 'path' => [2, 'a'], + 'name' => 'a', + ], + [ + 'class' => ScalarEvent::class, + 'path' => [2, 'a'], + 'data' => 'b', + ], + [ + 'class' => AfterPropertyEvent::class, + 'path' => [2, 'a'], + 'name' => 'a', + ], + [ + 'class' => BeforePropertyEvent::class, + 'path' => [2, 'c'], + 'name' => 'c', + ], + [ + 'class' => ScalarEvent::class, + 'path' => [2, 'c'], + 'data' => 'd', + ], + [ + 'class' => AfterPropertyEvent::class, + 'path' => [2, 'c'], + 'name' => 'c', + ], + [ + 'class' => AfterObjectEvent::class, + 'path' => [2], + ], + ], + ], + ]; + } + + private function exportEvents(EventInterface ...$events): array + { + return array_map($this->exportEvent(...), $events); + } + + private function exportEvent(EventInterface $event): array + { + return [ + 'class' => $event::class, + 'path' => $event->getPath()->getElements(), + ...match (true) { + $event instanceof ScalarEventInterface => ['data' => $event->getData()], + $event instanceof ElementEventInterface => ['index' => $event->getIndex()], + $event instanceof PropertyEventInterface => ['name' => $event->getName()], + default => [], + }, + ]; + } + + public function testInvoke_ConstructedWithInvalidValue_ThrowsException(): void + { + $value = self::createStub(NodeValueInterface::class); + $generator = new EventGenerator($value, new Path()); + $this->expectException(UnexpectedEntityException::class); + iterator_to_array($generator()); + } +} diff --git a/tests/Walker/Exception/UnexpectedEntityExceptionTest.php b/tests/Walker/Exception/UnexpectedEntityExceptionTest.php index 5d041b5..5049e16 100644 --- a/tests/Walker/Exception/UnexpectedEntityExceptionTest.php +++ b/tests/Walker/Exception/UnexpectedEntityExceptionTest.php @@ -5,15 +5,13 @@ namespace Remorhaz\JSON\Data\Test\Walker\Exception; use Exception; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Remorhaz\JSON\Data\Walker\Exception\UnexpectedEntityException; -/** - * @covers \Remorhaz\JSON\Data\Walker\Exception\UnexpectedEntityException - */ +#[CoversClass(UnexpectedEntityException::class)] class UnexpectedEntityExceptionTest extends TestCase { - public function testGetMessage_Constructed_ReturnsMatchingValue(): void { $exception = new UnexpectedEntityException('a'); @@ -26,12 +24,6 @@ public function testGetEntity_ConstructedWithEntity_ReturnsSameValue(): void self::assertSame('a', $exception->getEntity()); } - public function testGetCode_Always_ReturnsZero(): void - { - $exception = new UnexpectedEntityException('a'); - self::assertSame(0, $exception->getCode()); - } - public function testGetPrevious_ConstructedWithoutPrevious_ReturnsNull(): void { $exception = new UnexpectedEntityException('a'); diff --git a/vendor-bin/cs/composer.json b/vendor-bin/cs/composer.json new file mode 100644 index 0000000..31bfe6e --- /dev/null +++ b/vendor-bin/cs/composer.json @@ -0,0 +1,5 @@ +{ + "require-dev": { + "squizlabs/php_codesniffer": "^3.8" + } +} diff --git a/vendor-bin/psalm/composer.json b/vendor-bin/psalm/composer.json new file mode 100644 index 0000000..83fd035 --- /dev/null +++ b/vendor-bin/psalm/composer.json @@ -0,0 +1,9 @@ +{ + "require-dev": { + "vimeo/psalm": "^5.21.1", + "psalm/plugin-phpunit": "^0.18.4" + }, + "conflict": { + "netresearch/jsonmapper": "<4.4.1" + } +}