diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index fe461b4..5545cd2 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -15,6 +15,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Dependency Review' uses: actions/dependency-review-action@v2 diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index eb95ed1..3759c43 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -8,14 +8,14 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.4' + php-version: '8.5' tools: composer - name: Install dependencies @@ -27,4 +27,4 @@ jobs: - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v4 with: - commit_message: Fix styling to adhere to PSR12 standards. \ No newline at end of file + commit_message: Fix styling to adhere to PSR12 standards. diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index a789f85..f716743 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -7,15 +7,19 @@ on: pull_request: jobs: + # The default dependency set: laravel/framework ^12 || ^13 is installed, so + # the provider's boot() is exercised against a real Illuminate application. test: + name: PHP ${{ matrix.php-version }} - locked deps runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - php-version: ['8.3', '8.4'] + php-version: ['8.3', '8.4', '8.5'] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 @@ -25,7 +29,71 @@ jobs: coverage: xdebug - name: Install dependencies - run: composer install --prefer-dist --no-progress --no-suggest + run: composer install --prefer-dist --no-progress --no-interaction - name: Run PHPUnit - run: vendor/bin/phpunit --testdox \ No newline at end of file + run: vendor/bin/phpunit --testdox + + # Every Laravel major the package claims to support, resolved from scratch. + # + # This job answers "does the suite pass against this dependency set", so it + # runs without a coverage driver. phpunit.xml sets failOnWarning, and PHPUnit + # warns when a coverage driver is missing, so --no-coverage is required here + # rather than optional -- without it the job fails on the warning alone while + # every test passes. + # + # Laravel 10 and 11 use the split illuminate/* packages deliberately: every + # laravel/framework release in those ranges is withheld by Composer's + # security-advisory policy, and the alternative would be disabling that check. + # The advisories do not apply to the split packages, so those majors are still + # exercised here -- they just cannot supply a real Application, and the + # boot() tests skip themselves accordingly. + laravel-majors: + name: Laravel ${{ matrix.laravel }} - PHP ${{ matrix.php-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php-version: ['8.3', '8.5'] + laravel: ['10', '11', '12', '13'] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: zip, intl + coverage: none + + - name: Pin the Laravel major + run: | + # laravel/framework replaces the illuminate/* split packages, so the + # two cannot both be constrained at once. Drop it for the majors whose + # framework releases are advisory-blocked, and pin it for the rest. + if [ "${{ matrix.laravel }}" -le 11 ]; then + composer remove --dev laravel/framework --no-update --no-interaction + else + composer require --dev "laravel/framework:^${{ matrix.laravel }}.0" \ + --no-update --no-interaction + fi + + for pkg in support validation filesystem translation config; do + composer require --dev "illuminate/${pkg}:^${{ matrix.laravel }}.0" \ + --no-update --no-interaction + done + + - name: Resolve dependencies + run: | + composer update --with-all-dependencies \ + --prefer-dist --no-progress --no-interaction + + - name: Show what actually resolved + # illuminate/support is not separately installed when laravel/framework + # is present -- the framework replaces it -- so list whichever is there. + run: composer show | grep -E '^(laravel/framework|illuminate/support) ' || true + + - name: Run PHPUnit + run: vendor/bin/phpunit --testdox --no-coverage diff --git a/.gitignore b/.gitignore index 42a1ee3..18972a7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ clover.xml /data-validation *.cache .phpunit.result.cache +docs/ diff --git a/Dockerfile b/Dockerfile index 4d074b4..1af589c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,13 @@ -FROM php:8.4-fpm +ARG PHP_VERSION=8.5 +FROM php:${PHP_VERSION}-fpm RUN apt-get update \ && apt-get -y install libzip-dev zlib1g-dev git zip unzip libicu-dev \ - && apt-get clean; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* -RUN docker-php-ext-configure zip && docker-php-ext-install zip intl +RUN docker-php-ext-configure zip \ + && docker-php-ext-install zip intl # Install Xdebug only RUN pecl install xdebug \ @@ -24,4 +27,4 @@ RUN chown -R www-data:www-data /var/www # Expose port 9000 and start php-fpm server EXPOSE 9000 -CMD ["php-fpm"] \ No newline at end of file +CMD ["php-fpm"] diff --git a/README.md b/README.md index 942a347..65b43bb 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,24 @@ composer require yorcreative/data-validation ``` **Requirements**: -- PHP 8.3 or 8.4 +- PHP 8.3, 8.4, or 8.5 + +### Optional Laravel Integration + +The core library has **zero runtime framework dependencies**. When the package is installed inside a Laravel 10, 11, 12, or 13 application, an optional service provider (`YorCreative\DataValidation\Laravel\DataValidationServiceProvider`) is auto-discovered and binds: + +- A shared `DataValidationConfig` singleton, hydrated from `config/data-validation.php` +- A `yorcreative.data-validation` factory closure that returns a `Validator` pre-configured from the container + +Publish the config file to customize cache limits and chunk size: + +```bash +php artisan vendor:publish --tag=data-validation-config +``` + +Set `chunk_size` to `null` if you want the validator to fall back to its dynamic wildcard chunking heuristic instead of a fixed override. + +Outside of Laravel the provider class is never loaded — the library remains fully framework-agnostic. ## Basic Usage @@ -191,6 +208,8 @@ if ($validator->fails()) { } ``` +`fails()` and `passes()` trigger validation on first use, so they can be used directly when you do not want to call `validate()` yourself. + ## Extending with Custom Rules For applications requiring bespoke validation logic, implement custom rules via `ValidationRuleInterface`: @@ -311,4 +330,4 @@ Report issues or suggest features on the [GitHub repository](https://github.com/ ## License -DataValidation is licensed under the MIT License. See the [LICENSE](https://github.com/yorcreative/data-validation/blob/main/LICENSE) file for details. \ No newline at end of file +DataValidation is licensed under the MIT License. See the [LICENSE](https://github.com/yorcreative/data-validation/blob/main/LICENSE) file for details. diff --git a/composer.json b/composer.json index 856c1f7..93182e6 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "psr-12", "psr-4", "security", - "open-source ", + "open-source", "no-dependencies", "testing", "enterprise", @@ -27,18 +27,23 @@ "benchmarks" ], "require": { - "php": "^8.3|^8.4" + "php": "^8.3 || ^8.4 || ^8.5" }, "require-dev": { - "orchestra/testbench": "^9.0|^10.0", "phpunit/phpunit": "^10.0|^11.5.3", "squizlabs/php_codesniffer": "^3.8.0", "friendsofphp/php-cs-fixer": "^3.44.2", "mockery/mockery": "^1.0", - "illuminate/validation": "^10.0 || ^11.0", - "symfony/translation": "^6.0 || ^7.0", - "illuminate/filesystem": "^10.0 || ^11.0", - "illuminate/translation": "^10.0 || ^11.0" + "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "illuminate/validation": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "symfony/translation": "^6.0 || ^7.0 || ^8.0", + "illuminate/filesystem": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "illuminate/translation": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "illuminate/config": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "laravel/framework": "^12.0 || ^13.0" + }, + "suggest": { + "illuminate/support": "Required only when using the optional Laravel ServiceProvider (^10.0 || ^11.0 || ^12.0 || ^13.0)." }, "autoload": { "psr-4": { @@ -53,7 +58,7 @@ }, "extra": { "laravel": { - "providers": ["YorCreative\\DataValidation\\DataValidationServiceProvider"] + "providers": ["YorCreative\\DataValidation\\Laravel\\DataValidationServiceProvider"] } }, "scripts": { @@ -66,6 +71,9 @@ "coverage-html": "php ./vendor/bin/phpunit --testdox --coverage-html data-validation" }, "config": { - "process-timeout": 0 + "process-timeout": 0, + "platform": { + "php": "8.3.0" + } } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index 143b93c..8f988f3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,30 +4,29 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "976126315911a52681f81e110a7148ed", + "content-hash": "b14306969e91f998cc6e426887e7a528", "packages": [], "packages-dev": [ { "name": "brick/math", - "version": "0.12.3", + "version": "0.18.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" + "reference": "82944324d1c1bdb2c2618e89978d4e2ad78d69ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", + "url": "https://api.github.com/repos/brick/math/zipball/82944324d1c1bdb2c2618e89978d4e2ad78d69ad", + "reference": "82944324d1c1bdb2c2618e89978d4e2ad78d69ad", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.2" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^10.1", - "vimeo/psalm": "6.8.8" + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" }, "type": "library", "autoload": { @@ -57,7 +56,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.3" + "source": "https://github.com/brick/math/tree/0.18.0" }, "funding": [ { @@ -65,20 +64,20 @@ "type": "github" } ], - "time": "2025-02-28T13:11:00+00:00" + "time": "2026-06-14T18:21:03+00:00" }, { "name": "carbonphp/carbon-doctrine-types", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", - "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + "reference": "5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", - "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc", + "reference": "5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc", "shasum": "" }, "require": { @@ -93,6 +92,11 @@ "phpunit/phpunit": "^10.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" @@ -118,7 +122,7 @@ ], "support": { "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", - "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.1" }, "funding": [ { @@ -134,7 +138,7 @@ "type": "tidelift" } ], - "time": "2024-02-09T16:56:22+00:00" + "time": "2026-09-06T14:11:38+00:00" }, { "name": "clue/ndjson-react", @@ -202,28 +206,29 @@ }, { "name": "composer/pcre", - "version": "3.3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, "conflict": { - "phpstan/phpstan": "<1.11.10" + "phpstan/phpstan": "<2.2.2" }, "require-dev": { - "phpstan/phpstan": "^1.12 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", - "phpunit/phpunit": "^8 || ^9" + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9" }, "type": "library", "extra": { @@ -261,7 +266,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.2" + "source": "https://github.com/composer/pcre/tree/3.4.0" }, "funding": [ { @@ -271,26 +276,22 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-11-12T16:29:46+00:00" + "time": "2026-06-07T11:47:49+00:00" }, { "name": "composer/semver", - "version": "3.4.3", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { @@ -342,7 +343,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.3" + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -352,13 +353,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-09-19T14:15:21+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "composer/xdebug-handler", @@ -503,33 +500,32 @@ }, { "name": "doctrine/inflector", - "version": "2.0.10", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11.0", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25 || ^5.4" + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + "Doctrine\\Inflector\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -574,7 +570,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.10" + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { @@ -590,7 +586,7 @@ "type": "tidelift" } ], - "time": "2024-02-18T20:23:39+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { "name": "doctrine/lexer", @@ -671,29 +667,28 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.4.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "8c784d071debd117328803d86b2097615b457500" + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500", - "reference": "8c784d071debd117328803d86b2097615b457500", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013", "shasum": "" }, "require": { - "php": "^7.2|^8.0", - "webmozart/assert": "^1.0" + "php": "^8.2|^8.3|^8.4|^8.5" }, "replace": { "mtdowling/cron-expression": "^1.0" }, "require-dev": { - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0" }, "type": "library", "extra": { @@ -724,7 +719,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.0" }, "funding": [ { @@ -732,7 +727,7 @@ "type": "github" } ], - "time": "2024-10-09T13:47:03+00:00" + "time": "2025-10-31T18:51:33+00:00" }, { "name": "egulias/email-validator", @@ -802,29 +797,52 @@ "time": "2025-03-06T22:45:56+00:00" }, { - "name": "evenement/evenement", - "version": "v3.0.2", + "name": "ergebnis/agent-detector", + "version": "1.2.0", "source": { "type": "git", - "url": "https://github.com/igorw/evenement.git", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + "url": "https://github.com/ergebnis/agent-detector.git", + "reference": "e211f17928c8b95a51e06040792d57f5462fb271" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "url": "https://api.github.com/repos/ergebnis/agent-detector/zipball/e211f17928c8b95a51e06040792d57f5462fb271", + "reference": "e211f17928c8b95a51e06040792d57f5462fb271", "shasum": "" }, "require": { - "php": ">=7.0" + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0" }, "require-dev": { - "phpunit/phpunit": "^9 || ^6" + "ergebnis/composer-normalize": "^2.51.0", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.60.2", + "ergebnis/phpstan-rules": "^2.13.1", + "ergebnis/phpunit-slow-test-detector": "^2.24.0", + "ergebnis/rector-rules": "^1.18.1", + "fakerphp/faker": "^1.24.1", + "infection/infection": "^0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.54", + "phpstan/phpstan-deprecation-rules": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.16", + "phpstan/phpstan-strict-rules": "^2.0.10", + "phpunit/phpunit": "^9.6.34", + "rector/rector": "^2.4.2" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, "autoload": { "psr-4": { - "Evenement\\": "src/" + "Ergebnis\\AgentDetector\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -833,61 +851,44 @@ ], "authors": [ { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" } ], - "description": "Événement is a very simple event dispatching library for PHP", - "keywords": [ - "event-dispatcher", - "event-emitter" - ], + "description": "Provides a detector for detecting the presence of an agent.", + "homepage": "https://github.com/ergebnis/agent-detector", "support": { - "issues": "https://github.com/igorw/evenement/issues", - "source": "https://github.com/igorw/evenement/tree/v3.0.2" + "issues": "https://github.com/ergebnis/agent-detector/issues", + "security": "https://github.com/ergebnis/agent-detector/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/agent-detector" }, - "time": "2023-08-08T05:53:35+00:00" + "time": "2026-05-07T08:19:07+00:00" }, { - "name": "fakerphp/faker", - "version": "v1.24.1", + "name": "evenement/evenement", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/FakerPHP/Faker.git", - "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", - "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "conflict": { - "fzaninotto/faker": "*" + "php": ">=7.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "doctrine/persistence": "^1.3 || ^2.0", - "ext-intl": "*", - "phpunit/phpunit": "^9.5.26", - "symfony/phpunit-bridge": "^5.4.16" - }, - "suggest": { - "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", - "ext-curl": "Required by Faker\\Provider\\Image to download images.", - "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", - "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", - "ext-mbstring": "Required for multibyte Unicode string functionality." + "phpunit/phpunit": "^9 || ^6" }, "type": "library", "autoload": { "psr-4": { - "Faker\\": "src/Faker/" + "Evenement\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -896,33 +897,33 @@ ], "authors": [ { - "name": "François Zaninotto" + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" } ], - "description": "Faker is a PHP library that generates fake data for you.", + "description": "Événement is a very simple event dispatching library for PHP", "keywords": [ - "data", - "faker", - "fixtures" + "event-dispatcher", + "event-emitter" ], "support": { - "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" }, - "time": "2024-11-21T13:46:39+00:00" + "time": "2023-08-08T05:53:35+00:00" }, { "name": "fidry/cpu-core-counter", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "8520451a140d3f46ac33042715115e290cf5785f" + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", - "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, "require": { @@ -932,10 +933,10 @@ "fidry/makefile": "^0.2.0", "fidry/php-cs-fixer-config": "^1.1.2", "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^1.9.2", - "phpstan/phpstan-deprecation-rules": "^1.0.0", - "phpstan/phpstan-phpunit": "^1.2.2", - "phpstan/phpstan-strict-rules": "^1.4.4", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^8.5.31 || ^9.5.26", "webmozarts/strict-phpunit": "^7.5" }, @@ -962,7 +963,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, "funding": [ { @@ -970,133 +971,62 @@ "type": "github" } ], - "time": "2024-08-06T10:04:20+00:00" - }, - { - "name": "filp/whoops", - "version": "2.18.1", - "source": { - "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26", - "reference": "8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^4.0 || ^5.0" - }, - "suggest": { - "symfony/var-dumper": "Pretty print complex values better with var-dumper available", - "whoops/soap": "Formats errors as SOAP responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Whoops\\": "src/Whoops/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" - } - ], - "description": "php error handling for cool kids", - "homepage": "https://filp.github.io/whoops/", - "keywords": [ - "error", - "exception", - "handling", - "library", - "throwable", - "whoops" - ], - "support": { - "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.18.1" - }, - "funding": [ - { - "url": "https://github.com/denis-sokolov", - "type": "github" - } - ], - "time": "2025-06-03T18:56:14+00:00" + "time": "2025-08-14T07:29:31+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.75.0", + "version": "v3.95.25", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "399a128ff2fdaf4281e4e79b755693286cdf325c" + "reference": "2cdfc1f3daf173d83a1ebb6177949b58c95de6ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/399a128ff2fdaf4281e4e79b755693286cdf325c", - "reference": "399a128ff2fdaf4281e4e79b755693286cdf325c", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2cdfc1f3daf173d83a1ebb6177949b58c95de6ff", + "reference": "2cdfc1f3daf173d83a1ebb6177949b58c95de6ff", "shasum": "" }, "require": { - "clue/ndjson-react": "^1.0", + "clue/ndjson-react": "^1.3", "composer/semver": "^3.4", - "composer/xdebug-handler": "^3.0.3", + "composer/xdebug-handler": "^3.0.5", + "ergebnis/agent-detector": "^1.2", "ext-filter": "*", "ext-hash": "*", "ext-json": "*", "ext-tokenizer": "*", - "fidry/cpu-core-counter": "^1.2", + "fidry/cpu-core-counter": "^1.3", "php": "^7.4 || ^8.0", - "react/child-process": "^0.6.5", - "react/event-loop": "^1.0", - "react/promise": "^2.0 || ^3.0", - "react/socket": "^1.0", - "react/stream": "^1.0", - "sebastian/diff": "^4.0 || ^5.1 || ^6.0 || ^7.0", - "symfony/console": "^5.4 || ^6.4 || ^7.0", - "symfony/event-dispatcher": "^5.4 || ^6.4 || ^7.0", - "symfony/filesystem": "^5.4 || ^6.4 || ^7.0", - "symfony/finder": "^5.4 || ^6.4 || ^7.0", - "symfony/options-resolver": "^5.4 || ^6.4 || ^7.0", - "symfony/polyfill-mbstring": "^1.31", - "symfony/polyfill-php80": "^1.31", - "symfony/polyfill-php81": "^1.31", - "symfony/process": "^5.4 || ^6.4 || ^7.2", - "symfony/stopwatch": "^5.4 || ^6.4 || ^7.0" + "react/child-process": "^0.6.6", + "react/event-loop": "^1.5", + "react/socket": "^1.16", + "react/stream": "^1.4", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0 || ^9.0", + "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.37", + "symfony/polyfill-php80": "^1.37", + "symfony/polyfill-php81": "^1.37", + "symfony/polyfill-php84": "^1.37", + "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" }, "require-dev": { - "facile-it/paraunit": "^1.3.1 || ^2.6", - "infection/infection": "^0.29.14", - "justinrainbow/json-schema": "^5.3 || ^6.2", - "keradus/cli-executor": "^2.1", - "mikey179/vfsstream": "^1.6.12", - "php-coveralls/php-coveralls": "^2.7", - "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", - "phpunit/phpunit": "^9.6.22 || ^10.5.45 || ^11.5.12", - "symfony/var-dumper": "^5.4.48 || ^6.4.18 || ^7.2.3", - "symfony/yaml": "^5.4.45 || ^6.4.18 || ^7.2.3" + "facile-it/paraunit": "^1.3.1 || ^2.11.0", + "infection/infection": "^0.32.7", + "justinrainbow/json-schema": "^6.10.0", + "keradus/cli-executor": "^2.3", + "php-coveralls/php-coveralls": "^2.9.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.8", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.8", + "phpunit/phpunit": "^9.6.35 || ^10.5.64 || ^11.5.56 || ^12.5.31 || ^13.0.6", + "symfony/polyfill-php85": "^1.38", + "symfony/var-dumper": "^5.4.48 || ^6.4.36 || ^7.4.8 || ^8.1.1", + "symfony/yaml": "^5.4.53 || ^6.4.41 || ^7.4.13 || ^8.1.1" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -1111,7 +1041,7 @@ "PhpCsFixer\\": "src/" }, "exclude-from-classmap": [ - "src/Fixer/Internal/*" + "src/**/Internal/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -1137,7 +1067,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.75.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.25" }, "funding": [ { @@ -1145,35 +1075,35 @@ "type": "github" } ], - "time": "2025-03-31T18:40:42+00:00" + "time": "2026-09-08T11:11:37+00:00" }, { "name": "fruitcake/php-cors", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/fruitcake/php-cors.git", - "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b" + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/3d158f36e7875e2f040f37bc0573956240a5a38b", - "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", "shasum": "" }, "require": { - "php": "^7.4|^8.0", - "symfony/http-foundation": "^4.4|^5.4|^6|^7" + "php": "^8.1", + "symfony/http-foundation": "^5.4|^6.4|^7.3|^8" }, "require-dev": { - "phpstan/phpstan": "^1.4", + "phpstan/phpstan": "^2", "phpunit/phpunit": "^9", - "squizlabs/php_codesniffer": "^3.5" + "squizlabs/php_codesniffer": "^4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -1204,7 +1134,7 @@ ], "support": { "issues": "https://github.com/fruitcake/php-cors/issues", - "source": "https://github.com/fruitcake/php-cors/tree/v1.3.0" + "source": "https://github.com/fruitcake/php-cors/tree/v1.4.0" }, "funding": [ { @@ -1216,28 +1146,28 @@ "type": "github" } ], - "time": "2023-10-12T05:21:21+00:00" + "time": "2025-12-03T09:33:47+00:00" }, { "name": "graham-campbell/result-type", - "version": "v1.1.3", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + "reference": "adccca3324eece92ca35463648c12b9e6293c05b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", - "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/adccca3324eece92ca35463648c12b9e6293c05b", + "reference": "adccca3324eece92ca35463648c12b9e6293c05b", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.3" + "phpoption/phpoption": "^1.10" }, "require-dev": { - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.52 || ^9.6.34 || ^10.5.63 || ^11.5.55 || ^12.5.14" }, "type": "library", "autoload": { @@ -1266,7 +1196,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.2.0" }, "funding": [ { @@ -1278,29 +1208,31 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:45:45+00:00" + "time": "2026-08-24T09:06:52+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.9.3", + "version": "8.2.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" + "reference": "93939470950a9b11e2e84204166ef5e048c55fe4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", - "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/93939470950a9b11e2e84204166ef5e048c55fe4", + "reference": "93939470950a9b11e2e84204166ef5e048c55fe4", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.3", - "guzzlehttp/psr7": "^2.7.0", - "php": "^7.2.5 || ^8.0", + "guzzlehttp/promises": "^3.0.2", + "guzzlehttp/psr7": "^3.1", + "php": "^7.4 || ^8.0", "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" + "psr/http-factory": "^1.0", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php82": "^1.27" }, "provide": { "psr/http-client-implementation": "1.0" @@ -1308,9 +1240,10 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "guzzle/client-integration-tests": "3.0.2", + "guzzle/client-integration-tests": "4.0.1", + "guzzlehttp/test-server": "^1.0.1", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "phpunit/phpunit": "^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -1326,9 +1259,6 @@ } }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\": "src/" } @@ -1388,7 +1318,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.3" + "source": "https://github.com/guzzle/guzzle/tree/8.2.0" }, "funding": [ { @@ -1404,28 +1334,28 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:37:11+00:00" + "time": "2026-09-06T13:55:09+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.2.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" + "reference": "42118e66a53c492effaf92bc357e931985d5c6f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", + "url": "https://api.github.com/repos/guzzle/promises/zipball/42118e66a53c492effaf92bc357e931985d5c6f9", + "reference": "42118e66a53c492effaf92bc357e931985d5c6f9", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^9.6.34" }, "type": "library", "extra": { @@ -1471,7 +1401,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.2.0" + "source": "https://github.com/guzzle/promises/tree/3.0.2" }, "funding": [ { @@ -1487,36 +1417,39 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:27:01+00:00" + "time": "2026-08-24T10:00:26+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.7.1", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" + "reference": "a3059ba1a84c9139c4ae03cf0f45bea276c97c74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a3059ba1a84c9139c4ae03cf0f45bea276c97c74", + "reference": "a3059ba1a84c9139c4ae03cf0f45bea276c97c74", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0" + "php": "^7.4 || ^8.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^2.0", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php82": "^1.27" }, "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" + "psr/http-factory-implementation": "1.1", + "psr/http-message-implementation": "2.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "http-interop/http-factory-tests": "1.1.0", + "jshttp/mime-db": "1.54.0.1", + "php-http/psr7-integration-tests": "^1.5.1", + "phpunit/phpunit": "^9.6.34" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -1587,7 +1520,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.1" + "source": "https://github.com/guzzle/psr7/tree/3.1.0" }, "funding": [ { @@ -1603,30 +1536,30 @@ "type": "tidelift" } ], - "time": "2025-03-27T12:30:47+00:00" + "time": "2026-08-24T11:02:13+00:00" }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.4", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "30e286560c137526eccd4ce21b2de477ab0676d2" + "reference": "7a466ad606491eb6528c717482f7cca77f1851f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/30e286560c137526eccd4ce21b2de477ab0676d2", - "reference": "30e286560c137526eccd4ce21b2de477ab0676d2", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/7a466ad606491eb6528c717482f7cca77f1851f3", + "reference": "7a466ad606491eb6528c717482f7cca77f1851f3", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0", - "symfony/polyfill-php80": "^1.24" + "php": "^7.4 || ^8.0", + "symfony/polyfill-php80": "^1.25" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.36 || ^9.6.15", - "uri-template/tests": "1.0.0" + "phpunit/phpunit": "^9.6.34", + "uri-template/tests": "1.0.2" }, "type": "library", "extra": { @@ -1673,7 +1606,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.4" + "source": "https://github.com/guzzle/uri-template/tree/v2.0.1" }, "funding": [ { @@ -1689,23 +1622,25 @@ "type": "tidelift" } ], - "time": "2025-02-03T10:55:03+00:00" + "time": "2026-08-24T17:13:02+00:00" }, { "name": "hamcrest/hamcrest-php", - "version": "v2.1.1", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + "reference": "b61cd040da1a4925bc90a51c074f5297e7c0fa52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b61cd040da1a4925bc90a51c074f5297e7c0fa52", + "reference": "b61cd040da1a4925bc90a51c074f5297e7c0fa52", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-dom": "*", "php": "^7.4|^8.0" }, "replace": { @@ -1714,13 +1649,15 @@ "kodova/hamcrest-php": "*" }, "require-dev": { + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1738,30 +1675,30 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v3.0.0" }, - "time": "2025-04-30T06:54:44+00:00" + "time": "2026-03-17T11:56:53+00:00" }, { "name": "laravel/framework", - "version": "v11.45.1", + "version": "v13.31.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "b09ba32795b8e71df10856a2694706663984a239" + "reference": "7c75fbf93f91fa077d3df1c820cc14f4e59a9774" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/b09ba32795b8e71df10856a2694706663984a239", - "reference": "b09ba32795b8e71df10856a2694706663984a239", + "url": "https://api.github.com/repos/laravel/framework/zipball/7c75fbf93f91fa077d3df1c820cc14f4e59a9774", + "reference": "7c75fbf93f91fa077d3df1c820cc14f4e59a9774", "shasum": "" }, "require": { - "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", + "brick/math": "^0.14.2 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19", "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.4", - "egulias/email-validator": "^3.2.1|^4.0", + "egulias/email-validator": "^4.0", "ext-ctype": "*", "ext-filter": "*", "ext-hash": "*", @@ -1770,34 +1707,39 @@ "ext-session": "*", "ext-tokenizer": "*", "fruitcake/php-cors": "^1.3", - "guzzlehttp/guzzle": "^7.8.2", - "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", - "laravel/serializable-closure": "^1.3|^2.0", - "league/commonmark": "^2.7", + "guzzlehttp/guzzle": "^7.8.2 || ^8.0", + "guzzlehttp/promises": "^2.0.3 || ^3.0", + "guzzlehttp/psr7": "^2.9 || ^3.0", + "guzzlehttp/uri-template": "^1.0 || ^2.0", + "laravel/prompts": "^0.3.11", + "laravel/serializable-closure": "^2.0.10", + "league/commonmark": "^2.8.1", "league/flysystem": "^3.25.1", "league/flysystem-local": "^3.25.1", "league/uri": "^7.5.1", - "monolog/monolog": "^3.0", - "nesbot/carbon": "^2.72.6|^3.8.4", + "monolog/monolog": "^3.10", + "nesbot/carbon": "^3.8.4", "nunomaduro/termwind": "^2.0", - "php": "^8.2", - "psr/container": "^1.1.1|^2.0.1", - "psr/log": "^1.0|^2.0|^3.0", - "psr/simple-cache": "^1.0|^2.0|^3.0", + "php": "^8.3", + "psr/container": "^1.1.1 || ^2.0.1", + "psr/http-message": "^1.0 || ^2.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "ramsey/uuid": "^4.7", - "symfony/console": "^7.0.3", - "symfony/error-handler": "^7.0.3", - "symfony/finder": "^7.0.3", - "symfony/http-foundation": "^7.2.0", - "symfony/http-kernel": "^7.0.3", - "symfony/mailer": "^7.0.3", - "symfony/mime": "^7.0.3", - "symfony/polyfill-php83": "^1.31", - "symfony/process": "^7.0.3", - "symfony/routing": "^7.0.3", - "symfony/uid": "^7.0.3", - "symfony/var-dumper": "^7.0.3", + "symfony/console": "^7.4.0 || ^8.0.0", + "symfony/error-handler": "^7.4.0 || ^8.0.0", + "symfony/finder": "^7.4.0 || ^8.0.0", + "symfony/http-foundation": "^7.4.0 || ^8.0.0", + "symfony/http-kernel": "^7.4.0 || ^8.0.0", + "symfony/mailer": "^7.4.0 || ^8.0.0", + "symfony/mime": "^7.4.0 || ^8.0.0", + "symfony/polyfill-php84": "^1.36", + "symfony/polyfill-php85": "^1.36", + "symfony/polyfill-php86": "^1.36", + "symfony/process": "^7.4.5 || ^8.0.5", + "symfony/routing": "^7.4.0 || ^8.0.0", + "symfony/uid": "^7.4.0 || ^8.0.0", + "symfony/var-dumper": "^7.4.0 || ^8.0.0", "tijsverkoyen/css-to-inline-styles": "^2.2.5", "vlucas/phpdotenv": "^5.6.1", "voku/portable-ascii": "^2.0.2" @@ -1806,9 +1748,9 @@ "tightenco/collect": "<5.5.33" }, "provide": { - "psr/container-implementation": "1.1|2.0", - "psr/log-implementation": "1.0|2.0|3.0", - "psr/simple-cache-implementation": "1.0|2.0|3.0" + "psr/container-implementation": "1.1 || 2.0", + "psr/log-implementation": "1.0 || 2.0 || 3.0", + "psr/simple-cache-implementation": "1.0 || 2.0 || 3.0" }, "replace": { "illuminate/auth": "self.version", @@ -1829,6 +1771,8 @@ "illuminate/filesystem": "self.version", "illuminate/hashing": "self.version", "illuminate/http": "self.version", + "illuminate/image": "self.version", + "illuminate/json-schema": "self.version", "illuminate/log": "self.version", "illuminate/macroable": "self.version", "illuminate/mail": "self.version", @@ -1838,6 +1782,7 @@ "illuminate/process": "self.version", "illuminate/queue": "self.version", "illuminate/redis": "self.version", + "illuminate/reflection": "self.version", "illuminate/routing": "self.version", "illuminate/session": "self.version", "illuminate/support": "self.version", @@ -1852,8 +1797,7 @@ "aws/aws-sdk-php": "^3.322.9", "ext-gmp": "*", "fakerphp/faker": "^1.24", - "guzzlehttp/promises": "^2.0.3", - "guzzlehttp/psr7": "^2.4", + "intervention/image": "^4.0", "laravel/pint": "^1.18", "league/flysystem-aws-s3-v3": "^3.25.1", "league/flysystem-ftp": "^3.25.1", @@ -1861,22 +1805,24 @@ "league/flysystem-read-only": "^3.25.1", "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", - "orchestra/testbench-core": "^9.13.2", - "pda/pheanstalk": "^5.0.6", + "opis/json-schema": "^2.4.1", + "orchestra/testbench-core": "^11.0.0", + "pda/pheanstalk": "^7.0.0 || ^8.0.0", "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1", - "predis/predis": "^2.3", - "resend/resend-php": "^0.10.0", - "symfony/cache": "^7.0.3", - "symfony/http-client": "^7.0.3", - "symfony/psr-http-message-bridge": "^7.0.3", - "symfony/translation": "^7.0.3" + "phpunit/phpunit": "^11.5.50 || ^12.5.8 || ^13.0.3", + "predis/predis": "^2.3 || ^3.0", + "rector/rector": "2.6.3", + "resend/resend-php": "^1.0", + "symfony/cache": "^7.4.0 || ^8.0.0", + "symfony/http-client": "^7.4.0 || ^8.0.0", + "symfony/psr-http-message-bridge": "^7.4.0 || ^8.0.0", + "symfony/translation": "^7.4.0 || ^8.0.0" }, "suggest": { "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).", - "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", + "brianium/paratest": "Required to run tests in parallel (^7.0 || ^8.0).", "ext-apcu": "Required to use the APC cache driver.", "ext-fileinfo": "Required to use the Filesystem class.", "ext-ftp": "Required to use the Flysystem FTP driver.", @@ -1885,9 +1831,10 @@ "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", "ext-pdo": "Required to use all database features.", "ext-posix": "Required to use all features of the queue worker.", - "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0|^6.0).", - "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0 || ^5.0 || ^6.0).", + "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.23).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", + "intervention/image": "Required to use the image processing features (^4.0).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", @@ -1895,24 +1842,24 @@ "league/flysystem-read-only": "Required to use read-only disks (^3.25.1)", "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", "mockery/mockery": "Required to use mocking (^1.6).", - "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^7.0 || ^8.0).", "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", - "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.3.6|^12.0.1).", - "predis/predis": "Required to use the predis connector (^2.3).", - "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", - "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^7.0).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).", - "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.0).", - "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.0).", - "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.0).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.0)." + "phpunit/phpunit": "Required to use assertions and run tests (^11.5.50 || ^12.5.8 || ^13.0.3).", + "predis/predis": "Required to use the predis connector (^2.3 || ^3.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0 || ^7.0).", + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0 || ^1.0).", + "spatie/fork": "Required to use the 'fork' concurrency driver (^1.2).", + "symfony/cache": "Required to PSR-6 cache bridge (^7.4 || ^8.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.4 || ^8.0).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.4 || ^8.0).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.4 || ^8.0).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.4 || ^8.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.4 || ^8.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "13.0.x-dev" } }, "autoload": { @@ -1923,6 +1870,7 @@ "src/Illuminate/Filesystem/functions.php", "src/Illuminate/Foundation/helpers.php", "src/Illuminate/Log/functions.php", + "src/Illuminate/Reflection/helpers.php", "src/Illuminate/Support/functions.php", "src/Illuminate/Support/helpers.php" ], @@ -1931,7 +1879,8 @@ "Illuminate\\Support\\": [ "src/Illuminate/Macroable/", "src/Illuminate/Collections/", - "src/Illuminate/Conditionable/" + "src/Illuminate/Conditionable/", + "src/Illuminate/Reflection/" ] } }, @@ -1955,117 +1904,38 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-06-03T14:01:40+00:00" - }, - { - "name": "laravel/pail", - "version": "v1.2.3", - "source": { - "type": "git", - "url": "https://github.com/laravel/pail.git", - "reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/pail/zipball/8cc3d575c1f0e57eeb923f366a37528c50d2385a", - "reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "illuminate/console": "^10.24|^11.0|^12.0", - "illuminate/contracts": "^10.24|^11.0|^12.0", - "illuminate/log": "^10.24|^11.0|^12.0", - "illuminate/process": "^10.24|^11.0|^12.0", - "illuminate/support": "^10.24|^11.0|^12.0", - "nunomaduro/termwind": "^1.15|^2.0", - "php": "^8.2", - "symfony/console": "^6.0|^7.0" - }, - "require-dev": { - "laravel/framework": "^10.24|^11.0|^12.0", - "laravel/pint": "^1.13", - "orchestra/testbench-core": "^8.13|^9.0|^10.0", - "pestphp/pest": "^2.20|^3.0", - "pestphp/pest-plugin-type-coverage": "^2.3|^3.0", - "phpstan/phpstan": "^1.12.27", - "symfony/var-dumper": "^6.3|^7.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Laravel\\Pail\\PailServiceProvider" - ] - }, - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Laravel\\Pail\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - }, - { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" - } - ], - "description": "Easily delve into your Laravel application's log files directly from the command line.", - "homepage": "https://github.com/laravel/pail", - "keywords": [ - "dev", - "laravel", - "logs", - "php", - "tail" - ], - "support": { - "issues": "https://github.com/laravel/pail/issues", - "source": "https://github.com/laravel/pail" - }, - "time": "2025-06-05T13:55:57+00:00" + "time": "2026-09-08T14:22:55+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.5", + "version": "v0.3.24", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "57b8f7efe40333cdb925700891c7d7465325d3b1" + "reference": "5d3cdef29e93ca3b62b1871359db3078cd99908b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/57b8f7efe40333cdb925700891c7d7465325d3b1", - "reference": "57b8f7efe40333cdb925700891c7d7465325d3b1", + "url": "https://api.github.com/repos/laravel/prompts/zipball/5d3cdef29e93ca3b62b1871359db3078cd99908b", + "reference": "5d3cdef29e93ca3b62b1871359db3078cd99908b", "shasum": "" }, "require": { "composer-runtime-api": "^2.2", "ext-mbstring": "*", "php": "^8.1", - "symfony/console": "^6.2|^7.0" + "symfony/console": "^6.2|^7.0|^8.0" }, "conflict": { "illuminate/console": ">=10.17.0 <10.25.0", "laravel/framework": ">=10.17.0 <10.25.0" }, "require-dev": { - "illuminate/collections": "^10.0|^11.0|^12.0", + "illuminate/collections": "^10.0|^11.0|^12.0|^13.0", "mockery/mockery": "^1.5", - "pestphp/pest": "^2.3|^3.4", - "phpstan/phpstan": "^1.11", - "phpstan/phpstan-mockery": "^1.1" + "pestphp/pest": "^2.3|^3.4|^4.0", + "phpstan/phpstan": "^1.12.28", + "phpstan/phpstan-mockery": "^1.1.3" }, "suggest": { "ext-pcntl": "Required for the spinner to be animated." @@ -2091,33 +1961,33 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.5" + "source": "https://github.com/laravel/prompts/tree/v0.3.24" }, - "time": "2025-02-11T13:34:40+00:00" + "time": "2026-08-20T12:55:36+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.4", + "version": "v2.0.16", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841" + "reference": "7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b352cf0534aa1ae6b4d825d1e762e35d43f8a841", - "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed", + "reference": "7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed", "shasum": "" }, "require": { "php": "^8.1" }, "require-dev": { - "illuminate/support": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", "nesbot/carbon": "^2.67|^3.0", - "pestphp/pest": "^2.36|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", "phpstan/phpstan": "^2.0", - "symfony/var-dumper": "^6.2.0|^7.0.0" + "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0" }, "type": "library", "extra": { @@ -2154,86 +2024,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2025-03-19T13:51:03+00:00" - }, - { - "name": "laravel/tinker", - "version": "v2.10.1", - "source": { - "type": "git", - "url": "https://github.com/laravel/tinker.git", - "reference": "22177cc71807d38f2810c6204d8f7183d88a57d3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/22177cc71807d38f2810c6204d8f7183d88a57d3", - "reference": "22177cc71807d38f2810c6204d8f7183d88a57d3", - "shasum": "" - }, - "require": { - "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", - "php": "^7.2.5|^8.0", - "psy/psysh": "^0.11.1|^0.12.0", - "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0" - }, - "require-dev": { - "mockery/mockery": "~1.3.3|^1.4.2", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^8.5.8|^9.3.3|^10.0" - }, - "suggest": { - "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0)." - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Laravel\\Tinker\\TinkerServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Laravel\\Tinker\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Powerful REPL for the Laravel framework.", - "keywords": [ - "REPL", - "Tinker", - "laravel", - "psysh" - ], - "support": { - "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.10.1" - }, - "time": "2025-01-27T14:24:01+00:00" + "time": "2026-08-18T20:28:54+00:00" }, { "name": "league/commonmark", - "version": "2.7.0", + "version": "2.10.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405" + "reference": "9d489ab67a02960fd8ffe624d93f751daf95439e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", - "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/9d489ab67a02960fd8ffe624d93f751daf95439e", + "reference": "9d489ab67a02960fd8ffe624d93f751daf95439e", "shasum": "" }, "require": { @@ -2255,14 +2059,14 @@ "github/gfm": "0.29.0", "michelf/php-markdown": "^1.4 || ^2.0", "nyholm/psr7": "^1.5", - "phpstan/phpstan": "^1.8.2", - "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "phpstan/phpstan": "^2.0.0", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0 || ^12.0.0 || ^13.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0 | ^7.0", - "symfony/process": "^5.4 | ^6.0 | ^7.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", + "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0", "unleashedtech/php-coding-standard": "^3.1.1", - "vimeo/psalm": "^4.24.0 || ^5.0.0" + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" }, "suggest": { "symfony/yaml": "v2.3+ required if using the Front Matter extension" @@ -2270,7 +2074,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.8-dev" + "dev-main": "2.11-dev" } }, "autoload": { @@ -2327,7 +2131,7 @@ "type": "tidelift" } ], - "time": "2025-05-05T12:20:28+00:00" + "time": "2026-09-07T13:44:26+00:00" }, { "name": "league/config", @@ -2413,16 +2217,16 @@ }, { "name": "league/flysystem", - "version": "3.29.1", + "version": "3.36.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319" + "reference": "f7fb152932f30072d573510cbd4dd657d6475b25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319", - "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f7fb152932f30072d573510cbd4dd657d6475b25", + "reference": "f7fb152932f30072d573510cbd4dd657d6475b25", "shasum": "" }, "require": { @@ -2446,13 +2250,13 @@ "composer/semver": "^3.0", "ext-fileinfo": "*", "ext-ftp": "*", - "ext-mongodb": "^1.3", + "ext-mongodb": "^1.3|^2", "ext-zip": "*", "friendsofphp/php-cs-fixer": "^3.5", "google/cloud-storage": "^1.23", "guzzlehttp/psr7": "^2.6", "microsoft/azure-storage-blob": "^1.1", - "mongodb/mongodb": "^1.2", + "mongodb/mongodb": "^1.2|^2", "phpseclib/phpseclib": "^3.0.36", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5.11|^10.0", @@ -2490,22 +2294,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.29.1" + "source": "https://github.com/thephpleague/flysystem/tree/3.36.0" }, - "time": "2024-10-08T08:58:34+00:00" + "time": "2026-09-02T08:00:27+00:00" }, { "name": "league/flysystem-local", - "version": "3.29.0", + "version": "3.35.3", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" + "reference": "a099b24dce160f3b2239043d13d47c4a1a214ea4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", - "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/a099b24dce160f3b2239043d13d47c4a1a214ea4", + "reference": "a099b24dce160f3b2239043d13d47c4a1a214ea4", "shasum": "" }, "require": { @@ -2539,22 +2343,22 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.35.3" }, - "time": "2024-08-09T21:24:39+00:00" + "time": "2026-08-12T13:29:21+00:00" }, { "name": "league/mime-type-detection", - "version": "1.16.0", + "version": "1.17.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/f5f47eff7c48ed1003069a2ca67f316fb4021c76", + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76", "shasum": "" }, "require": { @@ -2564,7 +2368,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0 || ^11.0 || ^12.0" }, "type": "library", "autoload": { @@ -2585,7 +2389,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.17.0" }, "funding": [ { @@ -2597,37 +2401,42 @@ "type": "tidelift" } ], - "time": "2024-09-21T08:32:55+00:00" + "time": "2026-07-09T11:49:27+00:00" }, { "name": "league/uri", - "version": "7.5.1", + "version": "7.8.1", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "81fb5145d2644324614cc532b28efd0215bda430" + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", - "reference": "81fb5145d2644324614cc532b28efd0215bda430", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/08cf38e3924d4f56238125547b5720496fac8fd4", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.5", - "php": "^8.1" + "league/uri-interfaces": "^7.8.1", + "php": "^8.1", + "psr/http-factory": "^1" }, "conflict": { "league/uri-schemes": "^1.0" }, "suggest": { "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", "ext-fileinfo": "to create Data URI from file contennts", "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", - "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", - "league/uri-components": "Needed to easily manipulate URI objects components", + "ext-uri": "to use the PHP native URI class", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -2655,6 +2464,7 @@ "description": "URI manipulation library", "homepage": "https://uri.thephpleague.com", "keywords": [ + "URN", "data-uri", "file-uri", "ftp", @@ -2667,9 +2477,11 @@ "psr-7", "query-string", "querystring", + "rfc2141", "rfc3986", "rfc3987", "rfc6570", + "rfc8141", "uri", "uri-template", "url", @@ -2679,7 +2491,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.5.1" + "source": "https://github.com/thephpleague/uri/tree/7.8.1" }, "funding": [ { @@ -2687,26 +2499,25 @@ "type": "github" } ], - "time": "2024-12-08T08:40:02+00:00" + "time": "2026-03-15T20:22:25+00:00" }, { "name": "league/uri-interfaces", - "version": "7.5.0", + "version": "7.8.1", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/85d5c77c5d6d3af6c54db4a78246364908f3c928", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928", "shasum": "" }, "require": { "ext-filter": "*", "php": "^8.1", - "psr/http-factory": "^1", "psr/http-message": "^1.1 || ^2.0" }, "suggest": { @@ -2714,6 +2525,7 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -2738,7 +2550,7 @@ "homepage": "https://nyamsprod.com" } ], - "description": "Common interfaces and classes for URI representation and interaction", + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", "homepage": "https://uri.thephpleague.com", "keywords": [ "data-uri", @@ -2763,7 +2575,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.1" }, "funding": [ { @@ -2771,33 +2583,32 @@ "type": "github" } ], - "time": "2024-12-08T08:18:47+00:00" + "time": "2026-03-08T20:05:35+00:00" }, { "name": "mockery/mockery", - "version": "1.6.12", + "version": "1.6.15", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + "reference": "967a801bd188989a5669bd280f252d51c0fdc9ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "url": "https://api.github.com/repos/mockery/mockery/zipball/967a801bd188989a5669bd280f252d51c0fdc9ee", + "reference": "967a801bd188989a5669bd280f252d51c0fdc9ee", "shasum": "" }, "require": { - "hamcrest/hamcrest-php": "^2.0.1", - "lib-pcre": ">=7.0", + "hamcrest/hamcrest-php": "^2.0 || ^3.0", "php": ">=7.3" }, "conflict": { "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.6.17", - "symplify/easy-coding-standard": "^12.1.14" + "phpunit/phpunit": "^9.6.36", + "symplify/easy-coding-standard": "^13.2.17" }, "type": "library", "autoload": { @@ -2854,20 +2665,20 @@ "security": "https://github.com/mockery/mockery/security/advisories", "source": "https://github.com/mockery/mockery" }, - "time": "2024-05-16T03:13:13+00:00" + "time": "2026-08-19T19:37:52+00:00" }, { "name": "monolog/monolog", - "version": "3.9.0", + "version": "3.12.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" + "reference": "72c534fc0ab181ef52d92a68382318631e301608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", - "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/72c534fc0ab181ef52d92a68382318631e301608", + "reference": "72c534fc0ab181ef52d92a68382318631e301608", "shasum": "" }, "require": { @@ -2885,7 +2696,7 @@ "graylog2/gelf-php": "^1.4.2 || ^2.0", "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/psr7": "^2.2", - "mongodb/mongodb": "^1.8", + "mongodb/mongodb": "^1.8 || ^2.0", "php-amqplib/php-amqplib": "~2.4 || ^3", "php-console/php-console": "^3.1.8", "phpstan/phpstan": "^2", @@ -2893,6 +2704,7 @@ "phpstan/phpstan-strict-rules": "^2", "phpunit/phpunit": "^10.5.17 || ^11.0.7", "predis/predis": "^1.1 || ^2", + "psr/clock": "^1.0", "rollbar/rollbar": "^4.0", "ruflin/elastica": "^7 || ^8", "symfony/mailer": "^5.4 || ^6", @@ -2911,6 +2723,7 @@ "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "psr/clock": "Required to pass a clock to the Logger and control the timestamp of log records", "rollbar/rollbar": "Allow sending log messages to Rollbar", "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, @@ -2945,7 +2758,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.9.0" + "source": "https://github.com/Seldaek/monolog/tree/3.12.0" }, "funding": [ { @@ -2957,24 +2770,24 @@ "type": "tidelift" } ], - "time": "2025-03-24T10:02:05+00:00" + "time": "2026-09-09T08:34:20+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.13.1", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.0" }, "conflict": { "doctrine/collections": "<1.6.8", @@ -3009,28 +2822,28 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "url": "https://github.com/mnapoli", + "type": "github" } ], - "time": "2025-04-29T12:36:36+00:00" + "time": "2026-08-11T10:17:44+00:00" }, { "name": "nesbot/carbon", - "version": "3.9.1", + "version": "3.13.2", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "ced71f79398ece168e24f7f7710462f462310d4d" + "reference": "a1c54919f5fff9800cd03c32bd01defd5a4061cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ced71f79398ece168e24f7f7710462f462310d4d", - "reference": "ced71f79398ece168e24f7f7710462f462310d4d", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/a1c54919f5fff9800cd03c32bd01defd5a4061cb", + "reference": "a1c54919f5fff9800cd03c32bd01defd5a4061cb", "shasum": "" }, "require": { @@ -3038,9 +2851,9 @@ "ext-json": "*", "php": "^8.1", "psr/clock": "^1.0", - "symfony/clock": "^6.3 || ^7.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", "symfony/polyfill-mbstring": "^1.0", - "symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0" + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" }, "provide": { "psr/clock-implementation": "1.0" @@ -3048,14 +2861,13 @@ "require-dev": { "doctrine/dbal": "^3.6.3 || ^4.0", "doctrine/orm": "^2.15.2 || ^3.0", - "friendsofphp/php-cs-fixer": "^3.57.2", + "friendsofphp/php-cs-fixer": "^v3.87.1", "kylekatarnls/multi-tester": "^2.5.3", - "ondrejmirtes/better-reflection": "^6.25.0.4", "phpmd/phpmd": "^2.15.0", - "phpstan/extension-installer": "^1.3.1", - "phpstan/phpstan": "^1.11.2", - "phpunit/phpunit": "^10.5.20", - "squizlabs/php_codesniffer": "^3.9.0" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" }, "bin": [ "bin/carbon" @@ -3098,14 +2910,14 @@ } ], "description": "An API extension for DateTime that supports 281 different languages.", - "homepage": "https://carbon.nesbot.com", + "homepage": "https://carbonphp.github.io/carbon/", "keywords": [ "date", "datetime", "time" ], "support": { - "docs": "https://carbon.nesbot.com/docs", + "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", "issues": "https://github.com/CarbonPHP/carbon/issues", "source": "https://github.com/CarbonPHP/carbon" }, @@ -3123,29 +2935,31 @@ "type": "tidelift" } ], - "time": "2025-05-01T19:51:51+00:00" + "time": "2026-08-08T11:40:35+00:00" }, { "name": "nette/schema", - "version": "v1.3.2", + "version": "v1.3.6", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "da801d52f0354f70a638673c4a0f04e16529431d" + "reference": "c54350438cd6914616f790a49cb424605f421562" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", - "reference": "da801d52f0354f70a638673c4a0f04e16529431d", + "url": "https://api.github.com/repos/nette/schema/zipball/c54350438cd6914616f790a49cb424605f421562", + "reference": "c54350438cd6914616f790a49cb424605f421562", "shasum": "" }, "require": { "nette/utils": "^4.0", - "php": "8.1 - 8.4" + "php": "8.1 - 8.5" }, "require-dev": { - "nette/tester": "^2.5.2", - "phpstan/phpstan-nette": "^1.0", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.6", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.39@stable", "tracy/tracy": "^2.8" }, "type": "library", @@ -3155,6 +2969,9 @@ } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -3183,40 +3000,42 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.2" + "source": "https://github.com/nette/schema/tree/v1.3.6" }, - "time": "2024-10-06T23:10:23+00:00" + "time": "2026-08-16T21:58:41+00:00" }, { "name": "nette/utils", - "version": "v4.0.7", + "version": "v4.1.5", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2" + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2", - "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2", + "url": "https://api.github.com/repos/nette/utils/zipball/b043439dbdf954e6c28b5ea7e34b0100f83165e0", + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0", "shasum": "" }, "require": { - "php": "8.0 - 8.4" + "php": "8.2 - 8.5" }, "conflict": { "nette/finder": "<3", "nette/schema": "<1.2.2" }, "require-dev": { - "jetbrains/phpstorm-attributes": "dev-master", + "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", "nette/tester": "^2.5", - "phpstan/phpstan": "^1.0", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", "tracy/tracy": "^2.9" }, "suggest": { "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-iconv": "to use Strings::chr(), ord() and reverse()", "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", "ext-json": "to use Nette\\Utils\\Json", "ext-mbstring": "to use Strings::lower() etc...", @@ -3225,10 +3044,13 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -3269,26 +3091,25 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.7" + "source": "https://github.com/nette/utils/tree/v4.1.5" }, - "time": "2025-06-03T04:55:08+00:00" + "time": "2026-07-17T23:02:45+00:00" }, { "name": "nikic/php-parser", - "version": "v5.5.0", + "version": "v5.8.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-json": "*", "ext-tokenizer": "*", "php": ">=7.4" @@ -3303,7 +3124,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -3327,585 +3148,56 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" }, - "time": "2025-05-31T08:24:38+00:00" + "time": "2026-07-04T14:30:18+00:00" }, { - "name": "nunomaduro/collision", - "version": "v8.8.1", + "name": "nunomaduro/termwind", + "version": "v2.4.0", "source": { "type": "git", - "url": "https://github.com/nunomaduro/collision.git", - "reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5" + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/44ccb82e3e21efb5446748d2a3c81a030ac22bd5", - "reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8", "shasum": "" }, "require": { - "filp/whoops": "^2.18.1", - "nunomaduro/termwind": "^2.3.1", - "php": "^8.2.0", - "symfony/console": "^7.3.0" - }, - "conflict": { - "laravel/framework": "<11.44.2 || >=13.0.0", - "phpunit/phpunit": "<11.5.15 || >=13.0.0" + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.4.4 || ^8.0.4" }, "require-dev": { - "brianium/paratest": "^7.8.3", - "larastan/larastan": "^3.4.2", - "laravel/framework": "^11.44.2 || ^12.18", - "laravel/pint": "^1.22.1", - "laravel/sail": "^1.43.1", - "laravel/sanctum": "^4.1.1", - "laravel/tinker": "^2.10.1", - "orchestra/testbench-core": "^9.12.0 || ^10.4", - "pestphp/pest": "^3.8.2", - "sebastian/environment": "^7.2.1 || ^8.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" - ] - }, - "branch-alias": { - "dev-8.x": "8.x-dev" - } - }, - "autoload": { - "files": [ - "./src/Adapters/Phpunit/Autoload.php" - ], - "psr-4": { - "NunoMaduro\\Collision\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" - } - ], - "description": "Cli error handling for console/command-line PHP applications.", - "keywords": [ - "artisan", - "cli", - "command-line", - "console", - "dev", - "error", - "handling", - "laravel", - "laravel-zero", - "php", - "symfony" - ], - "support": { - "issues": "https://github.com/nunomaduro/collision/issues", - "source": "https://github.com/nunomaduro/collision" - }, - "funding": [ - { - "url": "https://www.paypal.com/paypalme/enunomaduro", - "type": "custom" - }, - { - "url": "https://github.com/nunomaduro", - "type": "github" - }, - { - "url": "https://www.patreon.com/nunomaduro", - "type": "patreon" - } - ], - "time": "2025-06-11T01:04:21+00:00" - }, - { - "name": "nunomaduro/termwind", - "version": "v2.3.1", - "source": { - "type": "git", - "url": "https://github.com/nunomaduro/termwind.git", - "reference": "dfa08f390e509967a15c22493dc0bac5733d9123" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dfa08f390e509967a15c22493dc0bac5733d9123", - "reference": "dfa08f390e509967a15c22493dc0bac5733d9123", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": "^8.2", - "symfony/console": "^7.2.6" - }, - "require-dev": { - "illuminate/console": "^11.44.7", - "laravel/pint": "^1.22.0", + "illuminate/console": "^11.47.0", + "laravel/pint": "^1.27.1", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0 || ^3.8.2", - "phpstan/phpstan": "^1.12.25", - "phpstan/phpstan-strict-rules": "^1.6.2", - "symfony/var-dumper": "^7.2.6", - "thecodingmachine/phpstan-strict-rules": "^1.0.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Termwind\\Laravel\\TermwindServiceProvider" - ] - }, - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, - "autoload": { - "files": [ - "src/Functions.php" - ], - "psr-4": { - "Termwind\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" - } - ], - "description": "Its like Tailwind CSS, but for the console.", - "keywords": [ - "cli", - "console", - "css", - "package", - "php", - "style" - ], - "support": { - "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.1" - }, - "funding": [ - { - "url": "https://www.paypal.com/paypalme/enunomaduro", - "type": "custom" - }, - { - "url": "https://github.com/nunomaduro", - "type": "github" - }, - { - "url": "https://github.com/xiCO2k", - "type": "github" - } - ], - "time": "2025-05-08T08:14:37+00:00" - }, - { - "name": "orchestra/canvas", - "version": "v9.2.2", - "source": { - "type": "git", - "url": "https://github.com/orchestral/canvas.git", - "reference": "002d948834c0899e511f5ac0381669363d7881e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/orchestral/canvas/zipball/002d948834c0899e511f5ac0381669363d7881e5", - "reference": "002d948834c0899e511f5ac0381669363d7881e5", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.2", - "composer/semver": "^3.0", - "illuminate/console": "^11.43.0", - "illuminate/database": "^11.43.0", - "illuminate/filesystem": "^11.43.0", - "illuminate/support": "^11.43.0", - "orchestra/canvas-core": "^9.1.1", - "orchestra/sidekick": "^1.0.2", - "orchestra/testbench-core": "^9.11.0", - "php": "^8.2", - "symfony/polyfill-php83": "^1.31", - "symfony/yaml": "^7.0.3" - }, - "require-dev": { - "laravel/framework": "^11.43.0", - "laravel/pint": "^1.21", - "mockery/mockery": "^1.6.10", - "phpstan/phpstan": "^2.1", - "phpunit/phpunit": "^11.5.7", - "spatie/laravel-ray": "^1.39.1" - }, - "bin": [ - "canvas" - ], - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Orchestra\\Canvas\\LaravelServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Orchestra\\Canvas\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - }, - { - "name": "Mior Muhammad Zaki", - "email": "crynobone@gmail.com" - } - ], - "description": "Code Generators for Laravel Applications and Packages", - "support": { - "issues": "https://github.com/orchestral/canvas/issues", - "source": "https://github.com/orchestral/canvas/tree/v9.2.2" - }, - "time": "2025-02-19T04:27:08+00:00" - }, - { - "name": "orchestra/canvas-core", - "version": "v9.1.1", - "source": { - "type": "git", - "url": "https://github.com/orchestral/canvas-core.git", - "reference": "a8ebfa6c2e50f8c6597c489b4dfaf9af6789f62a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/orchestral/canvas-core/zipball/a8ebfa6c2e50f8c6597c489b4dfaf9af6789f62a", - "reference": "a8ebfa6c2e50f8c6597c489b4dfaf9af6789f62a", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.2", - "composer/semver": "^3.0", - "illuminate/console": "^11.43.0", - "illuminate/support": "^11.43.0", - "orchestra/sidekick": "^1.0.2", - "php": "^8.2", - "symfony/polyfill-php83": "^1.31" - }, - "require-dev": { - "laravel/framework": "^11.43.0", - "laravel/pint": "^1.20", - "mockery/mockery": "^1.6.10", - "orchestra/testbench-core": "^9.11.0", - "phpstan/phpstan": "^2.1", - "phpunit/phpunit": "^11.5.7", - "symfony/yaml": "^7.0.3" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Orchestra\\Canvas\\Core\\LaravelServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Orchestra\\Canvas\\Core\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - }, - { - "name": "Mior Muhammad Zaki", - "email": "crynobone@gmail.com" - } - ], - "description": "Code Generators Builder for Laravel Applications and Packages", - "support": { - "issues": "https://github.com/orchestral/canvas/issues", - "source": "https://github.com/orchestral/canvas-core/tree/v9.1.1" - }, - "time": "2025-02-19T04:14:36+00:00" - }, - { - "name": "orchestra/sidekick", - "version": "v1.2.12", - "source": { - "type": "git", - "url": "https://github.com/orchestral/sidekick.git", - "reference": "c182e9f91d7aa68417eae0585e004fbbc7beac26" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/orchestral/sidekick/zipball/c182e9f91d7aa68417eae0585e004fbbc7beac26", - "reference": "c182e9f91d7aa68417eae0585e004fbbc7beac26", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.2", - "php": "^8.1", - "symfony/polyfill-php83": "^1.32" - }, - "require-dev": { - "fakerphp/faker": "^1.21", - "laravel/framework": "^10.48.29|^11.44.7|^12.1.1|^13.0", - "laravel/pint": "^1.4", - "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^8.37.0|^9.14.0|^10.0|^11.0", - "phpstan/phpstan": "^2.1.14", - "phpunit/phpunit": "^10.0|^11.0|^12.0", - "symfony/process": "^6.0|^7.0" - }, - "type": "library", - "autoload": { - "files": [ - "src/Eloquent/functions.php", - "src/Http/functions.php", - "src/functions.php" - ], - "psr-4": { - "Orchestra\\Sidekick\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mior Muhammad Zaki", - "email": "crynobone@gmail.com" - } - ], - "description": "Packages Toolkit Utilities and Helpers for Laravel", - "support": { - "issues": "https://github.com/orchestral/sidekick/issues", - "source": "https://github.com/orchestral/sidekick/tree/v1.2.12" - }, - "time": "2025-06-08T03:58:04+00:00" - }, - { - "name": "orchestra/testbench", - "version": "v9.14.0", - "source": { - "type": "git", - "url": "https://github.com/orchestral/testbench.git", - "reference": "86d9a613acbe246f09e5c3b318821ba8b242ea4f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench/zipball/86d9a613acbe246f09e5c3b318821ba8b242ea4f", - "reference": "86d9a613acbe246f09e5c3b318821ba8b242ea4f", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.2", - "fakerphp/faker": "^1.23", - "laravel/framework": "^11.44.7", - "mockery/mockery": "^1.6.10", - "orchestra/testbench-core": "^9.14.0", - "orchestra/workbench": "^9.13.5", - "php": "^8.2", - "phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1", - "symfony/process": "^7.0.3", - "symfony/yaml": "^7.0.3", - "vlucas/phpdotenv": "^5.6.1" - }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mior Muhammad Zaki", - "email": "crynobone@gmail.com", - "homepage": "https://github.com/crynobone" - } - ], - "description": "Laravel Testing Helper for Packages Development", - "homepage": "https://packages.tools/testbench/", - "keywords": [ - "BDD", - "TDD", - "dev", - "laravel", - "laravel-packages", - "testing" - ], - "support": { - "issues": "https://github.com/orchestral/testbench/issues", - "source": "https://github.com/orchestral/testbench/tree/v9.14.0" - }, - "time": "2025-05-12T06:29:13+00:00" - }, - { - "name": "orchestra/testbench-core", - "version": "v9.15.0", - "source": { - "type": "git", - "url": "https://github.com/orchestral/testbench-core.git", - "reference": "4f13b9543feba1b549c4e7cc19cd7657be4c20c8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/4f13b9543feba1b549c4e7cc19cd7657be4c20c8", - "reference": "4f13b9543feba1b549c4e7cc19cd7657be4c20c8", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.2", - "orchestra/sidekick": "~1.1.14|^1.2.10", - "php": "^8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-php83": "^1.32" - }, - "conflict": { - "brianium/paratest": "<7.3.0|>=8.0.0", - "laravel/framework": "<11.44.7|>=12.0.0", - "laravel/serializable-closure": "<1.3.0|>=2.0.0 <2.0.3|>=3.0.0", - "nunomaduro/collision": "<8.0.0|>=9.0.0", - "orchestra/testbench-dusk": "<9.10.0|>=10.0.0", - "phpunit/phpunit": "<10.5.35|>=11.0.0 <11.3.6|>=12.0.0 <12.0.1|>=12.2.0" - }, - "require-dev": { - "fakerphp/faker": "^1.24", - "laravel/framework": "^11.44.7", - "laravel/pint": "^1.22", - "laravel/serializable-closure": "^1.3|^2.0.4", - "mockery/mockery": "^1.6.10", - "phpstan/phpstan": "^2.1.14", - "phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1", - "spatie/laravel-ray": "^1.40.2", - "symfony/process": "^7.0.3", - "symfony/yaml": "^7.0.3", - "vlucas/phpdotenv": "^5.6.1" - }, - "suggest": { - "brianium/paratest": "Allow using parallel testing (^7.3).", - "ext-pcntl": "Required to use all features of the console signal trapping.", - "fakerphp/faker": "Allow using Faker for testing (^1.23).", - "laravel/framework": "Required for testing (^11.44.2).", - "mockery/mockery": "Allow using Mockery for testing (^1.6).", - "nunomaduro/collision": "Allow using Laravel style tests output and parallel testing (^8.0).", - "orchestra/testbench-dusk": "Allow using Laravel Dusk for testing (^9.10).", - "phpunit/phpunit": "Allow using PHPUnit for testing (^10.5.35|^11.3.6|^12.0.1).", - "symfony/process": "Required to use Orchestra\\Testbench\\remote function (^7.0).", - "symfony/yaml": "Required for Testbench CLI (^7.0).", - "vlucas/phpdotenv": "Required for Testbench CLI (^5.6.1)." - }, - "bin": [ - "testbench" - ], - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Orchestra\\Testbench\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mior Muhammad Zaki", - "email": "crynobone@gmail.com", - "homepage": "https://github.com/crynobone" - } - ], - "description": "Testing Helper for Laravel Development", - "homepage": "https://packages.tools/testbench", - "keywords": [ - "BDD", - "TDD", - "dev", - "laravel", - "laravel-packages", - "testing" - ], - "support": { - "issues": "https://github.com/orchestral/testbench/issues", - "source": "https://github.com/orchestral/testbench-core" - }, - "time": "2025-06-08T04:26:48+00:00" - }, - { - "name": "orchestra/workbench", - "version": "v9.13.5", - "source": { - "type": "git", - "url": "https://github.com/orchestral/workbench.git", - "reference": "1da2ea95089ed3516bda6f8e9cd57c81290004bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/orchestral/workbench/zipball/1da2ea95089ed3516bda6f8e9cd57c81290004bf", - "reference": "1da2ea95089ed3516bda6f8e9cd57c81290004bf", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.2", - "fakerphp/faker": "^1.23", - "laravel/framework": "^11.44.2", - "laravel/pail": "^1.2", - "laravel/tinker": "^2.9", - "nunomaduro/collision": "^8.0", - "orchestra/canvas": "^9.2.2", - "orchestra/sidekick": "^1.1.0", - "orchestra/testbench-core": "^9.12.0", - "php": "^8.2", - "symfony/polyfill-php83": "^1.31", - "symfony/polyfill-php84": "^1.31", - "symfony/process": "^7.0.3", - "symfony/yaml": "^7.0.3" - }, - "require-dev": { - "laravel/pint": "^1.21", - "mockery/mockery": "^1.6.10", - "phpstan/phpstan": "^2.1", - "phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1", - "spatie/laravel-ray": "^1.39.1" - }, - "suggest": { - "ext-pcntl": "Required to use all features of the console signal trapping." + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2", + "phpstan/phpstan": "^1.12.32", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.3.5 || ^8.0.4", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, "autoload": { + "files": [ + "src/Functions.php" + ], "psr-4": { - "Orchestra\\Workbench\\": "src/" + "Termwind\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3914,22 +3206,38 @@ ], "authors": [ { - "name": "Mior Muhammad Zaki", - "email": "crynobone@gmail.com" + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" } ], - "description": "Workbench Companion for Laravel Packages Development", + "description": "It's like Tailwind CSS, but for the console.", "keywords": [ - "dev", - "laravel", - "laravel-packages", - "testing" + "cli", + "console", + "css", + "package", + "php", + "style" ], "support": { - "issues": "https://github.com/orchestral/workbench/issues", - "source": "https://github.com/orchestral/workbench/tree/v9.13.5" + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v2.4.0" }, - "time": "2025-04-06T11:06:19+00:00" + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2026-02-16T23:10:27+00:00" }, { "name": "phar-io/manifest", @@ -4051,16 +3359,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.3", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/67b192b6a42ec03944b972d6e633ddec78ad2c6d", + "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d", "shasum": "" }, "require": { @@ -4068,7 +3376,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.54 || ^9.6.36 || ^10.5.64 || ^11.5.56 || ^12.5.33" }, "type": "library", "extra": { @@ -4110,7 +3418,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + "source": "https://github.com/schmittjoh/php-option/tree/1.10.0" }, "funding": [ { @@ -4122,39 +3430,39 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:41:07+00:00" + "time": "2026-08-24T00:54:40+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "11.0.9", + "version": "11.0.12", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7" + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/14d63fbcca18457e49c6f8bebaa91a87e8e188d7", - "reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.4.0", + "nikic/php-parser": "^5.7.0", "php": ">=8.2", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-text-template": "^4.0.1", "sebastian/code-unit-reverse-lookup": "^4.0.1", "sebastian/complexity": "^4.0.1", - "sebastian/environment": "^7.2.0", + "sebastian/environment": "^7.2.1", "sebastian/lines-of-code": "^3.0.1", "sebastian/version": "^5.0.2", - "theseer/tokenizer": "^1.2.3" + "theseer/tokenizer": "^1.3.1" }, "require-dev": { - "phpunit/phpunit": "^11.5.2" + "phpunit/phpunit": "^11.5.46" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -4192,40 +3500,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.9" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2025-02-25T13:26:39+00:00" + "time": "2025-12-24T07:01:01+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "5.1.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -4253,15 +3573,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" } ], - "time": "2024-08-27T05:02:59+00:00" + "time": "2026-02-02T13:52:54+00:00" }, { "name": "phpunit/php-invoker", @@ -4449,43 +3781,44 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.22", + "version": "11.5.56", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "4cd72faaa8f811e4cc63040cba167757660a5538" + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4cd72faaa8f811e4cc63040cba167757660a5538", - "reference": "4cd72faaa8f811e4cc63040cba167757660a5538", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f83edffa6967c3db468d48a695ec7bcb02e9256", + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256", "shasum": "" }, "require": { "ext-dom": "*", + "ext-filter": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", - "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.1", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.9", - "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.1", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", "sebastian/code-unit": "^3.0.3", - "sebastian/comparator": "^6.3.1", + "sebastian/comparator": "^6.3.3", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.1", - "sebastian/exporter": "^6.3.0", + "sebastian/exporter": "^6.3.2", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", - "sebastian/type": "^5.1.2", + "sebastian/recursion-context": "^6.0.3", + "sebastian/type": "^5.1.3", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" }, @@ -4530,31 +3863,15 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.22" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.56" }, "funding": [ { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" + "url": "https://phpunit.de/sponsoring.html", + "type": "other" } ], - "time": "2025-06-06T02:48:05+00:00" + "time": "2026-07-06T14:52:39+00:00" }, { "name": "psr/clock", @@ -4968,129 +4285,6 @@ }, "time": "2021-10-29T13:26:27+00:00" }, - { - "name": "psy/psysh", - "version": "v0.12.8", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/psysh.git", - "reference": "85057ceedee50c49d4f6ecaff73ee96adb3b3625" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/85057ceedee50c49d4f6ecaff73ee96adb3b3625", - "reference": "85057ceedee50c49d4f6ecaff73ee96adb3b3625", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "nikic/php-parser": "^5.0 || ^4.0", - "php": "^8.0 || ^7.4", - "symfony/console": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", - "symfony/var-dumper": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" - }, - "conflict": { - "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.2" - }, - "suggest": { - "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", - "ext-pdo-sqlite": "The doc command requires SQLite to work.", - "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." - }, - "bin": [ - "bin/psysh" - ], - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": false, - "forward-command": false - }, - "branch-alias": { - "dev-main": "0.12.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Psy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "An interactive shell for modern PHP.", - "homepage": "http://psysh.org", - "keywords": [ - "REPL", - "console", - "interactive", - "shell" - ], - "support": { - "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.8" - }, - "time": "2025-03-16T03:05:19+00:00" - }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" - }, - "time": "2019-03-08T08:55:37+00:00" - }, { "name": "ramsey/collection", "version": "2.1.1", @@ -5169,21 +4363,20 @@ }, { "name": "ramsey/uuid", - "version": "4.8.1", + "version": "4.9.3", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28" + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", - "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/1df15849d00943a67d677dc9cfd80795f038c9f8", + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", - "ext-json": "*", + "brick/math": ">=0.8.16 <=0.18", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -5242,9 +4435,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.8.1" + "source": "https://github.com/ramsey/uuid/tree/4.9.3" }, - "time": "2025-06-01T06:28:46+00:00" + "time": "2026-06-18T03:57:49+00:00" }, { "name": "react/cache", @@ -5320,16 +4513,16 @@ }, { "name": "react/child-process", - "version": "v0.6.6", + "version": "v0.6.7", "source": { "type": "git", "url": "https://github.com/reactphp/child-process.git", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3", "shasum": "" }, "require": { @@ -5383,7 +4576,7 @@ ], "support": { "issues": "https://github.com/reactphp/child-process/issues", - "source": "https://github.com/reactphp/child-process/tree/v0.6.6" + "source": "https://github.com/reactphp/child-process/tree/v0.6.7" }, "funding": [ { @@ -5391,20 +4584,20 @@ "type": "open_collective" } ], - "time": "2025-01-01T16:37:48+00:00" + "time": "2025-12-23T15:25:20+00:00" }, { "name": "react/dns", - "version": "v1.13.0", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/reactphp/dns.git", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", "shasum": "" }, "require": { @@ -5459,7 +4652,7 @@ ], "support": { "issues": "https://github.com/reactphp/dns/issues", - "source": "https://github.com/reactphp/dns/tree/v1.13.0" + "source": "https://github.com/reactphp/dns/tree/v1.14.0" }, "funding": [ { @@ -5467,20 +4660,20 @@ "type": "open_collective" } ], - "time": "2024-06-13T14:18:03+00:00" + "time": "2025-11-18T19:34:28+00:00" }, { "name": "react/event-loop", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/reactphp/event-loop.git", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", "shasum": "" }, "require": { @@ -5531,7 +4724,7 @@ ], "support": { "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" }, "funding": [ { @@ -5539,27 +4732,27 @@ "type": "open_collective" } ], - "time": "2023-11-13T13:48:05+00:00" + "time": "2025-11-17T20:46:25+00:00" }, { "name": "react/promise", - "version": "v3.2.0", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", "shasum": "" }, "require": { "php": ">=7.1.0" }, "require-dev": { - "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpstan/phpstan": "1.12.28 || 1.4.10", "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", @@ -5604,7 +4797,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.2.0" + "source": "https://github.com/reactphp/promise/tree/v3.3.0" }, "funding": [ { @@ -5612,20 +4805,20 @@ "type": "open_collective" } ], - "time": "2024-05-24T10:39:05+00:00" + "time": "2025-08-19T18:57:03+00:00" }, { "name": "react/socket", - "version": "v1.16.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/reactphp/socket.git", - "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", - "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", "shasum": "" }, "require": { @@ -5684,7 +4877,7 @@ ], "support": { "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.16.0" + "source": "https://github.com/reactphp/socket/tree/v1.17.0" }, "funding": [ { @@ -5692,7 +4885,7 @@ "type": "open_collective" } ], - "time": "2024-07-26T10:38:09+00:00" + "time": "2025-11-19T20:47:34+00:00" }, { "name": "react/stream", @@ -5944,16 +5137,16 @@ }, { "name": "sebastian/comparator", - "version": "6.3.1", + "version": "6.3.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959" + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959", - "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", "shasum": "" }, "require": { @@ -6012,15 +5205,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2025-03-07T06:57:01+00:00" + "time": "2026-01-24T09:26:40+00:00" }, { "name": "sebastian/complexity", @@ -6225,16 +5430,16 @@ }, { "name": "sebastian/exporter", - "version": "6.3.0", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", - "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", "shasum": "" }, "require": { @@ -6248,7 +5453,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -6291,15 +5496,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-12-05T09:17:50+00:00" + "time": "2025-09-24T06:12:51+00:00" }, { "name": "sebastian/global-state", @@ -6537,23 +5754,23 @@ }, { "name": "sebastian/recursion-context", - "version": "6.0.2", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", - "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { @@ -6589,28 +5806,40 @@ "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2024-07-03T05:10:34+00:00" + "time": "2025-08-13T04:42:22+00:00" }, { "name": "sebastian/type", - "version": "5.1.2", + "version": "5.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e" + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", - "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", "shasum": "" }, "require": { @@ -6646,15 +5875,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.1.2" + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2025-03-18T13:35:50+00:00" + "time": "2025-08-09T06:55:48+00:00" }, { "name": "sebastian/version", @@ -6712,16 +5953,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.13.0", + "version": "3.13.6", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "65ff2489553b83b4597e89c3b8b721487011d186" + "reference": "4c378e1a528ea066890fc2397cbdd2f94eb2fc91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/65ff2489553b83b4597e89c3b8b721487011d186", - "reference": "65ff2489553b83b4597e89c3b8b721487011d186", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/4c378e1a528ea066890fc2397cbdd2f94eb2fc91", + "reference": "4c378e1a528ea066890fc2397cbdd2f94eb2fc91", "shasum": "" }, "require": { @@ -6738,11 +5979,6 @@ "bin/phpcs" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" @@ -6792,7 +6028,7 @@ "type": "thanks_dev" } ], - "time": "2025-05-11T03:36:00+00:00" + "time": "2026-08-06T00:17:32+00:00" }, { "name": "staabm/side-effects-detector", @@ -6848,16 +6084,16 @@ }, { "name": "symfony/clock", - "version": "v7.3.0", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" + "reference": "674fa3b98e21531dd040e613479f5f6fa8f32111" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", - "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "url": "https://api.github.com/repos/symfony/clock/zipball/674fa3b98e21531dd040e613479f5f6fa8f32111", + "reference": "674fa3b98e21531dd040e613479f5f6fa8f32111", "shasum": "" }, "require": { @@ -6902,7 +6138,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.3.0" + "source": "https://github.com/symfony/clock/tree/v7.4.8" }, "funding": [ { @@ -6913,25 +6149,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/console", - "version": "v7.3.0", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44" + "reference": "23d6f88a29f6d0eac45bd77d70307adf83ba7ab0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/66c1440edf6f339fd82ed6c7caa76cb006211b44", - "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44", + "url": "https://api.github.com/repos/symfony/console/zipball/23d6f88a29f6d0eac45bd77d70307adf83ba7ab0", + "reference": "23d6f88a29f6d0eac45bd77d70307adf83ba7ab0", "shasum": "" }, "require": { @@ -6939,7 +6179,7 @@ "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2" + "symfony/string": "^7.2|^8.0" }, "conflict": { "symfony/dependency-injection": "<6.4", @@ -6953,16 +6193,16 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -6996,7 +6236,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.0" + "source": "https://github.com/symfony/console/tree/v7.4.18" }, "funding": [ { @@ -7007,25 +6247,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-24T10:34:04+00:00" + "time": "2026-08-25T14:18:37+00:00" }, { "name": "symfony/css-selector", - "version": "v7.3.0", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" + "reference": "fecf40067fc8d8880ea87b8ac227600b9aadd1d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", - "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/fecf40067fc8d8880ea87b8ac227600b9aadd1d0", + "reference": "fecf40067fc8d8880ea87b8ac227600b9aadd1d0", "shasum": "" }, "require": { @@ -7061,7 +6305,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.3.0" + "source": "https://github.com/symfony/css-selector/tree/v7.4.18" }, "funding": [ { @@ -7072,25 +6316,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-08-23T10:03:40+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { @@ -7103,7 +6351,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -7128,7 +6376,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -7139,41 +6387,46 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/error-handler", - "version": "v7.3.0", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "cf68d225bc43629de4ff54778029aee6dc191b83" + "reference": "8373921e231e190a88e2ad526951bbaa791576fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/cf68d225bc43629de4ff54778029aee6dc191b83", - "reference": "cf68d225bc43629de4ff54778029aee6dc191b83", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8373921e231e190a88e2ad526951bbaa791576fa", + "reference": "8373921e231e190a88e2ad526951bbaa791576fa", "shasum": "" }, "require": { "php": ">=8.2", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "conflict": { "symfony/deprecation-contracts": "<2.5", "symfony/http-kernel": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0|^8.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", "symfony/webpack-encore-bundle": "^1.0|^2.0" }, "bin": [ @@ -7205,7 +6458,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.3.0" + "source": "https://github.com/symfony/error-handler/tree/v7.4.17" }, "funding": [ { @@ -7216,25 +6469,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-29T07:19:49+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.3.0", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" + "reference": "d269974ee93c61d03620ffee358355bfdb471d66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d269974ee93c61d03620ffee358355bfdb471d66", + "reference": "d269974ee93c61d03620ffee358355bfdb471d66", "shasum": "" }, "require": { @@ -7251,13 +6508,14 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/error-handler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0" + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -7285,7 +6543,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.17" }, "funding": [ { @@ -7296,25 +6554,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-22T09:11:45+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", "shasum": "" }, "require": { @@ -7328,7 +6590,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -7361,7 +6623,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" }, "funding": [ { @@ -7372,25 +6634,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/filesystem", - "version": "v7.3.0", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + "reference": "90d412aa5277c6819db39e7605aa46b1019e3232" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/90d412aa5277c6819db39e7605aa46b1019e3232", + "reference": "90d412aa5277c6819db39e7605aa46b1019e3232", "shasum": "" }, "require": { @@ -7399,7 +6665,7 @@ "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^6.4|^7.0" + "symfony/process": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -7427,7 +6693,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.0" + "source": "https://github.com/symfony/filesystem/tree/v7.4.18" }, "funding": [ { @@ -7438,32 +6704,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-25T15:15:23+00:00" + "time": "2026-08-23T10:03:40+00:00" }, { "name": "symfony/finder", - "version": "v7.3.0", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" + "reference": "5ce28827081f6d1f0c32eaf3882750f19cb5bbe6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", + "url": "https://api.github.com/repos/symfony/finder/zipball/5ce28827081f6d1f0c32eaf3882750f19cb5bbe6", + "reference": "5ce28827081f6d1f0c32eaf3882750f19cb5bbe6", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0" + "symfony/filesystem": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -7491,7 +6761,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.0" + "source": "https://github.com/symfony/finder/tree/v7.4.17" }, "funding": [ { @@ -7499,7 +6769,11 @@ "type": "custom" }, { - "url": "https://github.com/fabpot", + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -7507,27 +6781,26 @@ "type": "tidelift" } ], - "time": "2024-12-30T19:00:26+00:00" + "time": "2026-08-21T12:09:28+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.3.7", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "db488a62f98f7a81d5746f05eea63a74e55bb7c4" + "reference": "d070b716a32fbe3bf04204db0f58ace73b86d133" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/db488a62f98f7a81d5746f05eea63a74e55bb7c4", - "reference": "db488a62f98f7a81d5746f05eea63a74e55bb7c4", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d070b716a32fbe3bf04204db0f58ace73b86d133", + "reference": "d070b716a32fbe3bf04204db0f58ace73b86d133", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php83": "^1.27" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" }, "conflict": { "doctrine/dbal": "<3.6", @@ -7536,13 +6809,13 @@ "require-dev": { "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4.12|^7.1.5", - "symfony/clock": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0" + "symfony/cache": "^6.4.12|^7.1.5|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -7570,7 +6843,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.7" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.18" }, "funding": [ { @@ -7590,29 +6863,29 @@ "type": "tidelift" } ], - "time": "2025-11-08T16:41:12+00:00" + "time": "2026-08-30T20:10:52+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.0", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "ac7b8e163e8c83dce3abcc055a502d4486051a9f" + "reference": "275d2d2d24530f2a0eaf17704a3a93860a036351" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ac7b8e163e8c83dce3abcc055a502d4486051a9f", - "reference": "ac7b8e163e8c83dce3abcc055a502d4486051a9f", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/275d2d2d24530f2a0eaf17704a3a93860a036351", + "reference": "275d2d2d24530f2a0eaf17704a3a93860a036351", "shasum": "" }, "require": { "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^6.4|^7.0", - "symfony/event-dispatcher": "^7.3", - "symfony/http-foundation": "^7.3", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^7.3|^8.0", + "symfony/http-foundation": "^7.4|^8.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -7622,6 +6895,7 @@ "symfony/console": "<6.4", "symfony/dependency-injection": "<6.4", "symfony/doctrine-bridge": "<6.4", + "symfony/flex": "<2.10", "symfony/form": "<6.4", "symfony/http-client": "<6.4", "symfony/http-client-contracts": "<2.5", @@ -7639,28 +6913,28 @@ }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^6.4|^7.0", - "symfony/clock": "^6.4|^7.0", - "symfony/config": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/css-selector": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/dom-crawler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", "symfony/http-client-contracts": "^2.5|^3", - "symfony/process": "^6.4|^7.0", - "symfony/property-access": "^7.1", - "symfony/routing": "^6.4|^7.0", - "symfony/serializer": "^7.1", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^7.1|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/serializer": "^7.1|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^6.4|^7.0", - "symfony/validator": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0", - "symfony/var-exporter": "^6.4|^7.0", - "twig/twig": "^3.12" + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12|^4.0" }, "type": "library", "autoload": { @@ -7688,7 +6962,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.3.0" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.18" }, "funding": [ { @@ -7699,25 +6973,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-29T07:47:32+00:00" + "time": "2026-08-30T21:24:29+00:00" }, { "name": "symfony/mailer", - "version": "v7.3.0", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "0f375bbbde96ae8c78e4aa3e63aabd486e33364c" + "reference": "b17c9bf3a551d5f635638a3b6c05f06c4dc87584" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/0f375bbbde96ae8c78e4aa3e63aabd486e33364c", - "reference": "0f375bbbde96ae8c78e4aa3e63aabd486e33364c", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b17c9bf3a551d5f635638a3b6c05f06c4dc87584", + "reference": "b17c9bf3a551d5f635638a3b6c05f06c4dc87584", "shasum": "" }, "require": { @@ -7725,8 +7003,8 @@ "php": ">=8.2", "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/mime": "^7.2", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/mime": "^7.2|^8.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -7737,10 +7015,10 @@ "symfony/twig-bridge": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/twig-bridge": "^6.4|^7.0" + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/twig-bridge": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -7768,7 +7046,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.0" + "source": "https://github.com/symfony/mailer/tree/v7.4.17" }, "funding": [ { @@ -7779,48 +7057,53 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-04T09:51:09+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/mime", - "version": "v7.3.4", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "b1b828f69cbaf887fa835a091869e55df91d0e35" + "reference": "bf328d82105831db3e409195db0540ff57f27c80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/b1b828f69cbaf887fa835a091869e55df91d0e35", - "reference": "b1b828f69cbaf887fa835a091869e55df91d0e35", + "url": "https://api.github.com/repos/symfony/mime/zipball/bf328d82105831db3e409195db0540ff57f27c80", + "reference": "bf328d82105831db3e409195db0540ff57f27c80", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, "conflict": { "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", "symfony/mailer": "<6.4", "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/property-access": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", - "symfony/serializer": "^6.4.3|^7.0.3" + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4.44|^7.4.17|^8.1.5" }, "type": "library", "autoload": { @@ -7852,7 +7135,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.3.4" + "source": "https://github.com/symfony/mime/tree/v7.4.18" }, "funding": [ { @@ -7872,20 +7155,20 @@ "type": "tidelift" } ], - "time": "2025-09-16T08:38:17+00:00" + "time": "2026-08-22T09:04:42+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.3.0", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca" + "reference": "2888fcdc4dc2fd5f7c7397be78631e8af12e02b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/afb9a8038025e5dbc657378bfab9198d75f10fca", - "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/2888fcdc4dc2fd5f7c7397be78631e8af12e02b4", + "reference": "2888fcdc4dc2fd5f7c7397be78631e8af12e02b4", "shasum": "" }, "require": { @@ -7923,7 +7206,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.3.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.4.8" }, "funding": [ { @@ -7934,25 +7217,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-04T13:12:05+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.32.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { @@ -8002,7 +7289,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { @@ -8013,32 +7300,294 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.32.0", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-28T08:25:59+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.42.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/51b5ff5ba85452b31ec6f55490b08148612339d9", + "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.42.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-24T10:51:20+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.42.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-07T06:33:24+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, + "provide": { + "ext-mbstring": "*" + }, "suggest": { - "ext-intl": "For best performance" + "ext-mbstring": "For best performance" }, "type": "library", "extra": { @@ -8052,7 +7601,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + "Symfony\\Polyfill\\Mbstring\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -8069,18 +7618,17 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "grapheme", - "intl", + "mbstring", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { @@ -8091,33 +7639,33 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { - "name": "symfony/polyfill-intl-idn", - "version": "v1.33.0", + "name": "symfony/polyfill-php80", + "version": "v1.37.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", "shasum": "" }, "require": { - "php": ">=7.2", - "symfony/polyfill-intl-normalizer": "^1.10" - }, - "suggest": { - "ext-intl": "For best performance" + "php": ">=7.2" }, "type": "library", "extra": { @@ -8131,8 +7679,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - } + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -8140,30 +7691,28 @@ ], "authors": [ { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" }, { - "name": "Trevor Rowbotham", - "email": "trevor.rowbotham@pm.me" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "idn", - "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" }, "funding": [ { @@ -8183,28 +7732,25 @@ "type": "tidelift" } ], - "time": "2024-09-10T14:38:51+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", + "name": "symfony/polyfill-php81", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", "shasum": "" }, "require": { "php": ">=7.2" }, - "suggest": { - "ext-intl": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -8217,7 +7763,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + "Symfony\\Polyfill\\Php81\\": "" }, "classmap": [ "Resources/stubs" @@ -8237,18 +7783,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "intl", - "normalizer", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" }, "funding": [ { @@ -8268,32 +7812,25 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-26T12:45:58+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", + "name": "symfony/polyfill-php82", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", + "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", "shasum": "" }, "require": { - "ext-iconv": "*", "php": ">=7.2" }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -8306,8 +7843,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } + "Symfony\\Polyfill\\Php82\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -8323,17 +7863,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.38.1" }, "funding": [ { @@ -8353,20 +7892,20 @@ "type": "tidelift" } ], - "time": "2024-12-23T08:48:59+00:00" + "time": "2026-05-26T12:45:58+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.33.0", + "name": "symfony/polyfill-php83", + "version": "v1.41.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/5ea99087fb99c273a9b9236ed4c31e78b16103c6", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6", "shasum": "" }, "require": { @@ -8384,7 +7923,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" + "Symfony\\Polyfill\\Php83\\": "" }, "classmap": [ "Resources/stubs" @@ -8395,10 +7934,6 @@ "MIT" ], "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -8408,7 +7943,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -8417,7 +7952,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.41.0" }, "funding": [ { @@ -8437,20 +7972,20 @@ "type": "tidelift" } ], - "time": "2025-01-02T08:10:11+00:00" + "time": "2026-07-01T12:47:55+00:00" }, { - "name": "symfony/polyfill-php81", - "version": "v1.32.0", + "name": "symfony/polyfill-php84", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", "shasum": "" }, "require": { @@ -8468,7 +8003,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" + "Symfony\\Polyfill\\Php84\\": "" }, "classmap": [ "Resources/stubs" @@ -8488,7 +8023,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -8497,7 +8032,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" }, "funding": [ { @@ -8508,25 +8043,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { - "name": "symfony/polyfill-php83", - "version": "v1.33.0", + "name": "symfony/polyfill-php85", + "version": "v1.41.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", "shasum": "" }, "require": { @@ -8544,7 +8083,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php83\\": "" + "Symfony\\Polyfill\\Php85\\": "" }, "classmap": [ "Resources/stubs" @@ -8564,7 +8103,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -8573,7 +8112,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" }, "funding": [ { @@ -8593,20 +8132,20 @@ "type": "tidelift" } ], - "time": "2025-07-08T02:45:35+00:00" + "time": "2026-07-01T12:47:55+00:00" }, { - "name": "symfony/polyfill-php84", - "version": "v1.32.0", + "name": "symfony/polyfill-php86", + "version": "v1.41.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "000df7860439609837bbe28670b0be15783b7fbf" + "url": "https://github.com/symfony/polyfill-php86.git", + "reference": "6bc356ed3d8dbfeea8f0de235e34d670704e880e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", - "reference": "000df7860439609837bbe28670b0be15783b7fbf", + "url": "https://api.github.com/repos/symfony/polyfill-php86/zipball/6bc356ed3d8dbfeea8f0de235e34d670704e880e", + "reference": "6bc356ed3d8dbfeea8f0de235e34d670704e880e", "shasum": "" }, "require": { @@ -8624,7 +8163,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php84\\": "" + "Symfony\\Polyfill\\Php86\\": "" }, "classmap": [ "Resources/stubs" @@ -8644,7 +8183,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.6+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -8653,7 +8192,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php86/tree/v1.41.0" }, "funding": [ { @@ -8664,25 +8203,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-20T12:04:08+00:00" + "time": "2026-07-02T13:42:24+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.32.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", - "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94", "shasum": "" }, "require": { @@ -8732,7 +8275,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.37.0" }, "funding": [ { @@ -8743,25 +8286,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/process", - "version": "v7.3.0", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" + "reference": "058d17fc284cce14efb2385783b55014a461b176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "url": "https://api.github.com/repos/symfony/process/zipball/058d17fc284cce14efb2385783b55014a461b176", + "reference": "058d17fc284cce14efb2385783b55014a461b176", "shasum": "" }, "require": { @@ -8793,7 +8340,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.0" + "source": "https://github.com/symfony/process/tree/v7.4.18" }, "funding": [ { @@ -8804,25 +8351,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-17T09:11:12+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/routing", - "version": "v7.3.0", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "8e213820c5fea844ecea29203d2a308019007c15" + "reference": "ddd558991e98f693ae6bf5063cc1b0362c6bbec3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8e213820c5fea844ecea29203d2a308019007c15", - "reference": "8e213820c5fea844ecea29203d2a308019007c15", + "url": "https://api.github.com/repos/symfony/routing/zipball/ddd558991e98f693ae6bf5063cc1b0362c6bbec3", + "reference": "ddd558991e98f693ae6bf5063cc1b0362c6bbec3", "shasum": "" }, "require": { @@ -8836,11 +8387,11 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0" + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -8874,7 +8425,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.3.0" + "source": "https://github.com/symfony/routing/tree/v7.4.18" }, "funding": [ { @@ -8885,25 +8436,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-24T20:43:28+00:00" + "time": "2026-08-17T13:12:36+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.0", + "version": "v3.7.3", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", "shasum": "" }, "require": { @@ -8921,7 +8476,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -8957,7 +8512,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.3" }, "funding": [ { @@ -8968,25 +8523,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2026-07-27T15:39:01+00:00" }, { "name": "symfony/stopwatch", - "version": "v7.3.0", + "version": "v7.4.8", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" + "reference": "70a852d72fec4d51efb1f48dcd968efcaf5ccb89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", - "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/70a852d72fec4d51efb1f48dcd968efcaf5ccb89", + "reference": "70a852d72fec4d51efb1f48dcd968efcaf5ccb89", "shasum": "" }, "require": { @@ -9019,7 +8578,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.3.0" + "source": "https://github.com/symfony/stopwatch/tree/v7.4.8" }, "funding": [ { @@ -9030,31 +8589,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-24T10:49:57+00:00" + "time": "2026-03-24T13:12:05+00:00" }, { "name": "symfony/string", - "version": "v7.3.0", + "version": "v7.4.15", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + "reference": "e394af32256bf9e7bf80849d95e589167c10097b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "url": "https://api.github.com/repos/symfony/string/zipball/e394af32256bf9e7bf80849d95e589167c10097b", + "reference": "e394af32256bf9e7bf80849d95e589167c10097b", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-grapheme": "~1.33", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0" }, @@ -9062,12 +8626,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -9106,7 +8669,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.0" + "source": "https://github.com/symfony/string/tree/v7.4.15" }, "funding": [ { @@ -9117,32 +8680,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-20T20:19:01+00:00" + "time": "2026-07-28T07:33:02+00:00" }, { "name": "symfony/translation", - "version": "v7.3.0", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "4aba29076a29a3aa667e09b791e5f868973a8667" + "reference": "2ee1e4a3b32a528a642babe041ff7c440b213b4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/4aba29076a29a3aa667e09b791e5f868973a8667", - "reference": "4aba29076a29a3aa667e09b791e5f868973a8667", + "url": "https://api.github.com/repos/symfony/translation/zipball/2ee1e4a3b32a528a642babe041ff7c440b213b4b", + "reference": "2ee1e4a3b32a528a642babe041ff7c440b213b4b", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.5|^3.0" + "symfony/translation-contracts": "^2.5.3|^3.3" }, "conflict": { "nikic/php-parser": "<5.0", @@ -9161,17 +8728,17 @@ "require-dev": { "nikic/php-parser": "^5.0", "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0" + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -9202,7 +8769,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.3.0" + "source": "https://github.com/symfony/translation/tree/v7.4.17" }, "funding": [ { @@ -9213,25 +8780,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-29T07:19:49+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.6.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", "shasum": "" }, "require": { @@ -9244,7 +8815,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -9280,7 +8851,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" }, "funding": [ { @@ -9291,25 +8862,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-27T08:32:26+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/uid", - "version": "v7.3.0", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "7beeb2b885cd584cd01e126c5777206ae4c3c6a3" + "reference": "69d732355a139c6f8881337d28515aa01f12b8be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/7beeb2b885cd584cd01e126c5777206ae4c3c6a3", - "reference": "7beeb2b885cd584cd01e126c5777206ae4c3c6a3", + "url": "https://api.github.com/repos/symfony/uid/zipball/69d732355a139c6f8881337d28515aa01f12b8be", + "reference": "69d732355a139c6f8881337d28515aa01f12b8be", "shasum": "" }, "require": { @@ -9317,7 +8892,7 @@ "symfony/polyfill-uuid": "^1.15" }, "require-dev": { - "symfony/console": "^6.4|^7.0" + "symfony/console": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -9354,7 +8929,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.3.0" + "source": "https://github.com/symfony/uid/tree/v7.4.17" }, "funding": [ { @@ -9365,25 +8940,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-24T14:28:13+00:00" + "time": "2026-08-11T07:38:58+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.3.0", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e" + "reference": "e088da50b813f32473a76871616cbb8fa54653a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/548f6760c54197b1084e1e5c71f6d9d523f2f78e", - "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e088da50b813f32473a76871616cbb8fa54653a8", + "reference": "e088da50b813f32473a76871616cbb8fa54653a8", "shasum": "" }, "require": { @@ -9395,12 +8974,11 @@ "symfony/console": "<6.4" }, "require-dev": { - "ext-iconv": "*", - "symfony/console": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/uid": "^6.4|^7.0", - "twig/twig": "^3.12" + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12|^4.0" }, "bin": [ "Resources/bin/var-dump-server" @@ -9438,7 +9016,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.18" }, "funding": [ { @@ -9450,75 +9028,7 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-04-27T18:39:23+00:00" - }, - { - "name": "symfony/yaml", - "version": "v7.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "cea40a48279d58dc3efee8112634cb90141156c2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/cea40a48279d58dc3efee8112634cb90141156c2", - "reference": "cea40a48279d58dc3efee8112634cb90141156c2", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<6.4" - }, - "require-dev": { - "symfony/console": "^6.4|^7.0" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -9526,20 +9036,20 @@ "type": "tidelift" } ], - "time": "2025-04-04T10:10:33+00:00" + "time": "2026-08-30T20:10:52+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -9568,7 +9078,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -9576,27 +9086,27 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-17T20:03:58+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", - "version": "v2.3.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "0d72ac1c00084279c1816675284073c5a337c20d" + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/0d72ac1c00084279c1816675284073c5a337c20d", - "reference": "0d72ac1c00084279c1816675284073c5a337c20d", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/f0292ccf0ec75843d65027214426b6b163b48b41", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "php": "^7.4 || ^8.0", - "symfony/css-selector": "^5.4 || ^6.0 || ^7.0" + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^2.0", @@ -9629,32 +9139,32 @@ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "support": { "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.3.0" + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.4.0" }, - "time": "2024-12-21T16:25:41+00:00" + "time": "2025-12-02T11:56:42+00:00" }, { "name": "vlucas/phpdotenv", - "version": "v5.6.2", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" + "reference": "301c07936b16d88628b126b01d082ba153cf4c40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", - "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/301c07936b16d88628b126b01d082ba153cf4c40", + "reference": "301c07936b16d88628b126b01d082ba153cf4c40", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.3", + "graham-campbell/result-type": "^1.2", "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.3", - "symfony/polyfill-ctype": "^1.24", - "symfony/polyfill-mbstring": "^1.24", - "symfony/polyfill-php80": "^1.24" + "phpoption/phpoption": "^1.10", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", @@ -9695,7 +9205,7 @@ "homepage": "https://github.com/vlucas" } ], - "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "description": "Loads environment variables from `.env` to `$_ENV` and `$_SERVER` automagically, and optionally to `getenv()`.", "keywords": [ "dotenv", "env", @@ -9703,7 +9213,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.7.0" }, "funding": [ { @@ -9715,27 +9225,27 @@ "type": "tidelift" } ], - "time": "2025-04-30T23:37:27+00:00" + "time": "2026-08-24T18:07:49+00:00" }, { "name": "voku/portable-ascii", - "version": "2.0.3", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + "reference": "8e1051fe39379367aecf014f41744ce7539a856f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", - "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/8e1051fe39379367aecf014f41744ce7539a856f", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f", "shasum": "" }, "require": { - "php": ">=7.0.0" + "php": ">=7.1.0" }, "require-dev": { - "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + "phpunit/phpunit": "~8.5 || ~9.6 || ~10.5 || ~11.5" }, "suggest": { "ext-intl": "Use Intl for transliterator_transliterate() support" @@ -9765,7 +9275,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + "source": "https://github.com/voku/portable-ascii/tree/2.1.1" }, "funding": [ { @@ -9789,65 +9299,7 @@ "type": "tidelift" } ], - "time": "2024-11-21T01:49:47+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" - }, - "time": "2022-06-03T18:03:27+00:00" + "time": "2026-04-26T05:33:54+00:00" } ], "aliases": [], @@ -9856,8 +9308,11 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^8.3|^8.4" + "php": "^8.3 || ^8.4 || ^8.5" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "platform-overrides": { + "php": "8.3.0" + }, + "plugin-api-version": "2.9.0" } diff --git a/config/data-validation.php b/config/data-validation.php new file mode 100644 index 0000000..826c046 --- /dev/null +++ b/config/data-validation.php @@ -0,0 +1,39 @@ + 1000, + + /* + |-------------------------------------------------------------------------- + | Parsed Rules Cache Size + |-------------------------------------------------------------------------- + | + | Maximum number of parsed pipe-string rule sets cached statically across + | Validator instances. Higher values speed up repeat validation runs that + | reuse the same rule strings. + | + */ + 'parsed_rules_cache' => 500, + + /* + |-------------------------------------------------------------------------- + | Wildcard Chunk Size + |-------------------------------------------------------------------------- + | + | Number of items processed per wildcard traversal chunk. The Validator + | will fall back to a dynamic heuristic when this is left null. + | + */ + 'chunk_size' => 500, +]; diff --git a/phpunit.xml b/phpunit.xml index 84058a3..2ad0e9a 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,5 +1,14 @@ - + + ./tests/Unit diff --git a/src/DataValidationConfig.php b/src/DataValidationConfig.php index 78d283f..09a5f83 100644 --- a/src/DataValidationConfig.php +++ b/src/DataValidationConfig.php @@ -6,14 +6,14 @@ class DataValidationConfig { public int $fieldCacheLimit = 1000; public int $parsedRulesCache = 500; - public int $chunkSize = 500; + public ?int $chunkSize = 500; public function optimizeForLargeDataset(int $totalItems): void { - $this->fieldCacheLimit = max(1000, $totalItems / 2); // e.g., 50,000 for 100,000 items - $this->parsedRulesCache = max(500, $totalItems / 20); // e.g., 5,000 for 100,000 items + $this->fieldCacheLimit = max(1000, intdiv($totalItems, 2)); // e.g., 50,000 for 100,000 items + $this->parsedRulesCache = max(500, intdiv($totalItems, 20)); // e.g., 5,000 for 100,000 items // Dynamic chunk size: between 500 and 10,000 - $this->chunkSize = max(500, min(10000, $totalItems / 100)); + $this->chunkSize = max(500, min(10000, intdiv($totalItems, 100))); } } diff --git a/src/Laravel/DataValidationServiceProvider.php b/src/Laravel/DataValidationServiceProvider.php new file mode 100644 index 0000000..21d7e4f --- /dev/null +++ b/src/Laravel/DataValidationServiceProvider.php @@ -0,0 +1,85 @@ +mergeConfigFrom( + $this->configPath(), + 'data-validation' + ); + + $this->app->singleton(DataValidationConfig::class, function ($app): DataValidationConfig { + $cfg = new DataValidationConfig(); + $values = (array) $app['config']->get('data-validation', []); + + if (array_key_exists('field_cache_limit', $values)) { + $cfg->fieldCacheLimit = (int) $values['field_cache_limit']; + } + if (array_key_exists('parsed_rules_cache', $values)) { + $cfg->parsedRulesCache = (int) $values['parsed_rules_cache']; + } + if (array_key_exists('chunk_size', $values)) { + $cfg->chunkSize = $values['chunk_size'] === null ? null : (int) $values['chunk_size']; + } + + return $cfg; + }); + + $this->app->bind('yorcreative.data-validation', function ($app): callable { + return static function ( + array $data, + array $rules, + array $messages = [], + array $attributes = [], + bool $stopOnFirstError = false + ) use ($app): Validator { + return Validator::make( + $data, + $rules, + $messages, + $attributes, + $stopOnFirstError, + $app->make(DataValidationConfig::class) + ); + }; + }); + } + + // NOTE: the runningInConsole()/publishes() wiring below is not covered by + // the test suite. Exercising it needs an application object exposing + // runningInConsole() and configPath(), which a bare + // Illuminate\Container\Container does not provide; building one for the + // test would be a test double, which this project's no-mocks constraint + // forbids. LaravelServiceProviderTest:: + // testConfigPathResolvesToAReadablePublishableConfigFile instead covers + // the one part of this that can break silently: configPath() resolving + // to a file that does not exist. + public function boot(): void + { + if ($this->app->runningInConsole()) { + $this->publishes([ + $this->configPath() => $this->app->configPath('data-validation.php'), + ], 'data-validation-config'); + } + } + + private function configPath(): string + { + return dirname(__DIR__, 2) . '/config/data-validation.php'; + } +} diff --git a/src/RuleRegistry.php b/src/RuleRegistry.php index 085399f..7296f70 100644 --- a/src/RuleRegistry.php +++ b/src/RuleRegistry.php @@ -54,7 +54,10 @@ public static function getRule(string $ruleName): ValidationRuleInterface if (isset(self::$rules[$ruleName])) { return self::$rules[$ruleName]; } - throw new InvalidArgumentException("Unknown validation rule: '$ruleName'. Ensure the rule class exists, is discoverable, and named correctly (e.g., 'MinRule' for 'min')."); + throw new InvalidArgumentException( + "Unknown validation rule: '$ruleName'. Ensure the rule class exists, is discoverable, " + . "and named correctly (e.g., 'MinRule' for 'min')." + ); } public static function hasRule(string $ruleName): bool @@ -88,7 +91,10 @@ private static function loadRulesFromDirectory(string $directory, ?string $baseN return; } - $directoryIterator = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS | RecursiveDirectoryIterator::FOLLOW_SYMLINKS); + $directoryIterator = new RecursiveDirectoryIterator( + $directory, + RecursiveDirectoryIterator::SKIP_DOTS | RecursiveDirectoryIterator::FOLLOW_SYMLINKS + ); $iterator = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::SELF_FIRST); foreach ($iterator as $file) { @@ -132,19 +138,27 @@ private static function deriveRuleNameFromClassName(string $className): string return $snakeCaseName; } - private static function getClassNameFromFile(SplFileInfo $file, string $baseDirectory, ?string $baseNamespace): ?string - { + private static function getClassNameFromFile( + SplFileInfo $file, + string $baseDirectory, + ?string $baseNamespace + ): ?string { $realPath = $file->getRealPath(); if (!$realPath || !is_readable($realPath)) { return null; } - $relativePath = trim(substr($realPath, strlen(rtrim($baseDirectory, DIRECTORY_SEPARATOR))), DIRECTORY_SEPARATOR); + $relativePath = trim( + substr($realPath, strlen(rtrim($baseDirectory, DIRECTORY_SEPARATOR))), + DIRECTORY_SEPARATOR + ); $classPath = str_replace(DIRECTORY_SEPARATOR, '\\', $relativePath); $classNameWithoutExtension = pathinfo($classPath, PATHINFO_FILENAME); $namespaceWithinBase = dirname($classPath); - $namespaceWithinBase = ($namespaceWithinBase === '.' || $namespaceWithinBase === '') ? '' : '\\' . $namespaceWithinBase; + $namespaceWithinBase = ($namespaceWithinBase === '.' || $namespaceWithinBase === '') + ? '' + : '\\' . $namespaceWithinBase; $namespaceWithinBase = str_replace('\\\\', '\\', $namespaceWithinBase); if ($baseNamespace) { diff --git a/src/Rules/BetweenRule.php b/src/Rules/BetweenRule.php index 4a5f178..bc2072b 100644 --- a/src/Rules/BetweenRule.php +++ b/src/Rules/BetweenRule.php @@ -39,7 +39,9 @@ public function getErrorMessage(string $field, array $parameters, ?string $custo public function validateParameters(string $field, array $parameters): void { if (count($parameters) !== 2 || !is_numeric($parameters[0]) || !is_numeric($parameters[1])) { - throw new InvalidArgumentException("The 'between' rule for field '{$field}' requires exactly two numeric parameters."); + throw new InvalidArgumentException( + "The 'between' rule for field '{$field}' requires exactly two numeric parameters." + ); } } } diff --git a/src/Rules/DateFormatRule.php b/src/Rules/DateFormatRule.php index 19fa69c..657b50b 100644 --- a/src/Rules/DateFormatRule.php +++ b/src/Rules/DateFormatRule.php @@ -26,7 +26,9 @@ public function getErrorMessage(string $field, array $parameters, ?string $custo public function validateParameters(string $field, array $parameters): void { if (count($parameters) !== 1) { - throw new InvalidArgumentException("The 'date_format' rule for field '{$field}' requires exactly one parameter."); + throw new InvalidArgumentException( + "The 'date_format' rule for field '{$field}' requires exactly one parameter." + ); } } } diff --git a/src/Rules/DifferentRule.php b/src/Rules/DifferentRule.php index 76177d3..bd49045 100644 --- a/src/Rules/DifferentRule.php +++ b/src/Rules/DifferentRule.php @@ -37,7 +37,9 @@ private function getFieldValue(string $field, array $data) public function validateParameters(string $field, array $parameters): void { if (count($parameters) !== 1) { - throw new InvalidArgumentException("The 'different' rule for field '{$field}' requires exactly one parameter."); + throw new InvalidArgumentException( + "The 'different' rule for field '{$field}' requires exactly one parameter." + ); } } } diff --git a/src/Rules/DigitsRule.php b/src/Rules/DigitsRule.php index 4e58d0b..edf2133 100644 --- a/src/Rules/DigitsRule.php +++ b/src/Rules/DigitsRule.php @@ -23,7 +23,9 @@ public function getErrorMessage(string $field, array $parameters, ?string $custo public function validateParameters(string $field, array $parameters): void { if (count($parameters) !== 1 || !is_numeric($parameters[0])) { - throw new InvalidArgumentException("The 'digits' rule for field '{$field}' requires exactly one numeric parameter."); + throw new InvalidArgumentException( + "The 'digits' rule for field '{$field}' requires exactly one numeric parameter." + ); } } } diff --git a/src/Rules/EndsWithRule.php b/src/Rules/EndsWithRule.php index edb643b..30c4224 100644 --- a/src/Rules/EndsWithRule.php +++ b/src/Rules/EndsWithRule.php @@ -31,7 +31,9 @@ public function getErrorMessage(string $field, array $parameters, ?string $custo public function validateParameters(string $field, array $parameters): void { if (empty($parameters)) { - throw new InvalidArgumentException("The 'ends_with' rule for field '{$field}' requires at least one parameter."); + throw new InvalidArgumentException( + "The 'ends_with' rule for field '{$field}' requires at least one parameter." + ); } } } diff --git a/src/Rules/MaxRule.php b/src/Rules/MaxRule.php index 6a57388..5e7b14f 100644 --- a/src/Rules/MaxRule.php +++ b/src/Rules/MaxRule.php @@ -29,7 +29,9 @@ public function getErrorMessage(string $field, array $parameters, ?string $custo public function validateParameters(string $field, array $parameters): void { if (count($parameters) !== 1 || !is_numeric($parameters[0])) { - throw new InvalidArgumentException("The 'max' rule for field '{$field}' requires exactly one numeric parameter."); + throw new InvalidArgumentException( + "The 'max' rule for field '{$field}' requires exactly one numeric parameter." + ); } } } diff --git a/src/Rules/MinRule.php b/src/Rules/MinRule.php index a1fd0ef..d214588 100644 --- a/src/Rules/MinRule.php +++ b/src/Rules/MinRule.php @@ -29,7 +29,9 @@ public function getErrorMessage(string $field, array $parameters, ?string $custo public function validateParameters(string $field, array $parameters): void { if (count($parameters) !== 1 || !is_numeric($parameters[0])) { - throw new InvalidArgumentException("The 'min' rule for field '{$field}' requires exactly one numeric parameter."); + throw new InvalidArgumentException( + "The 'min' rule for field '{$field}' requires exactly one numeric parameter." + ); } } } diff --git a/src/Rules/NotInRule.php b/src/Rules/NotInRule.php index dc54857..92573cd 100644 --- a/src/Rules/NotInRule.php +++ b/src/Rules/NotInRule.php @@ -19,7 +19,9 @@ public function getErrorMessage(string $field, array $parameters, ?string $custo public function validateParameters(string $field, array $parameters): void { if (empty($parameters)) { - throw new InvalidArgumentException("The 'not_in' rule for field '{$field}' requires at least one parameter."); + throw new InvalidArgumentException( + "The 'not_in' rule for field '{$field}' requires at least one parameter." + ); } } } diff --git a/src/Rules/RegexRule.php b/src/Rules/RegexRule.php index eb4b30d..687819e 100644 --- a/src/Rules/RegexRule.php +++ b/src/Rules/RegexRule.php @@ -22,11 +22,15 @@ public function getErrorMessage(string $field, array $parameters, ?string $custo public function validateParameters(string $field, array $parameters): void { if (count($parameters) !== 1) { - throw new InvalidArgumentException("The 'regex' rule for field '{$field}' requires exactly one parameter (the pattern)."); + throw new InvalidArgumentException( + "The 'regex' rule for field '{$field}' requires exactly one parameter (the pattern)." + ); } $pattern = $parameters[0]; if (!is_string($pattern)) { - throw new InvalidArgumentException("The 'regex' rule for field '{$field}' requires a string pattern as its parameter."); + throw new InvalidArgumentException( + "The 'regex' rule for field '{$field}' requires a string pattern as its parameter." + ); } // Test the pattern for validity if (@preg_match($pattern, '') === false) { diff --git a/src/Rules/RequiredIfRule.php b/src/Rules/RequiredIfRule.php index 8275f61..957ba10 100644 --- a/src/Rules/RequiredIfRule.php +++ b/src/Rules/RequiredIfRule.php @@ -48,7 +48,9 @@ private function getFieldValue(string $field, array $data) public function validateParameters(string $field, array $parameters): void { if (count($parameters) < 2) { - throw new InvalidArgumentException("The 'required_if' rule for field '{$field}' requires at least two parameters."); + throw new InvalidArgumentException( + "The 'required_if' rule for field '{$field}' requires at least two parameters." + ); } } } diff --git a/src/Rules/RequiredUnlessRule.php b/src/Rules/RequiredUnlessRule.php index d26349e..372ab00 100644 --- a/src/Rules/RequiredUnlessRule.php +++ b/src/Rules/RequiredUnlessRule.php @@ -41,7 +41,9 @@ private function getFieldValue(string $field, array $data) public function validateParameters(string $field, array $parameters): void { if (count($parameters) < 2) { - throw new InvalidArgumentException("The 'required_unless' rule for field '{$field}' requires at least two parameters."); + throw new InvalidArgumentException( + "The 'required_unless' rule for field '{$field}' requires at least two parameters." + ); } } } diff --git a/src/Rules/StartsWithRule.php b/src/Rules/StartsWithRule.php index 500a708..bf071bb 100644 --- a/src/Rules/StartsWithRule.php +++ b/src/Rules/StartsWithRule.php @@ -31,7 +31,9 @@ public function getErrorMessage(string $field, array $parameters, ?string $custo public function validateParameters(string $field, array $parameters): void { if (empty($parameters)) { - throw new InvalidArgumentException("The 'starts_with' rule for field '{$field}' requires at least one parameter."); + throw new InvalidArgumentException( + "The 'starts_with' rule for field '{$field}' requires at least one parameter." + ); } } } diff --git a/src/Validator.php b/src/Validator.php index 5010209..9cae5df 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -15,6 +15,8 @@ class Validator private array $attributes = []; private array $fieldValueCache = []; private bool $stopOnFirstError; + private bool $hasValidated = false; + private bool $validating = false; private int $fieldCacheLimit = 1000; private static array $rulesNeedingParamPrep = ['same', 'different', 'required_if', 'required_unless']; private static array $parsedRulesCache = []; @@ -63,9 +65,10 @@ public static function isValid( array $rules, array $messages = [], array $attributes = [], - bool $stopOnFirstError = false + bool $stopOnFirstError = false, + ?DataValidationConfig $config = null ): bool { - return static::make($data, $rules, $messages, $attributes, $stopOnFirstError)->validate(); + return static::make($data, $rules, $messages, $attributes, $stopOnFirstError, $config)->validate(); } public function clearParsedRulesCache(): void @@ -85,19 +88,25 @@ private function validateRulesConfiguration(): void { foreach ($this->rules as $field => $ruleSet) { if (!is_string($ruleSet) && !is_array($ruleSet)) { - throw new InvalidArgumentException("Invalid rule set type for field '{$field}': must be string or array"); + throw new InvalidArgumentException( + "Invalid rule set type for field '{$field}': must be string or array" + ); } $parsedRules = $this->getParsedRules($ruleSet); foreach ($parsedRules as $rule) { if (is_string($rule)) { [$ruleName, $parameters] = $this->parseRule($rule); if (!RuleRegistry::hasRule($ruleName)) { - throw new InvalidArgumentException("Unknown validation rule '{$ruleName}' for field '{$field}'"); + throw new InvalidArgumentException( + "Unknown validation rule '{$ruleName}' for field '{$field}'" + ); } $ruleInstance = RuleRegistry::getRule($ruleName); $ruleInstance->validateParameters($field, $parameters); } elseif (!($rule instanceof Closure)) { - throw new InvalidArgumentException("Invalid rule type for field '{$field}': must be string or Closure"); + throw new InvalidArgumentException( + "Invalid rule type for field '{$field}': must be string or Closure" + ); } } } @@ -105,73 +114,89 @@ private function validateRulesConfiguration(): void public function validate(): bool { - $this->errors = []; - $this->fieldValueCache = []; - $stack = []; - - foreach ($this->rules as $field => $ruleSet) { - if (empty($ruleSet) && !($ruleSet instanceof Closure)) { - continue; - } - $stack[] = [ - 'data' => &$this->data, - 'fieldParts' => explode('.', $field), - 'rules' => $this->getParsedRules($ruleSet), - 'currentPath' => '', - 'fullPath' => $field - ]; - } - - while (!empty($stack)) { - $current = array_shift($stack); - $dataValue = &$current['data']; - $fieldParts = $current['fieldParts']; - $rules = $current['rules']; - $currentPath = $current['currentPath']; - $fullPath = $current['fullPath']; - - if (empty($fieldParts)) { - $this->applyRules($currentPath, $dataValue, $rules, $fullPath, $this->data); - } elseif ($fieldParts[0] === '*') { - if (!is_array($dataValue)) { - if ($this->hasRequiredRule($rules)) { - $this->handleNonArray($currentPath, $fullPath, $rules); - } + $this->validating = true; + try { + // A new run invalidates the previous verdict immediately. The flag + // is set again only where this run completes -- the normal return + // and the stopOnFirstError early failure below -- so a run that + // throws part-way cannot leave a stale true behind for passes() + // and fails() to read as success against an already-emptied + // $errors array. + $this->hasValidated = false; + $this->errors = []; + $this->fieldValueCache = []; + $stack = []; + + foreach ($this->rules as $field => $ruleSet) { + if (empty($ruleSet) && !($ruleSet instanceof Closure)) { continue; } - $chunkSize = $this->determineChunkSize(count($dataValue)); - $keys = array_keys($dataValue); - foreach (array_chunk($keys, $chunkSize) as $chunkKeys) { - foreach ($chunkKeys as $key) { - $newPath = $currentPath ? "$currentPath.$key" : (string)$key; - $newFullPath = preg_replace('/(? &$dataValue[$key], - 'fieldParts' => array_slice($fieldParts, 1), - 'rules' => $rules, - 'currentPath' => $newPath, - 'fullPath' => $newFullPath - ]; - } - gc_collect_cycles(); - } - } else { - $part = array_shift($fieldParts); - $subData = is_array($dataValue) && array_key_exists($part, $dataValue) ? $dataValue[$part] : null; - $newPath = $currentPath ? "$currentPath.$part" : $part; $stack[] = [ - 'data' => &$subData, - 'fieldParts' => $fieldParts, - 'rules' => $rules, - 'currentPath' => $newPath, - 'fullPath' => $fullPath + 'data' => $this->data, + 'fieldParts' => explode('.', $field), + 'rules' => $this->getParsedRules($ruleSet), + 'currentPath' => '', + 'fullPath' => $field ]; } - if ($this->stopOnFirstError && !empty($this->errors)) { - return false; + + while (!empty($stack)) { + $current = array_shift($stack); + $dataValue = $current['data']; + $fieldParts = $current['fieldParts']; + $rules = $current['rules']; + $currentPath = $current['currentPath']; + $fullPath = $current['fullPath']; + + if (empty($fieldParts)) { + $this->applyRules($currentPath, $dataValue, $rules, $fullPath, $this->data); + } elseif ($fieldParts[0] === '*') { + if (!is_array($dataValue)) { + if ($this->hasRequiredRule($rules)) { + $this->handleNonArray($currentPath, $fullPath, $rules); + } + continue; + } + $chunkSize = $this->determineChunkSize(count($dataValue)); + $keys = array_keys($dataValue); + foreach (array_chunk($keys, $chunkSize) as $chunkKeys) { + foreach ($chunkKeys as $key) { + $newPath = $currentPath ? "$currentPath.$key" : (string)$key; + $newFullPath = preg_replace('/(? $dataValue[$key], + 'fieldParts' => array_slice($fieldParts, 1), + 'rules' => $rules, + 'currentPath' => $newPath, + 'fullPath' => $newFullPath + ]; + } + gc_collect_cycles(); + } + } else { + $part = array_shift($fieldParts); + $subData = is_array($dataValue) && array_key_exists($part, $dataValue) + ? $dataValue[$part] + : null; + $newPath = $currentPath ? "$currentPath.$part" : $part; + $stack[] = [ + 'data' => $subData, + 'fieldParts' => $fieldParts, + 'rules' => $rules, + 'currentPath' => $newPath, + 'fullPath' => $fullPath + ]; + } + if ($this->stopOnFirstError && !empty($this->errors)) { + $this->hasValidated = true; + return false; + } } + $this->hasValidated = true; + return empty($this->errors); + } finally { + $this->validating = false; } - return empty($this->errors); } private function hasRequiredRule(array $rules): bool @@ -186,8 +211,8 @@ private function hasRequiredRule(array $rules): bool private function determineChunkSize(int $dataSize): int { - if (($chunkSize = $this->config->chunkSize) !== null) { - return $chunkSize; + if (($chunkSize = $this->config?->chunkSize) !== null) { + return max(1, $chunkSize); } if ($dataSize <= self::DATA_SIZE_THRESHOLD_LOWER) { @@ -220,7 +245,8 @@ private function getParsedRules($ruleSet): array } if (!isset(self::$parsedRulesCache[$ruleSet])) { if (count(self::$parsedRulesCache) >= self::$cacheLimit) { - self::$parsedRulesCache = array_slice(self::$parsedRulesCache, -self::$cacheLimit / 2, null, true); + $itemsToRetain = max(1, intdiv(self::$cacheLimit, 2)); + self::$parsedRulesCache = array_slice(self::$parsedRulesCache, -$itemsToRetain, null, true); } self::$parsedRulesCache[$ruleSet] = explode('|', $ruleSet); } @@ -229,7 +255,7 @@ private function getParsedRules($ruleSet): array return is_array($ruleSet) ? $ruleSet : []; } - private function applyRules(string $field, $value, array $rules, string $originalRulePath, array &$data): void + private function applyRules(string $field, $value, array $rules, string $originalRulePath, array $data): void { $isNullable = in_array('nullable', $rules, true); if (($value === null || (is_string($value) && $value === '')) && $isNullable) { @@ -283,8 +309,10 @@ private function applyRules(string $field, $value, array $rules, string $origina } } - private function resolveOtherFieldPathForComparison(string $otherFieldWithPossibleWildcards, string $currentFieldPath): string - { + private function resolveOtherFieldPathForComparison( + string $otherFieldWithPossibleWildcards, + string $currentFieldPath + ): string { $otherParts = explode('.', $otherFieldWithPossibleWildcards); $currentParts = explode('.', $currentFieldPath); @@ -310,7 +338,8 @@ private function formatMessage(string $template, string $field, string $ruleName $replacements = []; - if (in_array($ruleName, ['in', 'not_in', 'starts_with', 'ends_with', 'required_if', 'required_unless']) && !empty($ruleParameters)) { + $ruleNamesRequiringValueList = ['in', 'not_in', 'starts_with', 'ends_with', 'required_if', 'required_unless']; + if (in_array($ruleName, $ruleNamesRequiringValueList) && !empty($ruleParameters)) { if (in_array($ruleName, ['required_if', 'required_unless'])) { $values = array_map('strval', array_slice($ruleParameters, 1)); } else { @@ -354,7 +383,10 @@ private function addErrorMessageByRuleName(string $field, string $ruleName, arra $template = 'The :attribute must be an array.'; $this->addErrorRaw($field, $this->formatMessage($template, $field, $ruleName, $parameters)); } else { - $this->addErrorRaw($field, "The {$this->getAttributeName($field)} is invalid (unknown rule {$ruleName})."); + $this->addErrorRaw( + $field, + "The {$this->getAttributeName($field)} is invalid (unknown rule {$ruleName})." + ); } } } @@ -405,11 +437,45 @@ public function errors(): array public function fails(): bool { - return !empty($this->errors); + return !$this->validationPassed(); } public function passes(): bool { + return $this->validationPassed(); + } + + // hasValidated is a claim about the CURRENT $data/$rules: once true, it + // means $this->errors reflects a completed run against exactly the data + // and rules this instance holds right now. That claim only holds because + // this class has no mutators for $data or $rules after construction -- + // there is nothing that can invalidate a completed run out from under + // this flag. Any future setter for either property MUST also reset + // hasValidated = false, or this method (and therefore passes()/fails()) + // will return a verdict about data/rules that no longer exist. Later + // workstreams build validated() and validateOrFail() on top of this + // flag, so a violation here is not merely cosmetic. + // + // The $validating guard handles a different case: a rule that captures + // its own validator and calls passes()/fails() while validate() is + // still running. hasValidated is not set until validate() returns, so + // without this guard such a call would restart validate() from inside + // itself, and that same rule would re-enter again -- unbounded + // recursion. When $validating is true we instead report the errors + // accumulated so far, without restarting or recursing. + private function validationPassed(): bool + { + if ($this->validating) { + // Re-entrant call from inside a rule: report the state + // accumulated so far rather than restarting validation and + // recursing forever. + return empty($this->errors); + } + + if (!$this->hasValidated) { + return $this->validate(); + } + return empty($this->errors); } @@ -426,7 +492,8 @@ public function __serialize(): array 'errors' => $this->errors, 'messages' => $this->messages, 'attributes' => $this->attributes, - 'stopOnFirstError' => $this->stopOnFirstError + 'stopOnFirstError' => $this->stopOnFirstError, + 'hasValidated' => $this->hasValidated ]; } @@ -438,6 +505,7 @@ public function __unserialize(array $sData): void $this->messages = $sData['messages'] ?? []; $this->attributes = $sData['attributes'] ?? []; $this->stopOnFirstError = $sData['stopOnFirstError'] ?? false; + $this->hasValidated = $sData['hasValidated'] ?? false; $this->fieldValueCache = []; } } diff --git a/tests/Feature/LaravelIntegrationTest.php b/tests/Feature/LaravelIntegrationTest.php new file mode 100644 index 0000000..d2af427 --- /dev/null +++ b/tests/Feature/LaravelIntegrationTest.php @@ -0,0 +1,405 @@ +assertFileExists($path); + + return json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR); + } + + // ----------------------------------------------------------------- + // Package discovery + // ----------------------------------------------------------------- + + public function testPackageDiscoveryNamesAProviderThatActuallyExists(): void + { + $manifest = $this->composerManifest(); + + $providers = $manifest['extra']['laravel']['providers'] ?? null; + + $this->assertIsArray($providers, 'composer.json declares no Laravel providers for auto-discovery.'); + $this->assertNotEmpty($providers); + + foreach ($providers as $provider) { + $this->assertTrue( + class_exists($provider), + "Auto-discovered provider [{$provider}] does not exist; discovery would fatal on boot." + ); + $this->assertTrue( + is_subclass_of($provider, ServiceProvider::class), + "Auto-discovered provider [{$provider}] is not a ServiceProvider." + ); + } + } + + public function testTheDiscoveredProviderIsTheOneUnderTest(): void + { + $manifest = $this->composerManifest(); + + $this->assertContains( + DataValidationServiceProvider::class, + $manifest['extra']['laravel']['providers'], + 'The provider covered by these tests is not the one Laravel would discover.' + ); + } + + // ----------------------------------------------------------------- + // Standalone installation stays free of Laravel + // ----------------------------------------------------------------- + + public function testStandaloneInstallRequiresNoLaravelRuntimePackages(): void + { + $manifest = $this->composerManifest(); + + $this->assertSame( + ['php'], + array_keys($manifest['require']), + 'The runtime requirements must stay PHP-only; a Laravel package here would ' + . 'force the framework on every standalone consumer.' + ); + } + + public function testTheLaravelIntegrationIsOptionalAndSuggestedOnly(): void + { + $manifest = $this->composerManifest(); + + foreach (array_keys($manifest['require']) as $package) { + $this->assertStringStartsNotWith('illuminate/', $package); + $this->assertStringStartsNotWith('laravel/', $package); + $this->assertStringStartsNotWith('symfony/', $package); + } + + $this->assertArrayHasKey( + 'illuminate/support', + $manifest['suggest'] ?? [], + 'The optional Laravel integration should be advertised via suggest.' + ); + } + + public function testCoreLibraryCodeDoesNotReferenceLaravel(): void + { + $srcDir = dirname(__DIR__, 2) . '/src'; + $laravelDir = $srcDir . '/Laravel'; + + $offenders = []; + + $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($srcDir)); + foreach ($files as $file) { + if (! $file->isFile() || $file->getExtension() !== 'php') { + continue; + } + + // The Laravel/ subtree is the integration itself and is only ever + // autoloaded inside a Laravel application. + if (str_starts_with($file->getPathname(), $laravelDir)) { + continue; + } + + $contents = file_get_contents($file->getPathname()); + if (preg_match('/\b(?:Illuminate|Laravel)\\\\/', $contents)) { + $offenders[] = str_replace($srcDir . '/', '', $file->getPathname()); + } + } + + $this->assertSame( + [], + $offenders, + 'Core library files reference Laravel: ' . implode(', ', $offenders) + ); + } + + // ----------------------------------------------------------------- + // Config publication + // ----------------------------------------------------------------- + + /** + * Boot the provider inside a real application and assert what boot() itself + * registered. + * + * An earlier version of this test called ServiceProvider::publishes() + * directly and then asserted the registration it had just made -- it passed + * with the provider's boot() body deleted. This drives the real + * Illuminate\Foundation\Application through register() and boot(), so the + * publish mapping under assertion is the one boot() actually produced. + */ + public function testBootRegistersTheConfigPublishMappingInARealApplication(): void + { + $this->forgetPublishRegistry(); + + $app = $this->bootedApplication(); + + $paths = ServiceProvider::pathsToPublish( + DataValidationServiceProvider::class, + 'data-validation-config' + ); + + $this->assertNotEmpty( + $paths, + 'boot() registered nothing under the data-validation-config tag.' + ); + + $reflection = new ReflectionClass(DataValidationServiceProvider::class); + $configPath = $reflection->getMethod('configPath') + ->invoke(new DataValidationServiceProvider($app)); + + $this->assertArrayHasKey( + $configPath, + $paths, + 'The published source path is not the packaged config file.' + ); + + $this->assertSame( + $app->configPath('data-validation.php'), + $paths[$configPath], + 'The publish target is not the application config path.' + ); + } + + public function testBootPublishesNothingOutsideTheConsole(): void + { + // boot() guards its publishes() call with runningInConsole(). A real + // application reports false for a non-console run, and nothing should + // be registered then. + $this->forgetPublishRegistry(); + + $app = $this->bootedApplication(runningInConsole: false); + + $this->assertSame( + [], + ServiceProvider::pathsToPublish( + DataValidationServiceProvider::class, + 'data-validation-config' + ), + 'boot() registered publishable paths outside of a console run.' + ); + + // register() still ran, so the bindings must be live either way. + $this->assertInstanceOf(DataValidationConfig::class, $app->make(DataValidationConfig::class)); + } + + public function testRegisterAndBootInARealApplicationProduceAWorkingValidator(): void + { + $app = $this->bootedApplication(); + + $validator = ($app->make('yorcreative.data-validation'))( + ['email' => 'nope'], + ['email' => 'required|email'] + ); + + $this->assertInstanceOf(Validator::class, $validator); + $this->assertFalse($validator->validate()); + $this->assertArrayHasKey('email', $validator->errors()); + } + + public function testTheRealApplicationMergesThePackagedConfigDefaults(): void + { + // mergeConfigFrom() only runs inside register(); a real application is + // what proves the packaged file is actually reachable from it. + $app = $this->bootedApplication(); + + $this->assertSame( + (new DataValidationConfig())->fieldCacheLimit, + $app['config']->get('data-validation.field_cache_limit'), + 'The packaged config was not merged into the application config.' + ); + } + + /** + * A genuine Illuminate\Foundation\Application with the provider registered + * and booted. Not a double: this is the class Laravel itself runs. + */ + private function bootedApplication(?bool $runningInConsole = null): Application + { + // The Laravel 10 and 11 matrix jobs install the split illuminate/* + // packages, which ship the Application contract but no concrete + // application. Skip rather than fail there: the provider's behaviour on + // those majors is covered by the container-level tests. + if (! class_exists(Application::class)) { + $this->markTestSkipped( + 'laravel/framework is not installed; a real Application is unavailable on this dependency set.' + ); + } + + $app = new Application(dirname(__DIR__, 2)); + $app->instance('config', new ConfigRepository([])); + + // Left at null, the real Application decides for itself from PHP_SAPI, + // which under PHPUnit is 'cli' -- so the console branch of boot() runs + // without being told to. Only the non-console case needs to be set, and + // it is set on the real class's own property, not on a substitute. + if ($runningInConsole !== null) { + (new ReflectionClass(Application::class)) + ->getProperty('isRunningInConsole') + ->setValue($app, $runningInConsole); + } + + $provider = new DataValidationServiceProvider($app); + $provider->register(); + $provider->boot(); + + return $app; + } + + private function forgetPublishRegistry(): void + { + $reflection = new ReflectionClass(ServiceProvider::class); + + foreach (['publishes', 'publishGroups'] as $property) { + if ($reflection->hasProperty($property)) { + $reflection->getProperty($property)->setValue(null, []); + } + } + } + + public function testThePublishedConfigMatchesTheCompiledDefaults(): void + { + // If the shipped config file drifts from DataValidationConfig, publishing + // it silently changes behaviour for anyone who runs vendor:publish. + $reflection = new ReflectionClass(DataValidationServiceProvider::class); + $configPath = $reflection->getMethod('configPath') + ->invoke(new DataValidationServiceProvider(new Container())); + + $published = require $configPath; + $defaults = new DataValidationConfig(); + + $this->assertSame( + $defaults->fieldCacheLimit, + $published['field_cache_limit'], + 'field_cache_limit in the published config drifted from the compiled default.' + ); + $this->assertSame( + $defaults->parsedRulesCache, + $published['parsed_rules_cache'], + 'parsed_rules_cache in the published config drifted from the compiled default.' + ); + $this->assertSame( + $defaults->chunkSize, + $published['chunk_size'], + 'chunk_size in the published config drifted from the compiled default.' + ); + } + + public function testPublishingThenRegisteringYieldsTheCompiledDefaults(): void + { + // The end-to-end shape of `vendor:publish` followed by a boot: the file + // that would land in the application's config directory, fed back through + // the provider, must reproduce the library's own defaults. + $reflection = new ReflectionClass(DataValidationServiceProvider::class); + $configPath = $reflection->getMethod('configPath') + ->invoke(new DataValidationServiceProvider(new Container())); + + $app = new Container(); + $app->instance('config', new ConfigRepository(['data-validation' => require $configPath])); + (new DataValidationServiceProvider($app))->register(); + + $cfg = $app->make(DataValidationConfig::class); + $defaults = new DataValidationConfig(); + + $this->assertSame($defaults->fieldCacheLimit, $cfg->fieldCacheLimit); + $this->assertSame($defaults->parsedRulesCache, $cfg->parsedRulesCache); + $this->assertSame($defaults->chunkSize, $cfg->chunkSize); + } + + // ----------------------------------------------------------------- + // Overrides and the factory binding, through the container + // ----------------------------------------------------------------- + + public function testOverriddenConfigReachesTheValidatorProducedByTheFactory(): void + { + $app = new Container(); + $app->instance('config', new ConfigRepository([ + 'data-validation' => [ + 'field_cache_limit' => 111, + 'parsed_rules_cache' => 222, + 'chunk_size' => 333, + ], + ])); + (new DataValidationServiceProvider($app))->register(); + + $validator = ($app->make('yorcreative.data-validation'))( + ['a' => 'x'], + ['a' => 'required'] + ); + + $this->assertInstanceOf(Validator::class, $validator); + + $config = (new ReflectionClass($validator))->getProperty('config')->getValue($validator); + + $this->assertInstanceOf(DataValidationConfig::class, $config); + $this->assertSame(111, $config->fieldCacheLimit); + $this->assertSame(222, $config->parsedRulesCache); + $this->assertSame(333, $config->chunkSize); + } + + public function testNullChunkSizeReachesTheValidatorAndSelectsTheHeuristic(): void + { + $app = new Container(); + $app->instance('config', new ConfigRepository([ + 'data-validation' => ['chunk_size' => null], + ])); + (new DataValidationServiceProvider($app))->register(); + + $validator = ($app->make('yorcreative.data-validation'))( + ['rows' => [['v' => 1], ['v' => 2]]], + ['rows.*.v' => 'required'] + ); + + $config = (new ReflectionClass($validator))->getProperty('config')->getValue($validator); + $this->assertNull($config->chunkSize, 'A null chunk_size must survive to the Validator.'); + + // And the heuristic still produces a working validation run. + $this->assertTrue($validator->validate()); + } + + public function testFactoryHonoursStopOnFirstError(): void + { + $app = new Container(); + $app->instance('config', new ConfigRepository(['data-validation' => []])); + (new DataValidationServiceProvider($app))->register(); + + $factory = $app->make('yorcreative.data-validation'); + + $validator = $factory( + ['a' => 'bad', 'b' => 'bad'], + ['a' => 'email', 'b' => 'email'], + [], + [], + true + ); + + $this->assertFalse($validator->validate()); + $this->assertCount(1, $validator->errors(), 'stopOnFirstError did not reach the Validator.'); + } +} diff --git a/tests/Feature/LaravelServiceProviderTest.php b/tests/Feature/LaravelServiceProviderTest.php new file mode 100644 index 0000000..5b42c6d --- /dev/null +++ b/tests/Feature/LaravelServiceProviderTest.php @@ -0,0 +1,133 @@ +app = new Container(); + $this->app->instance('config', new ConfigRepository([ + 'data-validation' => [ + 'field_cache_limit' => 2500, + 'parsed_rules_cache' => 750, + 'chunk_size' => 800, + ], + ])); + + $provider = new DataValidationServiceProvider($this->app); + $provider->register(); + } + + public function testConfigSingletonReflectsPublishedValues(): void + { + /** @var DataValidationConfig $cfg */ + $cfg = $this->app->make(DataValidationConfig::class); + + $this->assertSame(2500, $cfg->fieldCacheLimit); + $this->assertSame(750, $cfg->parsedRulesCache); + $this->assertSame(800, $cfg->chunkSize); + } + + public function testConfigSingletonIsShared(): void + { + $first = $this->app->make(DataValidationConfig::class); + $second = $this->app->make(DataValidationConfig::class); + + $this->assertSame($first, $second); + } + + public function testValidatorFactoryProducesValidatorUsingContainerConfig(): void + { + /** @var callable $factory */ + $factory = $this->app->make('yorcreative.data-validation'); + + $validator = $factory( + ['email' => 'nope'], + ['email' => 'required|email'] + ); + + $this->assertInstanceOf(Validator::class, $validator); + $this->assertFalse($validator->validate()); + $this->assertArrayHasKey('email', $validator->errors()); + } + + public function testRegisterUsesDefaultsWhenConfigKeysMissing(): void + { + $bareApp = new Container(); + $bareApp->instance('config', new ConfigRepository(['data-validation' => []])); + (new DataValidationServiceProvider($bareApp))->register(); + + /** @var DataValidationConfig $cfg */ + $cfg = $bareApp->make(DataValidationConfig::class); + + // These match the defaults declared in DataValidationConfig. + $this->assertSame(1000, $cfg->fieldCacheLimit); + $this->assertSame(500, $cfg->parsedRulesCache); + $this->assertSame(500, $cfg->chunkSize); + } + + public function testRegisterAllowsNullChunkSizeForDynamicHeuristic(): void + { + $app = new Container(); + $app->instance('config', new ConfigRepository([ + 'data-validation' => [ + 'chunk_size' => null, + ], + ])); + (new DataValidationServiceProvider($app))->register(); + + /** @var DataValidationConfig $cfg */ + $cfg = $app->make(DataValidationConfig::class); + + $this->assertNull($cfg->chunkSize); + } + + /** + * boot()'s runningInConsole()/publishes() wiring needs an application + * exposing runningInConsole() and configPath(), which a bare + * Illuminate\Container\Container does not provide and which we will not + * fake with a test double (no mocks). What CAN break silently without an + * app is configPath() itself: if it resolved to a nonexistent file, + * `vendor:publish --tag=data-validation-config` would publish nothing + * while still appearing to succeed. This test guards that. + */ + public function testConfigPathResolvesToAReadablePublishableConfigFile(): void + { + $reflection = new ReflectionClass(DataValidationServiceProvider::class); + $method = $reflection->getMethod('configPath'); + + $provider = new DataValidationServiceProvider($this->app); + $path = $method->invoke($provider); + + $this->assertFileExists($path); + $this->assertIsReadable($path); + + $config = require $path; + + $this->assertIsArray($config); + $this->assertArrayHasKey('field_cache_limit', $config); + $this->assertArrayHasKey('parsed_rules_cache', $config); + $this->assertArrayHasKey('chunk_size', $config); + } +} diff --git a/tests/Unit/DataValidationConfigTest.php b/tests/Unit/DataValidationConfigTest.php index 8e39773..66d8c69 100644 --- a/tests/Unit/DataValidationConfigTest.php +++ b/tests/Unit/DataValidationConfigTest.php @@ -95,4 +95,39 @@ public function testOptimizeForLargeDatasetWithNonIntegerChunkSize() $this->assertEquals(2505, $config->parsedRulesCache); // max(500, 50100 / 20) = 2505 $this->assertEquals(501, $config->chunkSize); // max(500, min(10000, 50100 / 100)) = max(500, 501) = 501 } -} \ No newline at end of file + + public function testChunkSizeCanBeNullToEnableDynamicHeuristic() + { + $config = new DataValidationConfig(); + $config->chunkSize = null; + + $this->assertNull($config->chunkSize); + } + + public function testOptimizeForLargeDatasetClampsToMinimumsForSmallTotals(): void + { + $config = new DataValidationConfig(); + + // 1001 divides evenly by none of 2, 20 or 100, so float division + // would produce 500.5 / 50.05 / 10.01 and blow up on assignment. + $config->optimizeForLargeDataset(1001); + + $this->assertSame(1000, $config->fieldCacheLimit); + $this->assertSame(500, $config->parsedRulesCache); + $this->assertSame(500, $config->chunkSize); + } + + // Depends on phpunit.xml's failOnDeprecation="true": the behaviour this + // test covers has no value-level observable, only a deprecation notice + // that would otherwise pass silently. + public function testOptimizeForLargeDatasetScalesWithLargeNonDivisibleTotals(): void + { + $config = new DataValidationConfig(); + + $config->optimizeForLargeDataset(100001); + + $this->assertSame(50000, $config->fieldCacheLimit); + $this->assertSame(5000, $config->parsedRulesCache); + $this->assertSame(1000, $config->chunkSize); + } +} diff --git a/tests/Unit/RuleRegistryTest.php b/tests/Unit/RuleRegistryTest.php index 2044de1..1d13b9b 100644 --- a/tests/Unit/RuleRegistryTest.php +++ b/tests/Unit/RuleRegistryTest.php @@ -23,20 +23,16 @@ protected function setUp(): void $reflection = new ReflectionClass(RuleRegistry::class); $rulesProp = $reflection->getProperty('rules'); - $rulesProp->setAccessible(true); - $rulesProp->setValue([]); + $rulesProp->setValue(null, []); $closureRulesProp = $reflection->getProperty('closureRules'); - $closureRulesProp->setAccessible(true); - $closureRulesProp->setValue([]); + $closureRulesProp->setValue(null, []); $dirsProp = $reflection->getProperty('customRuleDirectories'); - $dirsProp->setAccessible(true); - $dirsProp->setValue([]); + $dirsProp->setValue(null, []); $initProp = $reflection->getProperty('isInitialized'); - $initProp->setAccessible(true); - $initProp->setValue(false); + $initProp->setValue(null, false); } protected function tearDown(): void @@ -72,11 +68,9 @@ public function testRegisterCustomRuleDirectoryWithValidDirectory(): void RuleRegistry::registerCustomRuleDirectory($this->tempDir); $reflection = new ReflectionClass(RuleRegistry::class); $dirsProp = $reflection->getProperty('customRuleDirectories'); - $dirsProp->setAccessible(true); $dirs = $dirsProp->getValue(); $this->assertContains(rtrim($this->tempDir, DIRECTORY_SEPARATOR), $dirs); $initProp = $reflection->getProperty('isInitialized'); - $initProp->setAccessible(true); $this->assertFalse($initProp->getValue()); } @@ -93,7 +87,6 @@ public function testRegisterCustomRuleDirectorySkipsDuplicates(): void RuleRegistry::registerCustomRuleDirectory($this->tempDir); $reflection = new ReflectionClass(RuleRegistry::class); $dirsProp = $reflection->getProperty('customRuleDirectories'); - $dirsProp->setAccessible(true); $dirs = $dirsProp->getValue(); $this->assertCount(1, $dirs); $this->assertContains(rtrim($this->tempDir, DIRECTORY_SEPARATOR), $dirs); @@ -103,11 +96,9 @@ public function testInitializeIfNeededCallsLoadRulesWhenNotInitialized(): void { $reflection = new ReflectionClass(RuleRegistry::class); $initProp = $reflection->getProperty('isInitialized'); - $initProp->setAccessible(true); - $initProp->setValue(false); + $initProp->setValue(null, false); $rulesProp = $reflection->getProperty('rules'); - $rulesProp->setAccessible(true); $initialRules = $rulesProp->getValue(); RuleRegistry::hasRule('required'); // Triggers initialization @@ -123,11 +114,9 @@ public function testInitializeIfNeededSkipsLoadRulesWhenInitialized(): void { $reflection = new ReflectionClass(RuleRegistry::class); $initProp = $reflection->getProperty('isInitialized'); - $initProp->setAccessible(true); - $initProp->setValue(true); + $initProp->setValue(null, true); $rulesProp = $reflection->getProperty('rules'); - $rulesProp->setAccessible(true); $initialRules = $rulesProp->getValue(); RuleRegistry::hasRule('required'); // Should not reload rules @@ -179,7 +168,6 @@ public function testRegisterClosureRuleAddsRule(): void RuleRegistry::registerClosureRule('custom', $closure, 'Custom error.'); $reflection = new ReflectionClass(RuleRegistry::class); $closureRulesProp = $reflection->getProperty('closureRules'); - $closureRulesProp->setAccessible(true); $closureRules = $closureRulesProp->getValue(); $this->assertArrayHasKey('custom', $closureRules); $this->assertInstanceOf(ClosureRule::class, $closureRules['custom']); @@ -202,7 +190,6 @@ public function validateParameters(string $field, array $parameters): void { } $reflection = new ReflectionClass(RuleRegistry::class); $rulesProp = $reflection->getProperty('rules'); - $rulesProp->setAccessible(true); RuleRegistry::hasRule('required'); // Initialize default rules $initialRules = $rulesProp->getValue(); @@ -210,8 +197,7 @@ public function validateParameters(string $field, array $parameters): void { } RuleRegistry::registerCustomRuleDirectory($this->tempDir); $initProp = $reflection->getProperty('isInitialized'); - $initProp->setAccessible(true); - $initProp->setValue(false); + $initProp->setValue(null, false); RuleRegistry::hasRule('test'); // Trigger reload with custom rules @@ -226,11 +212,9 @@ public function testLoadRulesFromInvalidDirectorySkips(): void { $reflection = new ReflectionClass(RuleRegistry::class); $loadRulesFromDirectoryMethod = $reflection->getMethod('loadRulesFromDirectory'); - $loadRulesFromDirectoryMethod->setAccessible(true); $loadRulesFromDirectoryMethod->invoke(null, '/non/existent/path', null); $rulesProp = $reflection->getProperty('rules'); - $rulesProp->setAccessible(true); $this->assertEmpty($rulesProp->getValue()); } @@ -251,11 +235,9 @@ public function validateParameters(string $field, array $parameters): void { } $reflection = new ReflectionClass(RuleRegistry::class); $loadRulesFromDirectoryMethod = $reflection->getMethod('loadRulesFromDirectory'); - $loadRulesFromDirectoryMethod->setAccessible(true); $loadRulesFromDirectoryMethod->invoke(null, $this->tempDir, 'TestRules'); $rulesProp = $reflection->getProperty('rules'); - $rulesProp->setAccessible(true); $rules = $rulesProp->getValue(); $this->assertArrayHasKey('test', $rules); $this->assertInstanceOf(ValidationRuleInterface::class, $rules['test']); @@ -267,11 +249,9 @@ public function testLoadRulesSkipsNonPhpFiles(): void $reflection = new ReflectionClass(RuleRegistry::class); $loadRulesFromDirectoryMethod = $reflection->getMethod('loadRulesFromDirectory'); - $loadRulesFromDirectoryMethod->setAccessible(true); $loadRulesFromDirectoryMethod->invoke(null, $this->tempDir, null); $rulesProp = $reflection->getProperty('rules'); - $rulesProp->setAccessible(true); $this->assertEmpty($rulesProp->getValue()); } @@ -292,11 +272,9 @@ public function validateParameters(string $field, array $parameters): void { } $reflection = new ReflectionClass(RuleRegistry::class); $loadRulesFromDirectoryMethod = $reflection->getMethod('loadRulesFromDirectory'); - $loadRulesFromDirectoryMethod->setAccessible(true); $loadRulesFromDirectoryMethod->invoke(null, $this->tempDir, 'TestRules'); $rulesProp = $reflection->getProperty('rules'); - $rulesProp->setAccessible(true); $this->assertEmpty($rulesProp->getValue()); } @@ -313,11 +291,9 @@ interface TestInterface extends ValidationRuleInterface {} $reflection = new ReflectionClass(RuleRegistry::class); $loadRulesFromDirectoryMethod = $reflection->getMethod('loadRulesFromDirectory'); - $loadRulesFromDirectoryMethod->setAccessible(true); $loadRulesFromDirectoryMethod->invoke(null, $this->tempDir, 'TestRules'); $rulesProp = $reflection->getProperty('rules'); - $rulesProp->setAccessible(true); $this->assertEmpty($rulesProp->getValue()); } @@ -335,11 +311,9 @@ public function validate(string $field, $value, array $parameters, array $data): $reflection = new ReflectionClass(RuleRegistry::class); $loadRulesFromDirectoryMethod = $reflection->getMethod('loadRulesFromDirectory'); - $loadRulesFromDirectoryMethod->setAccessible(true); $loadRulesFromDirectoryMethod->invoke(null, $this->tempDir, 'TestRules'); $rulesProp = $reflection->getProperty('rules'); - $rulesProp->setAccessible(true); $this->assertEmpty($rulesProp->getValue()); } @@ -347,11 +321,9 @@ public function testLoadRulesSkipsClosureRule(): void { $reflection = new ReflectionClass(RuleRegistry::class); $rulesProp = $reflection->getProperty('rules'); - $rulesProp->setAccessible(true); - $rulesProp->setValue(['closure' => new ClosureRule(fn () => true)]); + $rulesProp->setValue(null, ['closure' => new ClosureRule(fn () => true)]); $loadRulesMethod = $reflection->getMethod('loadRules'); - $loadRulesMethod->setAccessible(true); $loadRulesMethod->invoke(null); $rules = $rulesProp->getValue(); @@ -377,11 +349,9 @@ public function validateParameters(string $field, array $parameters): void { } $reflection = new ReflectionClass(RuleRegistry::class); $loadRulesFromDirectoryMethod = $reflection->getMethod('loadRulesFromDirectory'); - $loadRulesFromDirectoryMethod->setAccessible(true); $loadRulesFromDirectoryMethod->invoke(null, $this->tempDir, 'TestRules'); $rulesProp = $reflection->getProperty('rules'); - $rulesProp->setAccessible(true); $this->assertEmpty($rulesProp->getValue()); } @@ -389,7 +359,6 @@ public function testDeriveRuleNameFromClassName(): void { $reflection = new ReflectionClass(RuleRegistry::class); $method = $reflection->getMethod('deriveRuleNameFromClassName'); - $method->setAccessible(true); $this->assertEquals('min', $method->invoke(null, 'YorCreative\\DataValidation\\Rules\\MinRule')); $this->assertEquals('required_if', $method->invoke(null, 'RequiredIfRule')); @@ -403,7 +372,6 @@ public function testGetClassNameFromFileWithBaseNamespace(): void $reflection = new ReflectionClass(RuleRegistry::class); $method = $reflection->getMethod('getClassNameFromFile'); - $method->setAccessible(true); $className = $method->invoke(null, $file, $this->tempDir, 'TestRules'); $this->assertEquals('TestRules\\TestRule', $className); @@ -416,7 +384,6 @@ public function testGetClassNameFromFileWithoutBaseNamespace(): void $reflection = new ReflectionClass(RuleRegistry::class); $method = $reflection->getMethod('getClassNameFromFile'); - $method->setAccessible(true); $className = $method->invoke(null, $file, $this->tempDir, null); $this->assertEquals('CustomRules\\TestRule', $className); @@ -429,7 +396,6 @@ public function testGetClassNameFromFileWithNoNamespace(): void $reflection = new ReflectionClass(RuleRegistry::class); $method = $reflection->getMethod('getClassNameFromFile'); - $method->setAccessible(true); $className = $method->invoke(null, $file, $this->tempDir, null); $this->assertNull($className); @@ -440,7 +406,6 @@ public function testGetClassNameFromFileWithNonExistentFile(): void $nonExistentFile = new SplFileInfo($this->tempDir . DIRECTORY_SEPARATOR . 'NonExistentRule.php'); $reflection = new ReflectionClass(RuleRegistry::class); $method = $reflection->getMethod('getClassNameFromFile'); - $method->setAccessible(true); $className = $method->invoke(null, $nonExistentFile, $this->tempDir, null); $this->assertNull($className); @@ -455,9 +420,8 @@ public function testGetClassNameFromFileWithNonRealPath(): void $reflection = new ReflectionClass(RuleRegistry::class); $method = $reflection->getMethod('getClassNameFromFile'); - $method->setAccessible(true); $className = $method->invoke(null, $nonRealFile, $this->tempDir, 'TestRules'); $this->assertNull($className); } -} \ No newline at end of file +} diff --git a/tests/Unit/ValidatorSiblingValueTest.php b/tests/Unit/ValidatorSiblingValueTest.php new file mode 100644 index 0000000..3121680 --- /dev/null +++ b/tests/Unit/ValidatorSiblingValueTest.php @@ -0,0 +1,250 @@ +getProperty('rules')->setValue(null, []); + $reflection->getProperty('closureRules')->setValue(null, []); + $reflection->getProperty('customRuleDirectories')->setValue(null, []); + $reflection->getProperty('isInitialized')->setValue(null, false); + + $validatorReflection = new ReflectionClass(Validator::class); + $validatorReflection->getProperty('parsedRulesCache')->setValue(null, []); + $validatorReflection->getProperty('cacheLimit')->setValue(null, 500); + } + + protected function tearDown(): void + { + gc_collect_cycles(); + parent::tearDown(); + } + + public function testInvalidFirstSiblingIsReportedWhenSecondIsValid(): void + { + $validator = Validator::make( + ['a' => 'not-an-email', 'b' => 'good@example.com'], + ['a' => 'email', 'b' => 'email'] + ); + + $this->assertFalse($validator->validate(), 'Invalid sibling "a" must fail validation'); + $this->assertArrayHasKey('a', $validator->errors(), 'Error expected for "a"'); + $this->assertArrayNotHasKey('b', $validator->errors(), 'No error expected for valid "b"'); + } + + public function testInvalidSecondSiblingIsReportedWhenFirstIsValid(): void + { + $validator = Validator::make( + ['a' => 'good@example.com', 'b' => 'not-an-email'], + ['a' => 'email', 'b' => 'email'] + ); + + $this->assertFalse($validator->validate(), 'Invalid sibling "b" must fail validation'); + $this->assertArrayHasKey('b', $validator->errors(), 'Error expected for "b"'); + $this->assertArrayNotHasKey('a', $validator->errors(), 'No error expected for valid "a"'); + } + + public function testResultsAreIndependentOfRuleOrdering(): void + { + $data = ['a' => 'not-an-email', 'b' => 'good@example.com']; + + $forward = Validator::make($data, ['a' => 'email', 'b' => 'email']); + $forward->validate(); + + $reversed = Validator::make($data, ['b' => 'email', 'a' => 'email']); + $reversed->validate(); + + $this->assertSame( + array_keys($forward->errors()), + array_keys($reversed->errors()), + 'Reversing rule order must not change which fields fail' + ); + $this->assertSame(['a'], array_keys($forward->errors())); + } + + public function testEachSiblingIsValidatedAgainstItsOwnValue(): void + { + $validator = Validator::make( + ['a' => 'bad-one', 'b' => 'bad-two', 'c' => 'good@example.com'], + ['a' => 'email', 'b' => 'email', 'c' => 'email'] + ); + + $validator->validate(); + + $this->assertSame( + ['a', 'b'], + array_keys($validator->errors()), + 'Both invalid siblings must fail even though the last sibling is valid' + ); + } + + public function testNestedSiblingsRetainTheirOwnValues(): void + { + $validator = Validator::make( + [ + 'user' => ['email' => 'not-an-email'], + 'admin' => ['email' => 'good@example.com'], + ], + [ + 'user.email' => 'email', + 'admin.email' => 'email', + ] + ); + + $validator->validate(); + + $this->assertArrayHasKey('user.email', $validator->errors()); + $this->assertArrayNotHasKey('admin.email', $validator->errors()); + } + + public function testWildcardRowsRetainTheirOwnValues(): void + { + $validator = Validator::make( + ['users' => [ + ['email' => 'not-an-email'], + ['email' => 'good@example.com'], + ]], + ['users.*.email' => 'email'] + ); + + $validator->validate(); + + $this->assertArrayHasKey('users.0.email', $validator->errors()); + $this->assertArrayNotHasKey('users.1.email', $validator->errors()); + } + + public function testWildcardRowsAlongsideScalarSiblingRule(): void + { + $validator = Validator::make( + [ + 'users' => [['email' => 'not-an-email'], ['email' => 'good@example.com']], + 'title' => 'a title', + ], + [ + 'users.*.email' => 'email', + 'title' => 'string', + ] + ); + + $validator->validate(); + + $this->assertArrayHasKey('users.0.email', $validator->errors()); + $this->assertArrayNotHasKey('users.1.email', $validator->errors()); + $this->assertArrayNotHasKey('title', $validator->errors()); + } + + public function testSameRuleComparesTheCorrectFieldsAlongsideSiblings(): void + { + $matching = Validator::make( + ['pw' => 'secret', 'pw_confirm' => 'secret', 'other' => 'bad-email'], + ['pw_confirm' => 'same:pw', 'other' => 'email'] + ); + $matching->validate(); + + $this->assertArrayNotHasKey('pw_confirm', $matching->errors(), '"same" must still match'); + $this->assertArrayHasKey('other', $matching->errors()); + + $mismatched = Validator::make( + ['pw' => 'secret', 'pw_confirm' => 'different', 'other' => 'ok@example.com'], + ['pw_confirm' => 'same:pw', 'other' => 'email'] + ); + $mismatched->validate(); + + $this->assertArrayHasKey('pw_confirm', $mismatched->errors(), '"same" must still detect a mismatch'); + $this->assertArrayNotHasKey('other', $mismatched->errors()); + } + + public function testDifferentRuleComparesTheCorrectFieldsAlongsideSiblings(): void + { + $distinct = Validator::make( + ['a' => 'x', 'b' => 'y', 'other' => 'bad-email'], + ['b' => 'different:a', 'other' => 'email'] + ); + $distinct->validate(); + + $this->assertArrayNotHasKey('b', $distinct->errors(), '"different" must still pass for distinct values'); + $this->assertArrayHasKey('other', $distinct->errors()); + + $identical = Validator::make( + ['a' => 'x', 'b' => 'x', 'other' => 'ok@example.com'], + ['b' => 'different:a', 'other' => 'email'] + ); + $identical->validate(); + + $this->assertArrayHasKey('b', $identical->errors(), '"different" must still detect identical values'); + $this->assertArrayNotHasKey('other', $identical->errors()); + } + + public function testConditionalRequiredRuleReadsTheCorrectFields(): void + { + $triggered = Validator::make( + ['type' => 'company', 'company_name' => null, 'contact' => 'bad-email'], + ['company_name' => 'required_if:type,company', 'contact' => 'email'] + ); + $triggered->validate(); + + $this->assertArrayHasKey('company_name', $triggered->errors(), 'required_if must trigger'); + $this->assertArrayHasKey('contact', $triggered->errors()); + + $notTriggered = Validator::make( + ['type' => 'person', 'company_name' => null, 'contact' => 'ok@example.com'], + ['company_name' => 'required_if:type,company', 'contact' => 'email'] + ); + $notTriggered->validate(); + + $this->assertEmpty($notTriggered->errors(), 'required_if must not trigger for a non-matching value'); + } + + public function testStopOnFirstErrorStillReportsTheFirstFailingField(): void + { + $validator = Validator::make( + ['a' => 'not-an-email', 'b' => 'also-bad'], + ['a' => 'email', 'b' => 'email'], + [], + [], + true + ); + + $this->assertFalse($validator->validate(), 'stopOnFirstError must still fail'); + $this->assertCount(1, $validator->errors(), 'stopOnFirstError must report exactly one field'); + $this->assertArrayHasKey('a', $validator->errors()); + } + + public function testDeeplyNestedSiblingBranchesDoNotShareValues(): void + { + $validator = Validator::make( + [ + 'a' => ['b' => ['c' => 'not-an-email']], + 'x' => ['y' => ['z' => 'good@example.com']], + ], + [ + 'a.b.c' => 'email', + 'x.y.z' => 'email', + ] + ); + + $validator->validate(); + + $this->assertArrayHasKey('a.b.c', $validator->errors()); + $this->assertArrayNotHasKey('x.y.z', $validator->errors()); + } +} diff --git a/tests/Unit/ValidatorTest.php b/tests/Unit/ValidatorTest.php index 9323d41..9d9ae32 100644 --- a/tests/Unit/ValidatorTest.php +++ b/tests/Unit/ValidatorTest.php @@ -6,7 +6,10 @@ use InvalidArgumentException; use PHPUnit\Framework\TestCase; use ReflectionClass; +use RuntimeException; +use YorCreative\DataValidation\DataValidationConfig; use YorCreative\DataValidation\RuleRegistry; +use YorCreative\DataValidation\Rules\ValidationRuleInterface; use YorCreative\DataValidation\Validator; class ValidatorTest extends TestCase @@ -18,20 +21,23 @@ protected function setUp(): void // Reset RuleRegistry to ensure it loads actual rules $reflection = new ReflectionClass(RuleRegistry::class); $rulesProp = $reflection->getProperty('rules'); - $rulesProp->setAccessible(true); - $rulesProp->setValue([]); + $rulesProp->setValue(null, []); $closureRulesProp = $reflection->getProperty('closureRules'); - $closureRulesProp->setAccessible(true); - $closureRulesProp->setValue([]); + $closureRulesProp->setValue(null, []); $dirsProp = $reflection->getProperty('customRuleDirectories'); - $dirsProp->setAccessible(true); - $dirsProp->setValue([]); + $dirsProp->setValue(null, []); $initProp = $reflection->getProperty('isInitialized'); - $initProp->setAccessible(true); - $initProp->setValue(false); + $initProp->setValue(null, false); + + // Validator keeps the parsed-rules cache AND its limit in statics. + // A test that lowers the limit would otherwise leak it into every + // test that follows in this file. + $validatorReflection = new ReflectionClass(Validator::class); + $validatorReflection->getProperty('parsedRulesCache')->setValue(null, []); + $validatorReflection->getProperty('cacheLimit')->setValue(null, 500); } protected function tearDown(): void @@ -55,7 +61,6 @@ public function testAddErrorMessageByRuleNameWithUnknownRule(): void $validator = Validator::make(['field' => 'value'], []); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('addErrorMessageByRuleName'); - $method->setAccessible(true); $method->invoke($validator, 'field', 'unknown', []); $this->assertArrayHasKey('field', $validator->errors()); $this->assertStringContainsString('The field is invalid (unknown rule unknown)', $validator->errors()['field'][0]); @@ -229,7 +234,6 @@ public function testParseRulesWithEmptyString(): void $validator = Validator::make([], ['field' => '']); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('getParsedRules'); - $method->setAccessible(true); $this->assertEquals([], $method->invoke($validator, '')); } @@ -238,7 +242,6 @@ public function testParseRulesWithMultipleRules(): void $validator = Validator::make([], ['field' => 'required|min:5']); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('getParsedRules'); - $method->setAccessible(true); $this->assertEquals(['required', 'min:5'], $method->invoke($validator, 'required|min:5')); } @@ -275,7 +278,6 @@ public function testApplyRulesWithUnknownRule(): void $validator = Validator::make(['field' => 'value'], []); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('applyRules'); - $method->setAccessible(true); $method->invoke($validator, 'field', 'value', ['unknown'], 'field', $validator->getData()); $this->assertArrayHasKey('field', $validator->errors()); $this->assertStringContainsString("Validation rule 'unknown' is not defined.", $validator->errors()['field'][0]); @@ -286,7 +288,6 @@ public function testApplyRulesWithNonStringNonClosureRule(): void $validator = Validator::make(['field' => 'value'], []); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('applyRules'); - $method->setAccessible(true); $method->invoke($validator, 'field', 'value', [new \stdClass()], 'field', $validator->getData()); $this->assertEmpty($validator->errors()); } @@ -334,7 +335,6 @@ public function testResolveOtherFieldPathForComparisonWithWildcards(): void $validator = Validator::make([], []); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('resolveOtherFieldPathForComparison'); - $method->setAccessible(true); $result = $method->invoke($validator, 'users.*.status', 'users.0.profile.email'); $this->assertEquals('users.0.status', $result); } @@ -344,7 +344,6 @@ public function testResolveOtherFieldPathForComparisonWithoutWildcards(): void $validator = Validator::make([], []); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('resolveOtherFieldPathForComparison'); - $method->setAccessible(true); $result = $method->invoke($validator, 'user.status', 'user.profile.email'); $this->assertEquals('user.status', $result); } @@ -354,7 +353,6 @@ public function testResolveOtherFieldPathForComparisonWithMismatchedSegments(): $validator = Validator::make([], []); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('resolveOtherFieldPathForComparison'); - $method->setAccessible(true); $result = $method->invoke($validator, 'other.*.data', 'user.profile'); $this->assertEquals('other.profile.data', $result); } @@ -364,7 +362,6 @@ public function testFormatMessageWithMinRule(): void $validator = Validator::make([], []); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('formatMessage'); - $method->setAccessible(true); $template = 'The :attribute must be at least :min.'; $result = $method->invoke($validator, $template, 'age', 'min', ['18']); $this->assertEquals('The age must be at least 18.', $result); @@ -375,7 +372,6 @@ public function testFormatMessageWithBetweenRule(): void $validator = Validator::make([], []); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('formatMessage'); - $method->setAccessible(true); $template = 'The :attribute must be between :min and :max.'; $result = $method->invoke($validator, $template, 'age', 'between', ['18', '65']); $this->assertEquals('The age must be between 18 and 65.', $result); @@ -386,7 +382,6 @@ public function testFormatMessageWithInRule(): void $validator = Validator::make([], ['status' => 'in:active,inactive']); $reflection = new \ReflectionClass($validator); $method = $reflection->getMethod('formatMessage'); - $method->setAccessible(true); $message = $method->invoke($validator, 'The :attribute must be one of: :values.', 'status', 'in', ['active', 'inactive']); $this->assertEquals('The status must be one of: active, inactive.', $message); } @@ -396,7 +391,6 @@ public function testFormatMessageWithSameRule(): void $validator = Validator::make([], [], [], ['other' => 'Other Field']); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('formatMessage'); - $method->setAccessible(true); $template = 'The :attribute must match :other.'; $result = $method->invoke($validator, $template, 'field', 'same', ['other']); $this->assertEquals('The field must match Other Field.', $result); @@ -407,7 +401,6 @@ public function testFormatMessageSanityCheck(): void $validator = Validator::make([], []); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('formatMessage'); - $method->setAccessible(true); $template = ':values'; $ruleParameters = ['active', 'inactive']; @@ -428,7 +421,6 @@ public function testAddErrorMessageByRuleNameWithArrayRule(): void $validator = Validator::make(['field' => 'value'], []); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('addErrorMessageByRuleName'); - $method->setAccessible(true); $method->invoke($validator, 'field', 'array', []); $this->assertArrayHasKey('field', $validator->errors()); $this->assertStringContainsString('The field must be an array.', $validator->errors()['field'][0]); @@ -502,7 +494,6 @@ public function testGetFieldValueCaching(): void $validator->getFieldValue('user.name'); $reflection = new ReflectionClass($validator); $cacheProp = $reflection->getProperty('fieldValueCache'); - $cacheProp->setAccessible(true); $cache = $cacheProp->getValue($validator); $this->assertArrayHasKey('user.name', $cache); $this->assertEquals('John', $cache['user.name']); @@ -515,6 +506,14 @@ public function testFailsWithErrors(): void $this->assertTrue($validator->fails()); } + public function testFailsTriggersValidationOnFirstUse(): void + { + $validator = Validator::make([], ['name' => 'required']); + + $this->assertTrue($validator->fails()); + $this->assertArrayHasKey('name', $validator->errors()); + } + public function testFailsWithoutErrors(): void { $validator = Validator::make(['name' => 'John'], ['name' => 'required']); @@ -529,6 +528,187 @@ public function testPassesWithNoErrors(): void $this->assertTrue($validator->passes()); } + public function testPassesTriggersValidationOnFirstUse(): void + { + // Invalid data: only a lazily-validating passes() can return false here. + // v1.0 returned true because the errors array had not been populated yet. + $validator = Validator::make([], ['name' => 'required']); + + $this->assertFalse($validator->passes()); + $this->assertArrayHasKey('name', $validator->errors()); + } + + public function testValidationStateSurvivesSerializationRoundTrip(): void + { + $validator = Validator::make([], ['name' => 'required']); + $validator->validate(); + + $restored = unserialize(serialize($validator)); + + // Guards __serialize()/__unserialize() actually round-tripping the + // hasValidated flag itself: without it, an unserialized instance + // defaults hasValidated to false, fails() silently re-runs + // validate(), and the assertions below would still pass even though + // the flag was never carried across the round trip. + $reflection = new ReflectionClass($restored); + $this->assertTrue( + $reflection->getProperty('hasValidated')->getValue($restored), + 'hasValidated must be restored as true by the serialization round trip.' + ); + + $this->assertTrue($restored->fails()); + $this->assertArrayHasKey('name', $restored->errors()); + } + + public function testHasValidatedStaysFalseWhenARuleThrowsMidValidation(): void + { + // Force RuleRegistry to finish its normal discovery pass first, so + // injecting a throwing rule below does not get wiped out by a + // deferred loadRules() the next time a rule is looked up. + RuleRegistry::hasRule('required'); + + $reflection = new ReflectionClass(RuleRegistry::class); + $rulesProp = $reflection->getProperty('rules'); + $rules = $rulesProp->getValue(); + $rules['throwing_test_rule'] = new class implements ValidationRuleInterface { + public function validate(string $field, $value, array $parameters, array $data): bool + { + throw new RuntimeException('boom mid-validation'); + } + + public function getErrorMessage(string $field, array $parameters, ?string $customMessage): string + { + return ''; + } + + public function validateParameters(string $field, array $parameters): void + { + } + }; + $rulesProp->setValue(null, $rules); + + $validator = Validator::make(['name' => 'John'], ['name' => 'throwing_test_rule']); + + $threw = false; + try { + $validator->validate(); + } catch (RuntimeException $e) { + $threw = true; + } + $this->assertTrue($threw, 'Expected the injected rule to throw mid-validation.'); + + $validatorReflection = new ReflectionClass($validator); + $hasValidatedProp = $validatorReflection->getProperty('hasValidated'); + $this->assertFalse( + $hasValidatedProp->getValue($validator), + 'hasValidated must stay false when validate() never completed.' + ); + + // A later passes() call must re-attempt validation (and therefore + // re-throw against this still-broken rule set) rather than silently + // reporting success from a stale, empty errors array. + $threwAgain = false; + try { + $validator->passes(); + } catch (RuntimeException $e) { + $threwAgain = true; + } + $this->assertTrue( + $threwAgain, + 'passes() must re-attempt validation rather than report success after an incomplete run.' + ); + } + + public function testHasValidatedIsResetWhenARevalidationRunThrows(): void + { + // The sibling test above covers a rule that throws on the very first + // run, where hasValidated was false to begin with. This covers the + // case the flag actually has to be reset for: a run that COMPLETED + // successfully, followed by a run that begins and then throws. If + // validate() does not clear hasValidated when the new run begins, the + // stale true from run one outlives the failed run two -- and because + // run two already emptied $errors, passes() reports success for a + // validation that never finished. + RuleRegistry::hasRule('required'); + + $reflection = new ReflectionClass(RuleRegistry::class); + $rulesProp = $reflection->getProperty('rules'); + $rules = $rulesProp->getValue(); + $rules['flaky_test_rule'] = new class implements ValidationRuleInterface { + private int $calls = 0; + + public function validate(string $field, $value, array $parameters, array $data): bool + { + if ($this->calls++ === 0) { + return true; + } + throw new RuntimeException('boom on revalidation'); + } + + public function getErrorMessage(string $field, array $parameters, ?string $customMessage): string + { + return ''; + } + + public function validateParameters(string $field, array $parameters): void + { + } + }; + $rulesProp->setValue(null, $rules); + + $validator = Validator::make(['name' => 'John'], ['name' => 'flaky_test_rule']); + $validatorReflection = new ReflectionClass($validator); + $hasValidatedProp = $validatorReflection->getProperty('hasValidated'); + + $this->assertTrue($validator->validate(), 'The first run must complete successfully.'); + $this->assertTrue( + $hasValidatedProp->getValue($validator), + 'hasValidated must be true after a completed run.' + ); + + $threw = false; + try { + $validator->validate(); + } catch (RuntimeException $e) { + $threw = true; + } + $this->assertTrue($threw, 'The second run must throw.'); + + $this->assertFalse( + $hasValidatedProp->getValue($validator), + 'hasValidated must be cleared once a new run begins and must not survive a run that threw.' + ); + + $threwAgain = false; + try { + $validator->passes(); + } catch (RuntimeException $e) { + $threwAgain = true; + } + $this->assertTrue( + $threwAgain, + 'passes() must retry and re-throw rather than report cached success from the earlier run.' + ); + } + + public function testRepeatedPassesCallsValidateOnlyOnce(): void + { + $validator = Validator::make(['name' => 'John'], ['name' => 'required']); + + $this->assertTrue($validator->passes()); + + $reflection = new ReflectionClass($validator); + $this->assertTrue($reflection->getProperty('hasValidated')->getValue($validator)); + } + + public function testPassesReturnsTrueForValidDataWithoutExplicitValidateCall(): void + { + $validator = Validator::make(['name' => 'John'], ['name' => 'required']); + + $this->assertTrue($validator->passes()); + $this->assertEmpty($validator->errors()); + } + public function testPassesWithErrors(): void { $validator = Validator::make([], ['name' => 'required']); @@ -583,7 +763,6 @@ public function testDynamicChunkSizeForSmallDataset(): void $validator = Validator::make([], []); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('determineChunkSize'); - $method->setAccessible(true); $chunkSize = $method->invoke($validator, 500); $this->assertEquals(500, $chunkSize, 'Chunk size should equal dataset size for small datasets'); @@ -594,7 +773,6 @@ public function testDynamicChunkSizeForMediumDataset(): void $validator = Validator::make([], []); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('determineChunkSize'); - $method->setAccessible(true); $chunkSize = $method->invoke($validator, 2000); $this->assertEquals(1000, $chunkSize, 'Chunk size should be 1000 for medium datasets'); @@ -605,12 +783,51 @@ public function testDynamicChunkSizeForLargeDataset(): void $validator = Validator::make([], []); $reflection = new ReflectionClass($validator); $method = $reflection->getMethod('determineChunkSize'); - $method->setAccessible(true); $chunkSize = $method->invoke($validator, 5000); $this->assertEquals(500, $chunkSize, 'Chunk size should be 500 for large datasets'); } + public function testDynamicChunkSizeIsUsedWhenConfiguredChunkSizeIsNull(): void + { + $config = new DataValidationConfig(); + $config->chunkSize = null; + + $validator = Validator::make([], [], [], [], false, $config); + $reflection = new ReflectionClass($validator); + $method = $reflection->getMethod('determineChunkSize'); + + $chunkSize = $method->invoke($validator, 2000); + $this->assertEquals(1000, $chunkSize, 'Dynamic chunk sizing should be used when config chunk size is null'); + } + + public function testConfiguredChunkSizeIsNormalizedToAtLeastOne(): void + { + $config = new DataValidationConfig(); + $config->chunkSize = 0; + + $validator = Validator::make([], [], [], [], false, $config); + $reflection = new ReflectionClass($validator); + $method = $reflection->getMethod('determineChunkSize'); + + $chunkSize = $method->invoke($validator, 2000); + $this->assertEquals(1, $chunkSize, 'Configured chunk size should be clamped to a positive value'); + } + + // Depends on phpunit.xml's failOnWarning="true": the behaviour this test + // covers has no value-level observable, only a warning that would + // otherwise pass silently. + public function testDeterminesChunkSizeDynamicallyWhenNoConfigProvided(): void + { + $validator = Validator::make([], []); + + $reflection = new ReflectionClass($validator); + $method = $reflection->getMethod('determineChunkSize'); + + $this->assertSame(1000, $method->invoke($validator, 2000)); + $this->assertSame(500, $method->invoke($validator, 5000)); + } + public function testValidateWithDynamicChunkSize(): void { $data = ['users' => array_fill(0, 1500, ['name' => 'John'])]; @@ -632,4 +849,107 @@ public function testValidatorIsValid(): void $rules = ['name' => 'required|string']; $this->assertTrue(Validator::isValid($data, $rules)); } -} \ No newline at end of file + + /** + * NOTE: despite its name, this does not prove that isValid() threads the + * config through to the Validator -- it passes even against unfixed code + * that silently drops the config argument, since chunkSize = null is + * also the Validator's default. It only exercises the null-chunk-size + * smoke path (that isValid() accepts a DataValidationConfig argument at + * all without erroring). See + * testIsValidThreadsTheConfigThroughToTheValidator() for the test that + * actually proves the config is forwarded. + */ + public function testValidatorIsValidSupportsConfigObject(): void + { + $data = ['users' => [['name' => 'John']]]; + $rules = ['users.*.name' => 'required|string']; + $config = new DataValidationConfig(); + $config->chunkSize = null; + + $this->assertTrue(Validator::isValid($data, $rules, [], [], false, $config)); + } + + public function testIsValidThreadsTheConfigThroughToTheValidator(): void + { + // isValid() returns bool, so no instance is reachable to inspect. + // The Validator constructor copies parsedRulesCache into a STATIC + // cache limit, and that side effect outlives the call -- it is the + // only way to prove isValid() forwarded its config, because PHP + // silently discards extra arguments and a dropped config leaves no + // other trace. setUp() resets this static to 500. + // + // NOTE: this couples the test to the static-leak behaviour that + // Workstream 2.1 is scheduled to remove. When $cacheLimit becomes + // instance-scoped, this test must be revisited. + $reflection = new ReflectionClass(Validator::class); + $cacheLimit = $reflection->getProperty('cacheLimit'); + + $config = new DataValidationConfig(); + $config->parsedRulesCache = 77; + + Validator::isValid(['name' => 'John'], ['name' => 'required'], [], [], false, $config); + + $this->assertSame( + 77, + $cacheLimit->getValue(), + 'isValid() must forward its config to the Validator instance.' + ); + } + + public function testParsedRulesCacheEvictionKeepsTheCacheBounded(): void + { + $config = new DataValidationConfig(); + + // A limit of 1 is the value at which the implementations diverge. + // v1.0 computes -1/2 = -0.5, which truncates to offset 0, so + // array_slice() returns the WHOLE array and the cache never shrinks. + // The fix computes max(1, intdiv(1, 2)) = 1 and retains one entry. + $config->parsedRulesCache = 1; + + $validator = Validator::make([], [], [], [], false, $config); + $validator->clearParsedRulesCache(); + + $reflection = new ReflectionClass($validator); + $method = $reflection->getMethod('getParsedRules'); + + // Push more distinct rule strings through than the cache can hold. + for ($i = 0; $i < 12; $i++) { + $method->invoke($validator, "required|string|min:{$i}"); + } + + $cache = $reflection->getProperty('parsedRulesCache')->getValue(); + + $this->assertLessThanOrEqual( + 2, + count($cache), + 'Cache must stay bounded. v1.0 grew to 12 because a float offset of -0.5 truncated to 0.' + ); + $this->assertNotEmpty($cache); + } + + public function testReentrantPassesCallDoesNotRestartValidation(): void + { + $validator = null; + $depth = 0; + + RuleRegistry::registerClosureRule('reentrant_probe', function () use (&$validator, &$depth) { + $depth++; + // Bound the recursion so an unfixed implementation fails the + // assertion instead of exhausting the stack. + if ($depth < 5 && $validator !== null) { + $validator->passes(); + } + return true; + }); + + $validator = Validator::make(['a' => 1], ['a' => 'reentrant_probe']); + $validator->validate(); + + $this->assertSame( + 1, + $depth, + 'A rule calling passes() on its own validator must not restart validation.' + ); + } +} diff --git a/xdebug.ini b/xdebug.ini index ea218d4..288668d 100644 --- a/xdebug.ini +++ b/xdebug.ini @@ -1,5 +1,4 @@ ; docker/xdebug.ini -zend_extension=xdebug.so xdebug.mode=coverage xdebug.start_with_request=no -xdebug.discover_client_host=1 \ No newline at end of file +xdebug.discover_client_host=1