diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b2aa2dd..307abf3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,11 +1,12 @@ version: 2 updates: -- package-ecosystem: composer - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 - ignore: - - dependency-name: phpstan/phpstan - versions: - - 0.12.70 + - package-ecosystem: composer + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + ignore: + - dependency-name: phpunit/phpunit + update-types: ["version-update:semver-patch"] + - dependency-name: phpstan/phpstan + update-types: ["version-update:semver-patch"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27cc5d8..19f7612 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,66 +1,171 @@ name: CI -on: [push] +on: + push: + pull_request: + +permissions: + contents: read + actions: read jobs: composer: runs-on: ubuntu-latest + strategy: + matrix: + php: [ 8.2, 8.3, 8.4, 8.5 ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Cache Composer dependencies - uses: actions/cache@v2 + uses: actions/cache@v5 with: path: /tmp/composer-cache - key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }} + key: ${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }} - - uses: php-actions/composer@v5 + - name: Composer install + uses: php-actions/composer@v6 + with: + php_version: ${{ matrix.php }} + php_extensions: pcntl - name: Archive build - run: mkdir /tmp/github-actions/ && tar -cvf /tmp/github-actions/build.tar ./ + run: mkdir /tmp/github-actions/ && tar --exclude=".git" -cvf /tmp/github-actions/build.tar ./ - name: Upload build archive for test runners - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: - name: build-artifact + name: build-artifact-${{ matrix.php }} path: /tmp/github-actions + retention-days: 1 phpunit: runs-on: ubuntu-latest - needs: [composer] + needs: [ composer ] + strategy: + matrix: + php: [ 8.2, 8.3, 8.4, 8.5 ] + + outputs: + coverage: ${{ steps.store-coverage.outputs.coverage_text }} steps: - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: - name: build-artifact + name: build-artifact-${{ matrix.php }} path: /tmp/github-actions - name: Extract build archive run: tar -xvf /tmp/github-actions/build.tar ./ - name: PHP Unit tests - uses: php-actions/phpunit@v2 + uses: php-actions/phpunit@v4 + env: + XDEBUG_MODE: cover with: - php_version: 8.0 + php_version: ${{ matrix.php }} php_extensions: xdebug - configuration: test/phpunit/phpunit.xml - bootstrap: vendor/autoload.php + coverage_text: _coverage/coverage.txt + coverage_clover: _coverage/clover.xml + + - name: Store coverage data + uses: actions/upload-artifact@v4 + with: + name: code-coverage-${{ matrix.php }}-${{ github.run_number }} + path: _coverage + + coverage: + runs-on: ubuntu-latest + needs: [ phpunit ] + strategy: + matrix: + php: [ 8.2, 8.3, 8.4, 8.5 ] + + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + name: code-coverage-${{ matrix.php }}-${{ github.run_number }} + path: _coverage + + - name: Output coverage + run: cat "_coverage/coverage.txt" + + - name: Upload to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: ${{ github.repository }} phpstan: runs-on: ubuntu-latest - needs: [composer] + needs: [ composer ] + strategy: + matrix: + php: [ 8.2, 8.3, 8.4, 8.5 ] steps: - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: - name: build-artifact + name: build-artifact-${{ matrix.php }} path: /tmp/github-actions - name: Extract build archive run: tar -xvf /tmp/github-actions/build.tar ./ - name: PHP Static Analysis - uses: php-actions/phpstan@v2 + uses: php-actions/phpstan@v3 + with: + php_version: ${{ matrix.php }} + path: src/ + level: 6 + memory_limit: 256M + + phpmd: + runs-on: ubuntu-latest + needs: [ composer ] + strategy: + matrix: + php: [ 8.2, 8.3, 8.4, 8.5 ] + + steps: + - uses: actions/download-artifact@v4 + with: + name: build-artifact-${{ matrix.php }} + path: /tmp/github-actions + + - name: Extract build archive + run: tar -xvf /tmp/github-actions/build.tar ./ + + - name: PHP Mess Detector + uses: php-actions/phpmd@v1 + with: + php_version: ${{ matrix.php }} + path: src/ + output: text + ruleset: phpmd.xml + + phpcs: + runs-on: ubuntu-latest + needs: [ composer ] + strategy: + matrix: + php: [ 8.2, 8.3, 8.4, 8.5 ] + + steps: + - uses: actions/download-artifact@v4 + with: + name: build-artifact-${{ matrix.php }} + path: /tmp/github-actions + + - name: Extract build archive + run: tar -xvf /tmp/github-actions/build.tar ./ + + - name: PHP Code Sniffer + uses: php-actions/phpcs@v1 with: - path: src/ \ No newline at end of file + php_version: ${{ matrix.php }} + path: src/ + standard: phpcs.xml diff --git a/.gitignore b/.gitignore index 3c5399a..e38702b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /vendor -/test/unit/_coverage +/test/phpunit/_coverage +/test/phpunit/.phpunit.cache /.idea -*.phar \ No newline at end of file +*.phar diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index 0e7d790..0000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,32 +0,0 @@ -build: - environment: - php: 8.0.0 - - nodes: - analysis: - tests: - override: - - php-scrutinizer-run - - tests-and-coverage: - environment: - php: - ini: - "xdebug.mode": coverage - tests: - override: - - phpcs-run ./src - - command: "vendor/bin/phpunit test/phpunit --coverage-clover test/phpunit/_coverage --whitelist src" - coverage: - file: "test/phpunit/_coverage" - format: "php-clover" - -checks: - php: - code_rating: true - duplication: true - -filter: - excluded_paths: - - test/* - - vendor/* \ No newline at end of file diff --git a/README.md b/README.md index c2f5e6a..6cac016 100644 --- a/README.md +++ b/README.md @@ -9,17 +9,17 @@ Sessions are addressed using dot notation, allowing for handling categories of s Build status - + Code quality - + Code coverage Current version - PHP.Gt/Session documentation + PHP.GT/Session documentation ## Example usage: Welcome a user by their first name or log out the user @@ -41,3 +41,46 @@ else { AuthenticationSystem::beginLogin($session->getStore("auth")); } ``` + +## Redis session storage + +This package now includes `GT\Session\RedisHandler` for shared session storage. +It works with Redis-compatible backends such as Redis and Valkey, and is intended +for deployments where application nodes are disposable and session state needs to +survive traffic moving between servers. + +`RedisHandler` expects `save_path` to be a DSN rather than a filesystem path. +It uses the `phpredis` extension at runtime. + +Example production config: + +```ini +[session] +handler=GT\Session\RedisHandler +save_path=rediss://default:secret@example-redis.internal:25061/0?prefix=GT:&ttl=1440 +name=GT +use_cookies=true +``` + +Supported DSN forms: + +- `redis://host:6379` +- `redis://:password@host:6379/0` +- `redis://username:password@host:6379/0` +- `rediss://username:password@host:6379/0` + +Useful query parameters: + +- `prefix`: key prefix for stored sessions, defaults to `:` +- `ttl`: session lifetime in seconds, defaults to `session.gc_maxlifetime` +- `timeout`: connection timeout in seconds +- `read_timeout`: socket read timeout in seconds +- `persistent=1`: enable persistent connections +- `persistent_id`: optional persistent connection pool id +- `verify_peer=0` / `verify_peer_name=0`: optional TLS verification flags + +# Proudly sponsored by + +[JetBrains Open Source sponsorship program](https://www.jetbrains.com/community/opensource/) + +[![JetBrains logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](https://www.jetbrains.com/community/opensource/) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..ee9f073 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,25 @@ +# Security Policy + +## Supported Versions + +All MAJOR versions of this package will receive security updates for **two years after the next major version is released**. For example, if version 4.0.0 is released, version 3.x will continue receiving security updates for two years from that date. + +Versions outside this window are considered end-of-life and will no longer receive updates, even for critical vulnerabilities. + +## Reporting a Vulnerability + +If you discover a security issue, please report it using GitHub's [**"Report a vulnerability"** feature](../../security/advisories/new) under the **Security** tab of this repository. + +When reporting, please include the following information to help us investigate quickly and thoroughly: + +- A clear description of the vulnerability and what part of the code it affects. +- Steps to reproduce the issue, ideally including: + - The affected version + - A code snippet or minimal test case + - The expected vs. actual behavior +- If applicable, an explanation of potential impact or severity. +- Any suggested mitigations or patches (optional, but appreciated). + +Please do not disclose the vulnerability publicly until we've had a chance to investigate and publish a fix. + +We appreciate responsible disclosure and are committed to resolving issues promptly. diff --git a/composer.json b/composer.json index f78bd5a..d11c730 100644 --- a/composer.json +++ b/composer.json @@ -4,21 +4,48 @@ "license": "MIT", "require": { - "php": ">=7.2" + "php": ">=8.2", + "phpgt/typesafegetter": "^1.3" + }, + "suggest": { + "ext-redis": "Required to use GT\\Session\\RedisHandler." }, "require-dev": { - "phpunit/phpunit": "9.*", - "phpstan/phpstan": ">=0.12.64" + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^10.1", + "phpmd/phpmd": "^2.13", + "squizlabs/php_codesniffer": "^3.7" + }, + + "scripts": { + "phpunit": "vendor/bin/phpunit --configuration phpunit.xml", + "phpstan": "vendor/bin/phpstan analyse --level 6 src --memory-limit 256M", + "phpcs": "vendor/bin/phpcs src --standard=phpcs.xml", + "phpmd": "vendor/bin/phpmd src/ text phpmd.xml", + "test": [ + "@phpunit", + "@phpstan", + "@phpcs", + "@phpmd" + ] }, "autoload": { "psr-4": { + "GT\\Session\\": "./src", "Gt\\Session\\": "./src" } }, "autoload-dev": { "psr-4": { - "Gt\\Session\\Test\\": "./test/phpunit" + "GT\\Session\\Test\\": "./test/phpunit" + } + }, + + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/PhpGt" } - } -} \ No newline at end of file + ] +} diff --git a/composer.lock b/composer.lock index 909dfba..807a21f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,39 +4,169 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bb32584e49fdaeea326c284a7294f289", - "packages": [], + "content-hash": "5bf8b129b89ebdbd2f4b53a66a2b077e", + "packages": [ + { + "name": "phpgt/typesafegetter", + "version": "v1.3.3", + "source": { + "type": "git", + "url": "https://github.com/phpgt/TypeSafeGetter.git", + "reference": "a0d339103828791989cbb81f760d252f3c2f8b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpgt/TypeSafeGetter/zipball/a0d339103828791989cbb81f760d252f3c2f8b8c", + "reference": "a0d339103828791989cbb81f760d252f3c2f8b8c", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "phpmd/phpmd": "^2.13", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.1", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Gt\\TypeSafeGetter\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Bowler", + "email": "greg.bowler@g105b.com" + } + ], + "description": "An interface for objects that expose type-safe getter methods.", + "support": { + "issues": "https://github.com/phpgt/TypeSafeGetter/issues", + "source": "https://github.com/phpgt/TypeSafeGetter/tree/v1.3.3" + }, + "funding": [ + { + "url": "https://github.com/sponsors/PhpGt", + "type": "github" + } + ], + "time": "2026-03-10T22:28:01+00:00" + } + ], "packages-dev": [ { - "name": "doctrine/instantiator", - "version": "1.4.0", + "name": "composer/pcre", + "version": "3.3.2", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" }, "require-dev": { - "doctrine/coding-standard": "^8.0", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "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" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + "Composer\\XdebugHandler\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -45,66 +175,71 @@ ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" } ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "description": "Restarts a process without Xdebug.", "keywords": [ - "constructor", - "instantiate" + "Xdebug", + "performance" ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, "funding": [ { - "url": "https://www.doctrine-project.org/sponsorship.html", + "url": "https://packagist.com", "type": "custom" }, { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" + "url": "https://github.com/composer", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], - "time": "2020-11-10T18:47:58+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.10.2", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -118,35 +253,41 @@ "object", "object graph" ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, "funding": [ { "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", "type": "tidelift" } ], - "time": "2020-11-13T09:40:50+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v4.10.4", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -154,7 +295,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -176,24 +317,92 @@ "parser", "php" ], - "time": "2020-12-20T10:01:03+00:00" + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "pdepend/pdepend", + "version": "2.16.2", + "source": { + "type": "git", + "url": "https://github.com/pdepend/pdepend.git", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "shasum": "" + }, + "require": { + "php": ">=5.3.7", + "symfony/config": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/polyfill-mbstring": "^1.19" + }, + "require-dev": { + "easy-doc/easy-doc": "0.0.0|^1.2.3", + "gregwar/rst": "^1.0", + "squizlabs/php_codesniffer": "^2.0.0" + }, + "bin": [ + "src/bin/pdepend" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "PDepend\\": "src/main/php/PDepend" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Official version of pdepend to be handled with Composer", + "keywords": [ + "PHP Depend", + "PHP_Depend", + "dev", + "pdepend" + ], + "support": { + "issues": "https://github.com/pdepend/pdepend/issues", + "source": "https://github.com/pdepend/pdepend/tree/2.16.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend", + "type": "tidelift" + } + ], + "time": "2023-12-17T18:09:59+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.1", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -232,20 +441,30 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2020-06-27T14:33:11+00:00" + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", - "version": "3.1.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "bae7c545bef187884426f042434e561ab1ddb182" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", - "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { @@ -279,313 +498,315 @@ } ], "description": "Library for handling version information and constraints", - "time": "2021-02-23T14:00:09+00:00" + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", + "name": "phpmd/phpmd", + "version": "2.15.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + "url": "https://github.com/phpmd/phpmd.git", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", + "ext-xml": "*", + "pdepend/pdepend": "^2.16.1", + "php": ">=5.3.9" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - } + "require-dev": { + "easy-doc/easy-doc": "0.0.0 || ^1.3.2", + "ext-json": "*", + "ext-simplexml": "*", + "gregwar/rst": "^1.0", + "mikey179/vfsstream": "^1.6.8", + "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2" }, + "bin": [ + "src/bin/phpmd" + ], + "type": "library", "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" + "psr-0": { + "PHPMD\\": "src/main/php" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" + "name": "Manuel Pichler", + "email": "github@manuel-pichler.de", + "homepage": "https://github.com/manuelpichler", + "role": "Project Founder" + }, + { + "name": "Marc Würth", + "email": "ravage@bluewin.ch", + "homepage": "https://github.com/ravage84", + "role": "Project Maintainer" + }, + { + "name": "Other contributors", + "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", + "role": "Contributors" } ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", + "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", + "homepage": "https://phpmd.org/", "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" + "dev", + "mess detection", + "mess detector", + "pdepend", + "phpmd", + "pmd" + ], + "support": { + "irc": "irc://irc.freenode.org/phpmd", + "issues": "https://github.com/phpmd/phpmd/issues", + "source": "https://github.com/phpmd/phpmd/tree/2.15.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd", + "type": "tidelift" + } ], - "time": "2020-06-27T09:03:43+00:00" + "time": "2023-12-11T08:22:20+00:00" }, { - "name": "phpdocumentor/reflection-docblock", - "version": "5.2.2", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" - }, + "name": "phpstan/phpstan", + "version": "2.1.51", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc3b523c45e714c70de2ac5113b958223b55dc59", + "reference": "dc3b523c45e714c70de2ac5113b958223b55dc59", "shasum": "" }, "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" + "php": "^7.4|^8.0" }, - "require-dev": { - "mockery/mockery": "~1.3.2" + "conflict": { + "phpstan/phpstan-shim": "*" }, + "bin": [ + "phpstan", + "phpstan.phar" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } + "files": [ + "bootstrap.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "url": "https://github.com/ondrejmirtes", + "type": "github" }, { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "url": "https://github.com/phpstan", + "type": "github" } ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2020-09-03T19:13:55+00:00" + "time": "2026-04-21T18:22:01+00:00" }, { - "name": "phpdocumentor/type-resolver", - "version": "1.4.0", + "name": "phpunit/php-code-coverage", + "version": "10.1.16", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "ext-tokenizer": "*" + "phpunit/phpunit": "^10.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-1.x": "1.x-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "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/10.1.16" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2020-09-17T18:55:26+00:00" + "time": "2024-08-22T04:31:57+00:00" }, { - "name": "phpspec/prophecy", - "version": "1.13.0", + "name": "phpunit/php-file-iterator", + "version": "4.1.0", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.1", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" + "php": ">=8.1" }, "require-dev": { - "phpspec/phpspec": "^6.0", - "phpunit/phpunit": "^8.0 || ^9.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-main": "4.0-dev" } }, "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2021-03-17T13:42:18+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "0.12.84", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "9c43f15da8798c8f30a4b099e6a94530a558cfd5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9c43f15da8798c8f30a4b099e6a94530a558cfd5", - "reference": "9c43f15da8798c8f30a4b099e6a94530a558cfd5", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" + "filesystem", + "iterator" ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.12-dev" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ] + "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/4.1.0" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", "funding": [ { - "url": "https://github.com/ondrejmirtes", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2021-04-19T17:10:54+00:00" + "time": "2023-08-31T06:24:48+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "9.2.5", + "name": "phpunit/php-invoker", + "version": "4.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f3e026641cc91909d421802dd3ac7827ebfd97e1", - "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.10.2", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcntl": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -604,45 +825,47 @@ "role": "lead" } ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ - "coverage", - "testing", - "xunit" + "process" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-11-28T06:44:49+00:00" + "time": "2023-02-03T06:56:09+00:00" }, { - "name": "phpunit/php-file-iterator", - "version": "3.0.5", + "name": "phpunit/php-text-template", + "version": "3.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -661,48 +884,48 @@ "role": "lead" } ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ - "filesystem", - "iterator" + "template" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-09-28T05:57:25+00:00" + "time": "2023-08-31T14:07:24+00:00" }, { - "name": "phpunit/php-invoker", - "version": "3.1.1", + "name": "phpunit/php-timer", + "version": "6.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcntl": "*" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -721,46 +944,81 @@ "role": "lead" } ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ - "process" + "timer" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2023-02-03T06:57:52+00:00" }, { - "name": "phpunit/php-text-template", - "version": "2.0.4", + "name": "phpunit/phpunit", + "version": "10.5.63", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "33198268dad71e926626b618f3ec3966661e4d90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", + "reference": "33198268dad71e926626b618f3ec3966661e4d90", "shasum": "" }, "require": { - "php": ">=7.3" + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.5", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.4", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" }, + "bin": [ + "phpunit" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "10.5-dev" } }, "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], "classmap": [ "src/" ] @@ -776,197 +1034,169 @@ "role": "lead" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", "keywords": [ - "template" + "phpunit", + "testing", + "xunit" ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" + }, "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" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2026-01-27T05:48:37+00:00" }, { - "name": "phpunit/php-timer", - "version": "5.0.3", + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "php": ">=7.4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Container\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "timer" - ], - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], - "time": "2020-10-26T13:16:10+00:00" + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" }, { - "name": "phpunit/phpunit", - "version": "9.5.4", + "name": "psr/log", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c73c6737305e779771147af66c96ca6a7ed8a741" + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c73c6737305e779771147af66c96ca6a7ed8a741", - "reference": "c73c6737305e779771147af66c96ca6a7ed8a741", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.1", - "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpspec/prophecy": "^1.12.1", - "phpunit/php-code-coverage": "^9.2.3", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.5", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.3", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3", - "sebastian/version": "^3.0.2" - }, - "require-dev": { - "ext-pdo": "*", - "phpspec/prophecy-phpunit": "^2.0.1" - }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "php": ">=8.0.0" }, - "bin": [ - "phpunit" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-master": "3.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ], - "files": [ - "src/Framework/Assert/Functions.php" - ] + "psr-4": { + "Psr\\Log\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "funding": [ - { - "url": "https://phpunit.de/donate.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } + "log", + "psr", + "psr-3" ], - "time": "2021-03-23T07:16:29+00:00" + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -987,38 +1217,43 @@ ], "description": "Library for parsing CLI options", "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-03-02T07:12:49+00:00" }, { "name": "sebastian/code-unit", - "version": "1.0.8", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -1039,38 +1274,42 @@ ], "description": "Collection of value objects that represent the PHP code units", "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-10-26T13:08:54+00:00" + "time": "2023-02-03T06:58:43+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1090,40 +1329,46 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2023-02-03T06:59:15+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.6", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1160,39 +1405,56 @@ "compare", "equality" ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" + }, "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": "2020-10-26T15:49:45+00:00" + "time": "2026-01-24T09:25:16+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "68ff824baeae169ec9f2137158ee529584553799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.2-dev" } }, "autoload": { @@ -1213,39 +1475,44 @@ ], "description": "Library for calculating the complexity of PHP code units", "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-21T08:37:17+00:00" }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -1275,33 +1542,38 @@ "unidiff", "unified diff" ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2024-03-02T07:15:17+00:00" }, { "name": "sebastian/environment", - "version": "5.1.3", + "version": "6.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "suggest": { "ext-posix": "*" @@ -1309,7 +1581,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "6.1-dev" } }, "autoload": { @@ -1328,46 +1600,51 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", "hhvm" ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-09-28T05:52:38+00:00" + "time": "2024-03-23T08:47:14+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.3", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + "reference": "0735b90f4da94969541dac1da743446e276defa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", - "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -1402,49 +1679,63 @@ } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ "export", "exporter" ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" + }, "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": "2020-09-28T05:24:23+00:00" + "time": "2025-09-24T06:09:11+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.2", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "a90ccbddffa067b51f574dea6eb25d5680839455" + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/a90ccbddffa067b51f574dea6eb25d5680839455", - "reference": "a90ccbddffa067b51f574dea6eb25d5680839455", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1463,43 +1754,48 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-10-26T15:55:19+00:00" + "time": "2024-03-02T07:19:19+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -1520,40 +1816,45 @@ ], "description": "Library for counting the lines of code in PHP source code", "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-21T08:38:20+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.4", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1573,38 +1874,42 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2023-02-03T07:08:32+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1624,38 +1929,42 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2023-02-03T07:06:18+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1682,39 +1991,56 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "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/5.0.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/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2025-08-10T07:50:56+00:00" }, { - "name": "sebastian/resource-operations", - "version": "3.0.3", + "name": "sebastian/type", + "version": "4.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1729,43 +2055,45 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2023-02-03T07:10:45+00:00" }, { - "name": "sebastian/type", - "version": "2.3.1", + "name": "sebastian/version", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", "shasum": "" }, "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1784,102 +2112,436 @@ "role": "lead" } ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2020-10-26T13:18:59+00:00" + "time": "2023-02-07T11:34:05+00:00" }, { - "name": "sebastian/version", - "version": "3.0.2", + "name": "squizlabs/php_codesniffer", + "version": "3.13.5", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", "shasum": "" }, "require": { - "php": ">=7.3" - }, + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-04T16:30:35+00:00" + }, + { + "name": "symfony/config", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "2d19dde43fa2ff720b9a40763ace7226594f503b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/2d19dde43fa2ff720b9a40763ace7226594f503b", + "reference": "2d19dde43fa2ff720b9a40763ace7226594f503b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^7.1|^8.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "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": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v7.4.8" + }, + "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-03-24T13:12:05+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "f7025fd7b687c240426562f86ada06a93b1e771d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f7025fd7b687c240426562f86ada06a93b1e771d", + "reference": "f7025fd7b687c240426562f86ada06a93b1e771d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.6", + "symfony/var-exporter": "^6.4.20|^7.2.5|^8.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "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": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.8" + }, + "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-03-31T06:50:29+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.6-dev" } }, "autoload": { - "classmap": [ - "src/" + "files": [ + "function.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", + "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" + }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "58b9790d12f9670b7f53a1c1738febd3108970a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/58b9790d12f9670b7f53a1c1738febd3108970a5", + "reference": "58b9790d12f9670b7f53a1c1738febd3108970a5", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "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": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.4.8" + }, + "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-03-24T13:12:05+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.22.1", + "version": "v1.36.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" }, "suggest": { "ext-ctype": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.22-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1903,6 +2565,9 @@ "polyfill", "portable" ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.36.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1912,94 +2577,220 @@ "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": "2021-01-07T16:49:33+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { - "name": "theseer/tokenizer", - "version": "1.2.0", + "name": "symfony/polyfill-mbstring", + "version": "v1.36.0", "source": { "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "75a63c33a8577608444246075ea0af0d052e452a" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", - "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.36.0" + }, "funding": [ { - "url": "https://github.com/theseer", + "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": "2020-07-12T23:59:07+00:00" + "time": "2026-04-10T17:25:58+00:00" }, { - "name": "webmozart/assert", - "version": "1.10.0", + "name": "symfony/service-contracts", + "version": "v3.6.1", "source": { "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "symfony/polyfill-ctype": "^1.8" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "1.10-dev" + "dev-main": "3.6-dev" } }, "autoload": { "psr-4": { - "Webmozart\\Assert\\": "src/" + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "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": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "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": "2025-07-15T11:30:57+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v7.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "398907e89a2a56fe426f7955c6fa943ec0c77225" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/398907e89a2a56fe426f7955c6fa943ec0c77225", + "reference": "398907e89a2a56fe426f7955c6fa943ec0c77225", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2007,27 +2798,108 @@ ], "authors": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Assertions to validate method input/output with nice error messages.", + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", "keywords": [ - "assert", - "check", - "validate" + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.4.8" + }, + "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-03-24T13:12:05+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "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.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } ], - "time": "2021-03-09T10:59:23+00:00" + "time": "2025-11-17T20:03:58+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.2" + "php": ">=8.2" }, - "platform-dev": [], - "plugin-api-version": "1.1.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..2d2aff4 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,64 @@ + + + Created from PHP.Gt/Styleguide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpmd.xml b/phpmd.xml new file mode 100644 index 0000000..d125c4d --- /dev/null +++ b/phpmd.xml @@ -0,0 +1,50 @@ + + + Custom ruleset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..5e1b5d9 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,28 @@ + + + + + + + + ./test/phpunit/ + + + + + + + + + + src/ + + + + diff --git a/src/FileHandler.php b/src/FileHandler.php index fce69be..71f82c1 100644 --- a/src/FileHandler.php +++ b/src/FileHandler.php @@ -1,28 +1,30 @@ $cache */ + protected array $cache; /** * @link http://php.net/manual/en/sessionhandlerinterface.open.php - * @param string $save_path The path where to store/retrieve the session. + * @param string $savePath The path where to store/retrieve the session. * @param string $name The session name. */ - public function open($save_path, $name):bool { + public function open(string $savePath, string $name):bool { $success = true; - $save_path = str_replace( + $savePath = str_replace( ["/", "\\"], DIRECTORY_SEPARATOR, - $save_path + $savePath ); $this->path = implode(DIRECTORY_SEPARATOR, [ - $save_path, + $savePath, $name, ]); @@ -33,48 +35,40 @@ public function open($save_path, $name):bool { return $success; } - /** - * @link http://php.net/manual/en/sessionhandlerinterface.close.php - */ + /** @link http://php.net/manual/en/sessionhandlerinterface.close.php */ public function close():bool { return true; } - /** - * @link http://php.net/manual/en/sessionhandlerinterface.read.php - * @param string $session_id - */ - public function read($session_id):string { - if(isset($this->cache[$session_id])) { - return $this->cache[$session_id]; + /** @link http://php.net/manual/en/sessionhandlerinterface.read.php */ + public function read(string $sessionId):string { + if(isset($this->cache[$sessionId])) { + return $this->cache[$sessionId]; } - $filePath = $this->getFilePath($session_id); + $filePath = $this->getFilePath($sessionId); if(!file_exists($filePath)) { return ""; } - $this->cache[$session_id] = file_get_contents($filePath); - return $this->cache[$session_id]; + $this->cache[$sessionId] = file_get_contents($filePath) ?: ""; + return $this->cache[$sessionId]; } - /** - * @link http://php.net/manual/en/sessionhandlerinterface.write.php - * @param string $session_id - * @param string $session_data - */ - public function write($session_id, $session_data):bool { - $filePath = $this->getFilePath($session_id); - return file_put_contents($filePath, $session_data) > 0; + /** @link http://php.net/manual/en/sessionhandlerinterface.write.php */ + public function write(string $sessionId, string $sessionData):bool { + if($sessionData === self::EMPTY_PHP_ARRAY) { + return true; + } + $filePath = $this->getFilePath($sessionId); + $bytesWritten = file_put_contents($filePath, $sessionData); + return $bytesWritten !== false; } - /** - * @link http://php.net/manual/en/sessionhandlerinterface.destroy.php - * @param string $session_id - */ - public function destroy($session_id):bool { - $filePath = $this->getFilePath($session_id); + /** @link http://php.net/manual/en/sessionhandlerinterface.destroy.php */ + public function destroy(string $id = ""):bool { + $filePath = $this->getFilePath($id); if(file_exists($filePath)) { return unlink($filePath); @@ -83,13 +77,11 @@ public function destroy($session_id):bool { return true; } - /** - * @link http://php.net/manual/en/sessionhandlerinterface.gc.php - * @param int $maxlifetime - */ - public function gc($maxlifetime):bool { + /** @link http://php.net/manual/en/sessionhandlerinterface.gc.php */ + public function gc(int $maxLifeTime):int|false { $now = time(); - $expired = $now - $maxlifetime; + $expired = $now - $maxLifeTime; + $num = 0; foreach(new DirectoryIterator($this->path) as $fileInfo) { if(!$fileInfo->isFile()) { @@ -101,10 +93,11 @@ public function gc($maxlifetime):bool { if(!unlink($fileInfo->getPathname())) { return false; } + $num++; } } - return true; + return $num; } protected function getFilePath(string $id):string { @@ -113,4 +106,4 @@ protected function getFilePath(string $id):string { $id, ]); } -} \ No newline at end of file +} diff --git a/src/Flash.php b/src/Flash.php new file mode 100644 index 0000000..34b9aa8 --- /dev/null +++ b/src/Flash.php @@ -0,0 +1,32 @@ +session->get("queue.$name"); + if(!$queue) { + $queue = new SplQueue(); + $this->session->set("queue.$name", $queue); + } + + $queue->enqueue(new FlashMessage($name, $message)); + } + + public function consume(string $name):?FlashMessage { + /** @var null|SplQueue $queue */ + $queue = $this->session->get("queue.$name"); + try { + return $queue->dequeue(); + } + catch(RuntimeException $e) { + $this->session->remove("queue.$name"); + return null; + } + } + +} diff --git a/src/FlashMessage.php b/src/FlashMessage.php new file mode 100644 index 0000000..a3a3394 --- /dev/null +++ b/src/FlashMessage.php @@ -0,0 +1,9 @@ +parseSavePath($savePath, $name); + $this->config = $config; + return $this->connect($config); + } + + /** @param array{ + * host:string, + * port:int, + * timeout:float, + * readTimeout:float, + * persistentId:?string, + * prefix:string, + * ttl:int, + * database:int, + * auth:array{string,string}|string|null, + * context:array{stream:array{verify_peer:bool,verify_peer_name:bool}}|null + * } $config + */ + private function connect(array $config):bool { + $client = $this->createClient(); + + $connected = $client->connect( + $config["host"], + $config["port"], + $config["timeout"], + $config["persistentId"], + 0, + $config["readTimeout"], + $config["context"], + ); + + if(!$connected) { + return false; + } + + if($config["auth"] !== null && !$client->auth($config["auth"])) { + return false; + } + + if($config["database"] > 0 && !$client->select($config["database"])) { + return false; + } + + $this->client = $client; + $this->prefix = $config["prefix"]; + $this->ttl = $config["ttl"]; + return true; + } + + public function close():bool { + if(is_null($this->client)) { + return true; + } + + try { + return $this->client->close(); + } + catch(Throwable) { + return true; + } + finally { + $this->client = null; + } + } + + public function read(string $sessionId):string { + $value = $this->retryOnce( + fn() => $this->requireClient()->get($this->getKey($sessionId)) + ); + return is_string($value) ? $value : ""; + } + + public function write(string $sessionId, string $sessionData):bool { + if($sessionData === self::EMPTY_PHP_ARRAY) { + return true; + } + + $key = $this->getKey($sessionId); + + if($this->ttl > 0) { + return $this->retryOnce( + fn() => $this->requireClient()->setEx($key, $this->ttl, $sessionData) + ); + } + + return $this->retryOnce( + fn() => $this->requireClient()->set($key, $sessionData) + ); + } + + public function destroy(string $id = ""):bool { + return $this->retryOnce( + fn() => $this->requireClient()->del($this->getKey($id)) + ) >= 0; + } + + // phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass + public function gc(int $maxLifeTime):int|false { + return 0; + } + // phpcs:enable + + /** + * @return array{ + * host:string, + * port:int, + * timeout:float, + * readTimeout:float, + * persistentId:?string, + * prefix:string, + * ttl:int, + * database:int, + * auth:array{string,string}|string|null, + * context:array{stream:array{verify_peer:bool,verify_peer_name:bool}}|null + * } + */ + private function parseSavePath(string $savePath, string $name):array { + $parts = parse_url($savePath); + if($parts === false || !isset($parts["host"])) { + throw new RuntimeException("Invalid Redis save_path DSN."); + } + + parse_str($parts["query"] ?? "", $query); + + [$host, $context] = $this->parseHostAndContext($parts, $query); + + return [ + "host" => $host, + "port" => (int)($parts["port"] ?? self::DEFAULT_PORT), + "timeout" => (float)($query["timeout"] ?? 0), + "readTimeout" => (float)($query["read_timeout"] ?? 0), + "persistentId" => $this->parsePersistentId($query), + "prefix" => $this->parsePrefix($query, $name), + "ttl" => (int)($query["ttl"] ?? ini_get("session.gc_maxlifetime")), + "database" => $this->parseDatabase($parts), + "auth" => $this->parseAuth($parts), + "context" => $context, + ]; + } + + /** + * @param array $parts + * @param array $query + * @return array{ + * 0:string, + * 1:array{stream:array{verify_peer:bool,verify_peer_name:bool}}|null + * } + */ + private function parseHostAndContext(array $parts, array $query):array { + $scheme = strtolower($parts["scheme"] ?? "redis"); + $host = $parts["host"]; + + if(!in_array($scheme, ["rediss", "tls"], true)) { + return [$host, null]; + } + + return [ + "tls://$host", + [ + "stream" => [ + "verify_peer" => filter_var( + $query["verify_peer"] ?? true, + FILTER_VALIDATE_BOOL + ), + "verify_peer_name" => filter_var( + $query["verify_peer_name"] ?? true, + FILTER_VALIDATE_BOOL + ), + ], + ], + ]; + } + + /** + * @param array $parts + * @return array{string,string}|string|null + */ + private function parseAuth(array $parts):array|string|null { + if(isset($parts["user"]) && $parts["user"] !== "" && isset($parts["pass"])) { + return [rawurldecode($parts["user"]), rawurldecode($parts["pass"])]; + } + + if(isset($parts["pass"])) { + return rawurldecode($parts["pass"]); + } + + return null; + } + + /** + * @param array $query + */ + private function parsePersistentId(array $query):?string { + if( + !isset($query["persistent"]) + || !filter_var($query["persistent"], FILTER_VALIDATE_BOOL) + ) { + return null; + } + + return is_string($query["persistent_id"] ?? null) + ? $query["persistent_id"] + : "phpgt-session"; + } + + /** + * @param array $query + */ + private function parsePrefix(array $query, string $name):string { + return is_string($query["prefix"] ?? null) + ? $query["prefix"] + : $name . self::DEFAULT_PREFIX_SEPARATOR; + } + + /** + * @param array $parts + */ + private function parseDatabase(array $parts):int { + return isset($parts["path"]) + ? (int)trim($parts["path"], "/") + : 0; + } + + private function getKey(string $sessionId):string { + return $this->prefix . $sessionId; + } + + protected function createClient():Redis { + if(!class_exists(Redis::class)) { + throw new RuntimeException( + "The phpredis extension is required to use GT\\Session\\RedisHandler." + ); + } + + return new Redis(); + } + + private function requireClient():Redis { + if(is_null($this->client)) { + throw new RuntimeException("RedisHandler::open() must be called before use."); + } + + return $this->client; + } + + private function reconnect():void { + if(is_null($this->config)) { + throw new RuntimeException("RedisHandler::open() must be called before reconnect."); + } + + $this->close(); + $this->connect($this->config); + } + + /** + * @template T + * @param callable():T $callback + * @return T + */ + private function retryOnce(callable $callback):mixed { + try { + return $callback(); + } + catch(Throwable) { + $this->reconnect(); + return $callback(); + } + } +} diff --git a/src/Session.php b/src/Session.php index 5289b05..eab4aa7 100644 --- a/src/Session.php +++ b/src/Session.php @@ -1,63 +1,64 @@ */ + protected array $config; + /** @param iterable $config */ public function __construct( SessionHandlerInterface $sessionHandler, iterable $config = [], - string $id = null + ?string $id = null, ) { $this->sessionHandler = $sessionHandler; + if(!is_array($config)) { + $config = iterator_to_array($config); + } + /** @var array $config */ + $this->config = $config; + if(is_null($id)) { $id = $this->getId(); } $this->id = $id; - $sessionPath = $this->getAbsolutePath( + $sessionPath = $this->normaliseSavePath( $config["save_path"] ?? self::DEFAULT_SESSION_PATH ); $sessionName = $config["name"] ?? self::DEFAULT_SESSION_NAME; - $sessionStartAttempts = 0; - - do { - $success = @session_start([ - "save_path" => $sessionPath, - "name" => $sessionName, - "cookie_lifetime" => $config["cookie_lifetime"] ?? self::DEFAULT_SESSION_LIFETIME, - "cookie_path" => $config["cookie_path"] ?? self::DEFAULT_COOKIE_PATH, - "cookie_domain" => $config["cookie_domain"] ?? self::DEFAULT_SESSION_DOMAIN, - "cookie_secure" => $config["cookie_secure"] ?? self::DEFAULT_SESSION_SECURE, - "cookie_httponly" => $config["cookie_httponly"] ?? self::DEFAULT_SESSION_HTTPONLY, - ]); - - if(!$success) { - $sessionStartAttempts++; - @session_destroy(); - } - } - while(!$success && $sessionStartAttempts <= 1); + $this->attemptStart($sessionPath, $sessionName, $config); $this->sessionHandler->open($sessionPath, $sessionName); - $this->store = $this->readSessionData() ?: null; + $this->store = $this->readSessionData(); if(is_null($this->store)) { $this->store = new SessionStore(__NAMESPACE__, $this); } @@ -67,7 +68,7 @@ public function kill():void { $this->sessionHandler->destroy($this->getId()); $params = session_get_cookie_params(); setcookie( - session_name(), + session_name() ?: "", "", -1, $params["path"], @@ -80,18 +81,18 @@ public function kill():void { public function getStore( string $namespace, bool $createIfNotExists = false - ):?SessionStore { + ):?SessionStoreInterface { return $this->store->getStore( $namespace, $createIfNotExists ); } - public function get(string $key) { + public function get(string $key):mixed { return $this->store->get($key); } - public function set(string $key, $value):void { + public function set(string $key, mixed $value):void { $this->store->set($key, $value); } @@ -109,10 +110,14 @@ public function getId():string { session_id($this->createNewId()); } - return session_id(); + return session_id() ?: ""; } - protected function getAbsolutePath(string $path):string { + protected function normaliseSavePath(string $path):string { + if($this->isDsn($path)) { + return $path; + } + $path = str_replace( ["/", "\\"], DIRECTORY_SEPARATOR, @@ -129,18 +134,132 @@ protected function getAbsolutePath(string $path):string { return $path; } + protected function isDsn(string $path):bool { + return (bool)preg_match('/^[a-z][a-z0-9+.-]*:\/\//i', $path); + } + + /** @SuppressWarnings("PHPMD.Superglobals") */ protected function createNewId():string { - return session_create_id(); + if(($this->config["use_trans_sid"] ?? null) + && !$this->config["use_cookies"]) { + return $_GET[$this->config["name"]] ?? session_create_id(); + } + return session_create_id() ?: ""; } - protected function readSessionData() { - return unserialize($this->sessionHandler->read($this->id)); + /** @SuppressWarnings("PHPMD.EmptyCatchBlock") */ + protected function readSessionData():?SessionStore { + try { + $data = $this->sessionHandler->read($this->id) ?: ""; + $store = unserialize($data); + if ($store instanceof SessionStore) { + return $store; + } + } + // PHPCS:ignore + catch (Throwable) {} + + return null; } - public function write() { - $this->sessionHandler->write( + + public function write():bool { + return $this->sessionHandler->write( $this->id, serialize($this->store) ); } -} \ No newline at end of file + + /** + * @param string $sessionPath + * @param string $sessionName + * @param array $config + * @SuppressWarnings("PHPMD.UnusedFormalParameter") + * @return void + */ + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed + private function attemptStart( + string $sessionPath, + string $sessionName, + array $config, + ?string $unusedContext = null, + ):void { + $sessionOptions = $this->getSessionOptions( + $sessionPath, + $sessionName, + $config, + ); + $this->tryStartSession($sessionOptions); + } + + /** + * @param array $config + * @return array + */ + private function getSessionOptions( + string $sessionPath, + string $sessionName, + array $config + ):array { + $defaultConfig = [ + "use_only_cookies" => true, + "use_cookies" => true, + "use_trans_sid" => false, + "cookie_lifetime" => self::DEFAULT_SESSION_LIFETIME, + "cookie_path" => self::DEFAULT_COOKIE_PATH, + "cookie_domain" => self::DEFAULT_SESSION_DOMAIN, + "cookie_secure" => self::DEFAULT_SESSION_SECURE, + "cookie_httponly" => self::DEFAULT_SESSION_HTTPONLY, + "cookie_samesite" => self::DEFAULT_COOKIE_SAMESITE, + "use_strict_mode" => self::DEFAULT_STRICT_MODE, + ]; + + $config = array_merge($defaultConfig, $config); + + return [ + "save_path" => $sessionPath, + "name" => $sessionName, + "serialize_handler" => "php_serialize", + "use_only_cookies" => $config["use_only_cookies"], + "use_cookies" => $config["use_cookies"], + "use_trans_sid" => $config["use_trans_sid"] ?? false, + "cookie_lifetime" => $config["cookie_lifetime"], + "cookie_path" => $config["cookie_path"], + "cookie_domain" => $config["cookie_domain"], + "cookie_secure" => $config["cookie_secure"], + "cookie_httponly" => $config["cookie_httponly"], + "cookie_samesite" => $config["cookie_samesite"], + "use_strict_mode" => $config["use_strict_mode"], + ]; + } + + /** + * @param array $sessionOptions + * @SuppressWarnings("PHPMD.EmptyCatchBlock") + */ + private function tryStartSession(array $sessionOptions):void { + $startAttempts = 0; + do { + $success = false; + + try { + if(session_status() !== PHP_SESSION_ACTIVE) { + $success = session_start($sessionOptions); + } + } + // PHPCS:ignore + catch(Throwable) {} + + if(!$success) { + if(session_status() === PHP_SESSION_ACTIVE) { + session_destroy(); + } + if(session_id()) { + session_regenerate_id(true); + } + } + $startAttempts++; + } + while(!$success && $startAttempts <= 1); + } +} diff --git a/src/SessionArrayWrapper.php b/src/SessionArrayWrapper.php index 7d808fc..ed2ab60 100644 --- a/src/SessionArrayWrapper.php +++ b/src/SessionArrayWrapper.php @@ -1,18 +1,20 @@ */ + private array $sourceArray; + /** @param array &$sourceArray */ public function __construct(array &$sourceArray) { $this->sourceArray = &$sourceArray; } - public function get(string $key) { + public function get(string $key):mixed { return $this->sourceArray[$key] ?? null; } - public function set(string $key, $value) { + public function set(string $key, mixed $value):void { $this->sourceArray[$key] = $value; } @@ -27,4 +29,4 @@ public function remove(string $key):void { unset($this->sourceArray[$key]); } -} \ No newline at end of file +} diff --git a/src/SessionContainer.php b/src/SessionContainer.php index 6b4e385..68d121c 100644 --- a/src/SessionContainer.php +++ b/src/SessionContainer.php @@ -1,9 +1,9 @@ + * @SuppressWarnings("PHPMD.TooManyPublicMethods") + */ +class SessionStore extends ArrayIterator implements SessionStoreInterface { + use NullableTypeSafeGetter; + + protected string $name; + protected Session $session; + /** @var array */ + protected array $stores; + protected ?SessionStore $parentStore; public function __construct( string $name, Session $session, - self $parentStore = null + ?self $parentStore = null ) { $this->name = $name; $this->session = $session; $this->parentStore = $parentStore; $this->stores = []; - $this->data = []; + parent::__construct(); } - public function setData(string $key, $value):void { - $this->data[$key] = $value; + public function setData(string $key, mixed $value):void { + $this->offsetSet($key, $value); } - public function getData(string $key) { - return $this->data[$key] ?? null; + public function getData(string $key):mixed { + if(!$this->offsetExists($key)) { + return null; + } + + return $this->offsetGet($key); } public function containsData(string $key):bool { - return isset($this->data[$key]); + return $this->offsetExists($key); } public function containsStore(string $key):bool { @@ -42,7 +52,7 @@ public function containsStore(string $key):bool { } public function removeData(string $key):void { - unset($this->data[$key]); + $this->offsetUnset($key); } public function removeStore(string $key):void { @@ -69,16 +79,12 @@ public function getStore( $namespaceParts = explode(".", $namespace); $topLevelStoreName = array_shift($namespaceParts); - /** @var SessionStore $store */ $store = $this->stores[$topLevelStoreName] ?? null; if(is_null($store)) { if($createIfNotExists) { - $store = $this->createStore($namespace); - return $store; - } - else { - return null; + return $this->createStore($namespace); } + return null; } if(empty($namespaceParts)) { @@ -117,27 +123,13 @@ public function createStore(string $namespace):SessionStore { return $this->getStore($namespace); } - public function get(string $key) { - $store = $this; - $lastDotPosition = strrpos($key, "."); - - if ($lastDotPosition !== false) { - $namespace = $this->getNamespaceFromKey($key); - $store = $this->getStore($namespace); - } - - if (is_null($store)) { - return null; - } - - if ($lastDotPosition !== false) { - $key = substr($key, $lastDotPosition + 1); - } - - return $store->getData($key); + public function get(string $key):mixed { + $store = $this->getStoreFromKey($key); + $key = $this->normaliseKey($key); + return $store?->getData($key); } - public function set(string $key, $value):void { + public function set(string $key, mixed $value):void { $store = $this; $lastDotPosition = strrpos($key, "."); @@ -159,6 +151,12 @@ public function set(string $key, $value):void { } public function contains(string $key):bool { + $store = $this->getStoreFromKey($key); + $key = $this->normaliseKey($key); + return $store?->containsData($key) ?? false; + } + + private function getStoreFromKey(string $key):?SessionStore { $store = $this; $lastDotPosition = strrpos($key, "."); @@ -168,19 +166,24 @@ public function contains(string $key):bool { } if (is_null($store)) { - return false; + return null; } - if ($lastDotPosition !== false) { + return $store; + } + + private function normaliseKey(string $key):string { + $lastDotPosition = strrpos($key, "."); + if($lastDotPosition !== false) { $key = substr($key, $lastDotPosition + 1); } - return $store->containsData($key); + return $key; } - public function remove(string $key = null):void { + public function remove(?string $key = null):void { if(is_null($key)) { - foreach($this->stores as $i => $childStore) { + foreach(array_keys($this->stores) as $i) { unset($this->stores[$i]); } @@ -191,16 +194,16 @@ public function remove(string $key = null):void { $store = $this; $lastDotPosition = strrpos($key, "."); - if ($lastDotPosition !== false) { + if($lastDotPosition !== false) { $namespace = $this->getNamespaceFromKey($key); $store = $this->getStore($namespace); } - if (is_null($store)) { + if(is_null($store)) { return; } - if ($lastDotPosition !== false) { + if($lastDotPosition !== false) { $key = substr($key, $lastDotPosition + 1); } @@ -213,11 +216,11 @@ protected function getSession():Session { } protected function getNamespaceFromKey(string $key):?string { - $lastDotPostition = strrpos($key, "."); - if ($lastDotPostition === false) { + $lastDotPosition = strrpos($key, "."); + if ($lastDotPosition === false) { return null; } - return substr($key, 0, $lastDotPostition); + return substr($key, 0, $lastDotPosition); } -} \ No newline at end of file +} diff --git a/src/SessionStoreFactory.php b/src/SessionStoreFactory.php index 10bdae1..2bf6d5b 100644 --- a/src/SessionStoreFactory.php +++ b/src/SessionStoreFactory.php @@ -1,8 +1,11 @@ expects(self::once()) + ->method("get") + ->with("queue.test") + ->willReturn(null); + $sessionStore->expects(self::once()) + ->method("set") + ->with( + self::equalTo("queue.test"), + self::isInstanceOf(SplQueue::class) + ); + + $sut = new Flash($sessionStore); + $sut->put("test", "Test message"); + } + + public function testConsume_none():void { + $queue = self::createMock(SplQueue::class); + $queue->expects(self::once()) + ->method("dequeue") + ->willThrowException(new RuntimeException("Can't shift from an empty datastructure")); + $sessionStore = self::createMock(SessionStoreInterface::class); + $sessionStore->expects(self::once()) + ->method("get") + ->with("queue.test") + ->willReturn($queue); + $sut = new Flash($sessionStore); + $flashMessage = $sut->consume("test"); + self::assertNull($flashMessage); + } + + public function testConsume():void { + $fm1 = new FlashMessage("test", "First"); + $fm2 = new FlashMessage("test", "Second"); + + $queue = self::createMock(SplQueue::class); + $queue->expects(self::exactly(3)) + ->method("dequeue") + ->willReturnOnConsecutiveCalls($fm1, $fm2); + + $sessionStore = self::createMock(SessionStoreInterface::class); + $sessionStore->expects(self::exactly(3)) + ->method("get") + ->with("queue.test") + ->willReturn($queue); + $sessionStore->expects(self::once()) + ->method("remove") + ->with("queue.test"); + $sut = new Flash($sessionStore); + + $flashMessage = $sut->consume("test"); + self::assertInstanceOf(FlashMessage::class, $flashMessage); + self::assertSame("First", $flashMessage->message); + $flashMessage = $sut->consume("test"); + self::assertInstanceOf(FlashMessage::class, $flashMessage); + self::assertSame("Second", $flashMessage->message); + $flashMessage = $sut->consume("test"); + self::assertNull($flashMessage); + } +} diff --git a/test/phpunit/Helper/DataProvider/ConfigProvider.php b/test/phpunit/Helper/DataProvider/ConfigProvider.php index 541d7c1..1f6d440 100644 --- a/test/phpunit/Helper/DataProvider/ConfigProvider.php +++ b/test/phpunit/Helper/DataProvider/ConfigProvider.php @@ -1,5 +1,7 @@ client; + } + }; + + $sut->open( + "redis://default:secret@example.internal:25061/2?prefix=prod:session:&ttl=1800&timeout=1.5&read_timeout=2.5&persistent=1&persistent_id=pool-a", + "GT", + ); + + self::assertSame("example.internal", $client->connectParameters["host"]); + self::assertSame(25061, $client->connectParameters["port"]); + self::assertSame(1.5, $client->connectParameters["timeout"]); + self::assertSame(2.5, $client->connectParameters["readTimeout"]); + self::assertSame("pool-a", $client->connectParameters["persistentId"]); + self::assertSame([["default", "secret"]], $client->authCalls); + self::assertSame([2], $client->selectCalls); + + $sut->write("abc123", "payload"); + self::assertSame("payload", $sut->read("abc123")); + self::assertSame( + [ + "key" => "prod:session:abc123", + "ttl" => 1800, + "value" => "payload", + ], + $client->setExCalls[0], + ); + } + + public function testOpenParsesTlsDsn():void { + $client = new TestRedisClient(); + $sut = new class($client) extends RedisHandler { + public function __construct(private readonly TestRedisClient $client) {} + + protected function createClient():Redis { + /** @phpstan-ignore-next-line */ + return $this->client; + } + }; + + $sut->open( + "rediss://:secret@example.internal?verify_peer=0&verify_peer_name=0", + "GT", + ); + $sut->write("abc123", "payload"); + + self::assertSame("tls://example.internal", $client->connectParameters["host"]); + self::assertSame( + [ + "stream" => [ + "verify_peer" => false, + "verify_peer_name" => false, + ], + ], + $client->connectParameters["context"], + ); + self::assertSame(["secret"], $client->authCalls); + self::assertSame("payload", $client->data["GT:abc123"]); + } + + public function testDestroyAndClose():void { + $client = new TestRedisClient(); + $sut = new class($client) extends RedisHandler { + public function __construct(private readonly TestRedisClient $client) {} + + protected function createClient():Redis { + /** @phpstan-ignore-next-line */ + return $this->client; + } + }; + $sut->open("redis://cache.internal", "GT"); + $sut->write("abc123", "payload"); + + self::assertTrue($sut->destroy("abc123")); + self::assertSame("", $sut->read("abc123")); + self::assertTrue($sut->close()); + self::assertTrue($client->closed); + } + + public function testEmptyNativePhpSessionWriteDoesNotOverwriteStoredSession():void { + $client = new TestRedisClient(); + $sut = new class($client) extends RedisHandler { + public function __construct(private readonly TestRedisClient $client) {} + + protected function createClient():Redis { + /** @phpstan-ignore-next-line */ + return $this->client; + } + }; + $sut->open("redis://cache.internal", "GT"); + $sut->write("abc123", "stored-session"); + $sut->write("abc123", "a:0:{}"); + + self::assertSame("stored-session", $sut->read("abc123")); + } + + public function testCommandRetriesAfterDisconnectedClient():void { + $client = new TestRedisClient(); + $sut = new class($client) extends RedisHandler { + public function __construct(private readonly TestRedisClient $client) {} + + protected function createClient():Redis { + /** @phpstan-ignore-next-line */ + return $this->client; + } + }; + $sut->open("redis://cache.internal", "GT"); + $client->failNextSetEx = true; + + self::assertTrue($sut->write("abc123", "payload")); + self::assertSame("payload", $sut->read("abc123")); + self::assertSame(2, $client->connectCount); + } +} + +class TestRedisClient extends Redis { + /** @var array */ + public array $connectParameters = []; + /** @var array */ + public array $setExCalls = []; + /** @var array */ + public array $setCalls = []; + /** @var array */ + public array $authCalls = []; + /** @var array */ + public array $selectCalls = []; + /** @var array */ + public array $data = []; + public int $deleted = 0; + public int $connectCount = 0; + public bool $failNextSetEx = false; + public bool $closed = false; + + /** + * @param array{auth?:array{0:string|false|null,1?:string},stream?:array}|null $context + */ + public function connect( + string $host, + int $port = 6379, + float $timeout = 0, + ?string $persistent_id = null, + int $retry_interval = 0, + float $read_timeout = 0, + ?array $context = null, + ):bool { + $this->connectCount++; + $this->connectParameters = [ + "host" => $host, + "port" => $port, + "timeout" => $timeout, + "persistentId" => $persistent_id, + "retryInterval" => $retry_interval, + "readTimeout" => $read_timeout, + "context" => $context, + ]; + return true; + } + + /** + * @param array{string,string}|string $credentials + */ + public function auth(mixed $credentials):Redis|bool { + $this->authCalls []= $credentials; + return true; + } + + public function select(int $database):Redis|bool { + $this->selectCalls []= $database; + return true; + } + + public function get(string $key):string|false { + return $this->data[$key] ?? false; + } + + public function set(string $key, mixed $value, mixed $options = null):Redis|string|bool { + $this->setCalls []= $key; + $this->data[$key] = (string)$value; + return true; + } + + public function setEx(string $key, int $ttl, mixed $value) { + if($this->failNextSetEx) { + $this->failNextSetEx = false; + throw new \RedisException("Redis server went away"); + } + + $this->setExCalls []= [ + "key" => $key, + "ttl" => $ttl, + "value" => (string)$value, + ]; + $this->data[$key] = (string)$value; + return true; + } + + public function del(array|string $key, string ...$otherKeys):Redis|int|false { + if(is_array($key)) { + $key = reset($key); + } + + unset($this->data[$key]); + return ++$this->deleted; + } + + public function close():bool { + $this->closed = true; + return true; + } +} diff --git a/test/phpunit/SessionArrayWrapperTest.php b/test/phpunit/SessionArrayWrapperTest.php index e74aff5..5ca7b31 100644 --- a/test/phpunit/SessionArrayWrapperTest.php +++ b/test/phpunit/SessionArrayWrapperTest.php @@ -1,7 +1,7 @@ getMockBuilder(Handler::class) - ->getMock(); + protected static function createStaticMock(string $className):MockObject { + $tc = new SessionStoreTest(""); + return $tc->createMock($className); + } + + /** @dataProvider data_randomKeyValuePairs */ + public static function testGetSetDotNotation(array $keyValuePairs, Handler $handler):void { $session = new Session($handler); $sessionNamespace = "gt.test.session"; @@ -81,4 +83,100 @@ public function testGetCreatesNonExistantStore() { self::assertNotNull($leafStore); } -} \ No newline at end of file + + public function testGetString():void { + $session = $this->createMock(Session::class); + $sut = new SessionStore("test", $session); + + $numericValue = rand(1000, 9999); + $sut->set("test.value", $numericValue); + + self::assertSame((string)$numericValue, $sut->getString("test.value")); + } + + public function testGetInt():void { + $session = $this->createMock(Session::class); + $sut = new SessionStore("test", $session); + + $numericStringValue = (string)rand(1000, 9999); + $sut->set("test.value", $numericStringValue); + + self::assertSame((int)$numericStringValue, $sut->getInt("test.value")); + } + + public function testGetFloat():void { + $session = $this->createMock(Session::class); + $sut = new SessionStore("test", $session); + + $numericStringValue = (string)(rand(1000, 9999) - 0.105); + $sut->set("test.value", $numericStringValue); + + self::assertSame((float)$numericStringValue, $sut->getFloat("test.value")); + } + + public function testGetBool():void { + $session = $this->createMock(Session::class); + $sut = new SessionStore("test", $session); + + $numericValue = rand(0, 1); + $sut->set("test.value", $numericValue); + + self::assertSame((bool)$numericValue, $sut->getBool("test.value")); + } + + public function testGetDateTime():void { + $session = $this->createMock(Session::class); + $sut = new SessionStore("test", $session); + + $numericValue = time(); + $sut->set("test.value", $numericValue); + + $dateTime = new DateTime(); + $dateTime->setTimestamp($numericValue); + self::assertEquals($dateTime, $sut->getDateTime("test.value")); + } + + public function testCount():void { + $session = $this->createMock(Session::class); + $sut = new SessionStore("test", $session); + + $rawData = [ + "one" => "first", + "two" => "second", + "three" => "third", + ]; + + foreach($rawData as $key => $value) { + $sut->set($key, $value); + } + + self::assertCount(3, $sut); + } + + + public function testSessionStoreIsIterable():void { + $session = $this->createMock(Session::class); + $sut = new SessionStore("test", $session); + + $rawData = [ + "key1" => "value1", + "key2" => "value2", + "key3" => "value3", + ]; + + foreach($rawData as $key => $value) { + $sut->set($key, $value); + } + + foreach($sut as $key => $value) { + self::assertArrayHasKey($key, $rawData); + self::assertSame($rawData[$key], $value); + } + } + + public function testSessionStoreImplementsInterface():void { + $session = $this->createMock(Session::class); + $sut = new SessionStore("test", $session); + self::assertInstanceOf(SessionStoreInterface::class, $sut); + } +} diff --git a/test/phpunit/SessionTest.php b/test/phpunit/SessionTest.php index 59e5814..bb4ec01 100644 --- a/test/phpunit/SessionTest.php +++ b/test/phpunit/SessionTest.php @@ -1,12 +1,13 @@ getMockBuilder(Handler::class) - ->getMock(); + protected static function createStaticMock(string $className):MockObject { + $tc = new SessionStoreTest(""); + return $tc->createMock($className); + } + public function testSessionStarts():void { self::assertEmpty(FunctionMocker::$mockCalls["session_start"]); + $handler = self::createMock(Handler::class); new Session($handler); self::assertCount( 1, @@ -34,15 +37,83 @@ public function testSessionStarts():void { ); } - /** - * @dataProvider data_randomConfig - */ - public function testSessionStartsWithConfig(array $config):void { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); + public function testSessionStartPreservesDsnSavePath():void { + $handler = self::createMock(Handler::class); + $savePath = "valkey://cache.internal:6379/0?ttl=1440"; + + new Session($handler, [ + "save_path" => $savePath, + ]); - new Session($handler ,$config); + $sessionStartParameter = FunctionMocker::$mockCalls["session_start"][0][0]; + self::assertSame($savePath, $sessionStartParameter["save_path"]); + } + + public function testWriteSessionDataCalled() { + $handler = self::createMock(Handler::class); + $handler->expects($this->exactly(2)) + ->method("write"); + $session = new Session($handler); + + $session->set("test-key", "test-value"); + $session->remove("test-key"); + } + + public function testGetString():void { + $handler = self::createMock(Handler::class); + $sut = new Session($handler); + + $numericValue = rand(1000, 9999); + $sut->set("test.value", $numericValue); + + self::assertSame((string)$numericValue, $sut->getString("test.value")); + } + + public function testGetInt():void { + $handler = self::createMock(Handler::class); + $sut = new Session($handler); + + $numericStringValue = (string)rand(1000, 9999); + $sut->set("test.value", $numericStringValue); + + self::assertSame((int)$numericStringValue, $sut->getInt("test.value")); + } + + public function testGetFloat():void { + $handler = self::createMock(Handler::class); + $sut = new Session($handler); + + $numericStringValue = (string)(rand(1000, 9999) - 0.105); + $sut->set("test.value", $numericStringValue); + + self::assertSame((float)$numericStringValue, $sut->getFloat("test.value")); + } + + public function testGetBool():void { + $handler = self::createMock(Handler::class); + $sut = new Session($handler); + + $numericValue = rand(0, 1); + $sut->set("test.value", $numericValue); + + self::assertSame((bool)$numericValue, $sut->getBool("test.value")); + } + + public function testGetDateTime():void { + $handler = self::createMock(Handler::class); + $sut = new Session($handler); + + $numericValue = time(); + $sut->set("test.value", $numericValue); + + $dateTime = new DateTime(); + $dateTime->setTimestamp($numericValue); + self::assertEquals($dateTime, $sut->getDateTime("test.value")); + } + + /** @dataProvider data_randomConfig */ + private static function testSessionStartsWithConfig(array $config, Handler $handler):void { + new Session($handler, $config); $sessionStartParameter = FunctionMocker::$mockCalls["session_start"][0][0]; foreach($config as $key => $value) { @@ -55,46 +126,20 @@ public function testSessionStartsWithConfig(array $config):void { } /** @dataProvider data_randomConfig */ - public function testSessionStartDestroysFailedSession(array $config):void { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); - + private static function testSessionStartDestroysFailedSession(array $config, Handler $handler):void { FunctionMocker::$callState["session_start__fail"] = true; new Session($handler, $config); self::assertCount(1, FunctionMocker::$mockCalls["session_destroy"]); } - public function testWriteSessionDataCalled() { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); - $handler->expects($this->exactly(2)) - ->method("write"); - $session = new Session($handler); - - $session->set("test-key", "test-value"); - $session->remove("test-key"); - } - - /** - * @dataProvider data_randomString - */ - public function testGetReturnsNull(string $randomString):void { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); + /** @dataProvider data_randomString */ + private static function testGetReturnsNull(string $randomString, Handler $handler):void { $session = new Session($handler); self::assertNull($session->get($randomString)); } - /** - * @@dataProvider data_randomKeyValuePairs - */ - public function testSetGet(array $keyValuePairs):void { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); + /** @dataProvider data_randomKeyValuePairs */ + private static function testSetGet(array $keyValuePairs, Handler $handler):void { $session = new Session($handler); foreach($keyValuePairs as $key => $value) { @@ -106,13 +151,8 @@ public function testSetGet(array $keyValuePairs):void { } } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testSetGetNamespaced(array $keyValuePairs):void { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); + /** @dataProvider data_randomKeyValuePairs */ + private static function testSetGetNamespaced(array $keyValuePairs, Handler $handler):void { $session = new Session($handler); foreach($keyValuePairs as $key => $value) { @@ -134,45 +174,35 @@ public function testSetGetNamespaced(array $keyValuePairs):void { } } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testSetGetNamespacedSameParentNamespace(array $keyValuePairs):void { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); - $session = new Session($handler); - - $parentNamespace = implode(".", [ - uniqid("namespace1-"), - uniqid("namespace2-"), - ]); - - foreach($keyValuePairs as $key => $value) { - $newKey = implode(".", [ - $parentNamespace, - $key, - ]); - $keyValuePairs[$newKey] = $value; - unset($keyValuePairs[$key]); - } - - foreach($keyValuePairs as $key => $value) { - $session->set($key, $value); - } - - foreach($keyValuePairs as $key => $value) { - self::assertEquals($value, $session->get($key)); - } - } - - /** - * @@dataProvider data_randomKeyValuePairs - */ - public function testSetGetNotExistsOtherKey(array $keyValuePairs):void { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); + /** @dataProvider data_randomKeyValuePairs */ + private static function testSetGetNamespacedSameParentNamespace(array $keyValuePairs, Handler $handler):void { + $session = new Session($handler); + + $parentNamespace = implode(".", [ + uniqid("namespace1-"), + uniqid("namespace2-"), + ]); + + foreach($keyValuePairs as $key => $value) { + $newKey = implode(".", [ + $parentNamespace, + $key, + ]); + $keyValuePairs[$newKey] = $value; + unset($keyValuePairs[$key]); + } + + foreach($keyValuePairs as $key => $value) { + $session->set($key, $value); + } + + foreach($keyValuePairs as $key => $value) { + self::assertEquals($value, $session->get($key)); + } + } + + /** @dataProvider data_randomKeyValuePairs */ + private static function testSetGetNotExistsOtherKey(array $keyValuePairs, Handler $handler):void { $session = new Session($handler); foreach($keyValuePairs as $key => $value) { @@ -197,13 +227,8 @@ public function testSetGetNotExistsOtherKey(array $keyValuePairs):void { } } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testContains(array $keyValuePairs):void { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); + /** @dataProvider data_randomKeyValuePairs */ + private static function testContains(array $keyValuePairs, Handler $handler):void { $session = new Session($handler); foreach($keyValuePairs as $key => $value) { @@ -215,13 +240,8 @@ public function testContains(array $keyValuePairs):void { } } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testNotContains(array $keyValuePairs):void { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); + /** @dataProvider data_randomKeyValuePairs */ + private static function testNotContains(array $keyValuePairs, Handler $handler):void { $session = new Session($handler); foreach($keyValuePairs as $key => $value) { @@ -233,20 +253,15 @@ public function testNotContains(array $keyValuePairs):void { } } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testRemove(array $keyValuePairs):void { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); + /** @dataProvider data_randomKeyValuePairs */ + private static function testRemove(array $keyValuePairs, Handler $handler):void { $session = new Session($handler); foreach($keyValuePairs as $key => $value) { $session->set($key, $value); } - uasort($keyValuePairs, function () { + uasort($keyValuePairs, function() { return rand(-1, 1); }); @@ -257,57 +272,47 @@ public function testRemove(array $keyValuePairs):void { } } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testNamespaceKeyIsRemovedFromSession(array $keyValuePairs):void { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); - $session = new Session($handler); - - $parentNamespace = implode(".", [ - uniqid("namespace1-"), - uniqid("namespace2-"), + /** @dataProvider data_randomKeyValuePairs */ + private static function testNamespaceKeyIsRemovedFromSession(array $keyValuePairs, Handler $handler):void { + $session = new Session($handler); + + $parentNamespace = implode(".", [ + uniqid("namespace1-"), + uniqid("namespace2-"), ]); - foreach($keyValuePairs as $key => $value) { - $fullKey = implode(".", [ - $parentNamespace, + foreach($keyValuePairs as $key => $value) { + $fullKey = implode(".", [ + $parentNamespace, $key, ]); - $session->set($fullKey, $value); + $session->set($fullKey, $value); } $keyToRemove = array_rand($keyValuePairs); - $fullKeyToRemove = implode(".", [ - $parentNamespace, + $fullKeyToRemove = implode(".", [ + $parentNamespace, $keyToRemove, ]); - $store = $session->getStore($parentNamespace); - $store->remove($keyToRemove); - unset($keyValuePairs[$keyToRemove]); + $store = $session->getStore($parentNamespace); + $store->remove($keyToRemove); + unset($keyValuePairs[$keyToRemove]); - foreach($keyValuePairs as $key => $value) { - $fullKey = implode(".", [ - $parentNamespace, + foreach($keyValuePairs as $key => $value) { + $fullKey = implode(".", [ + $parentNamespace, $key, ]); - self::assertTrue($session->contains($fullKey)); + self::assertTrue($session->contains($fullKey)); } self::assertFalse($session->contains($fullKeyToRemove)); } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testNamespaceKeyIsRemovedFromStore(array $keyValuePairs): void { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); + /** @dataProvider data_randomKeyValuePairs */ + private static function testNamespaceKeyIsRemovedFromStore(array $keyValuePairs, Handler $handler): void { $session = new Session($handler); $parentNamespace = implode(".", [ @@ -336,13 +341,8 @@ public function testNamespaceKeyIsRemovedFromStore(array $keyValuePairs): void { self::assertFalse($store->contains($keyToRemove)); } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testRemoveNamespace(array $keyValuePairs) { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); + /** @dataProvider data_randomKeyValuePairs */ + private static function testRemoveNamespace(array $keyValuePairs, Handler $handler) { $session = new Session($handler); $namespace1 = uniqid("namespace1-"); @@ -379,13 +379,8 @@ public function testRemoveNamespace(array $keyValuePairs) { self::assertFalse($session->contains($parentNamespace)); } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testRemoveSiblingNamespace(array $keyValuePairs) { - /** @var Handler|MockObject $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); + /** @dataProvider data_randomKeyValuePairs */ + private static function testRemoveSiblingNamespace(array $keyValuePairs, Handler $handler) { $session = new Session($handler); $namespace0 = uniqid("namespace0-"); @@ -464,4 +459,4 @@ public function testRemoveSiblingNamespace(array $keyValuePairs) { ); } } -} \ No newline at end of file +} diff --git a/test/phpunit/phpunit.xml b/test/phpunit/phpunit.xml deleted file mode 100644 index cf95bea..0000000 --- a/test/phpunit/phpunit.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - ../../src - - - - - - - - - . - - - - \ No newline at end of file