diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 68a7c5c..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,33 +0,0 @@ -version: 2 - -jobs: - build: - docker: - - image: circleci/php:7.2-cli - - working_directory: ~/repo - - steps: - - checkout - - - restore_cache: - keys: - - v1-dependencies-{{ checksum "composer.json" }} - - v1-dependencies- - - - run: composer install -n --prefer-dist - - - save_cache: - paths: - - ./vendor - key: v1-dependencies-{{ checksum "composer.json" }} - - - run: mkdir test/unit/_junit - - run: vendor/bin/phpunit -c test/unit/phpunit.xml --log-junit test/unit/_junit/junit.xml --coverage-html ./test/unit/_coverage -d memory_limit=512M - - - store_test_results: - path: test/unit/_junit - - - store_artifacts: - path: test/unit/_coverage - destination: TestCoverage \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..8ca7d16 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +# These are supported funding model platforms +github: [phpgt] \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..307abf3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - 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 new file mode 100644 index 0000000..19f7612 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,171 @@ +name: CI + +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@v6 + + - name: Cache Composer dependencies + uses: actions/cache@v5 + with: + path: /tmp/composer-cache + key: ${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }} + + - 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 --exclude=".git" -cvf /tmp/github-actions/build.tar ./ + + - name: Upload build archive for test runners + uses: actions/upload-artifact@v4 + with: + name: build-artifact-${{ matrix.php }} + path: /tmp/github-actions + retention-days: 1 + + phpunit: + runs-on: ubuntu-latest + 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@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 Unit tests + uses: php-actions/phpunit@v4 + env: + XDEBUG_MODE: cover + with: + php_version: ${{ matrix.php }} + php_extensions: xdebug + 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 ] + 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 Static Analysis + 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: + 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 dbbd1cb..0000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,21 +0,0 @@ -build: - environment: - php: - version: 7.1.0 - tests: - override: - - - command: 'vendor/bin/phpunit test/unit --coverage-clover coverage --whitelist src' - coverage: - file: '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 e8c2961..6cac016 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,20 @@ 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 @@ -32,7 +32,7 @@ if($session->contains("auth")) { } else { // Output a variable within the auth namespace: - $message = "Welcome back, " . $session->get("auth.user.name"); + $message = "Welcome back, " . $session->getString("auth.user.name"); } } else { @@ -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 5f37b3b..d11c730 100644 --- a/composer.json +++ b/composer.json @@ -4,20 +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": "^8.0" + "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/unit" + "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 a13f63a..807a21f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,42 +4,35 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "39b657e48de8b924fed3a9a11ac33f16", - "packages": [], - "packages-dev": [ + "content-hash": "5bf8b129b89ebdbd2f4b53a66a2b077e", + "packages": [ { - "name": "doctrine/instantiator", - "version": "1.1.0", + "name": "phpgt/typesafegetter", + "version": "v1.3.3", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + "url": "https://github.com/phpgt/TypeSafeGetter.git", + "reference": "a0d339103828791989cbb81f760d252f3c2f8b8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "url": "https://api.github.com/repos/phpgt/TypeSafeGetter/zipball/a0d339103828791989cbb81f760d252f3c2f8b8c", + "reference": "a0d339103828791989cbb81f760d252f3c2f8b8c", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=8.0" }, "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" + "phpmd/phpmd": "^2.13", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.1", + "squizlabs/php_codesniffer": "^3.7" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + "Gt\\TypeSafeGetter\\": "./src" } }, "notification-url": "https://packagist.org/downloads/", @@ -48,422 +41,436 @@ ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "name": "Greg Bowler", + "email": "greg.bowler@g105b.com" } ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", - "keywords": [ - "constructor", - "instantiate" + "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": "2017-07-22T11:58:36+00:00" - }, + "time": "2026-03-10T22:28:01+00:00" + } + ], + "packages-dev": [ { - "name": "myclabs/deep-copy", - "version": "1.8.1", + "name": "composer/pcre", + "version": "3.3.2", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.4 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" + "conflict": { + "phpstan/phpstan": "<1.11.10" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, - "files": [ - "src/DeepCopy/deep_copy.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "time": "2018-06-11T23:09:50+00:00" - }, - { - "name": "phar-io/manifest", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-phar": "*", - "phar-io/version": "^2.0", - "php": "^5.6 || ^7.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-master": "1.0.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Composer\\Pcre\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "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" }, { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" + "url": "https://github.com/composer", + "type": "github" }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" } ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2018-07-08T19:23:20+00:00" + "time": "2024-11-12T16:29:46+00:00" }, { - "name": "phar-io/version", - "version": "2.0.1", + "name": "composer/xdebug-handler", + "version": "3.0.5", "source": { "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "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": { - "classmap": [ - "src/" - ] + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "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://packagist.com", + "type": "custom" }, { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" + "url": "https://github.com/composer", + "type": "github" }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" } ], - "description": "Library for handling version information and constraints", - "time": "2018-07-08T19:19:57+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "1.0.1", + "name": "myclabs/deep-copy", + "version": "1.13.4", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { - "phpunit/phpunit": "^4.6" + "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", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] + "DeepCopy\\": "src/DeepCopy/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" } ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "time": "2017-09-11T18:02:19+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { - "name": "phpdocumentor/reflection-docblock", - "version": "4.3.0", + "name": "nikic/php-parser", + "version": "v5.7.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { - "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", - "webmozart/assert": "^1.0" + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" }, "require-dev": { - "doctrine/instantiator": "~1.0.5", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^6.4" + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" }, + "bin": [ + "bin/php-parse" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "PhpParser\\": "lib/PhpParser" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "name": "Nikita Popov" } ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-30T07:14:17+00:00" + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "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": "phpdocumentor/type-resolver", - "version": "0.4.0", + "name": "pdepend/pdepend", + "version": "2.16.2", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + "url": "https://github.com/pdepend/pdepend.git", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" + "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": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "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": "1.0.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "PDepend\\": "src/main/php/PDepend" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ + "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": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend", + "type": "tidelift" } ], - "time": "2017-07-14T14:27:02+00:00" + "time": "2023-12-17T18:09:59+00:00" }, { - "name": "phpspec/prophecy", - "version": "1.8.0", + "name": "phar-io/manifest", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" - }, - "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } + "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": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" }, { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "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" } ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2018-08-05T17:53:17+00:00" + "time": "2024-03-03T12:33:53+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "7.0.2", + "name": "phar-io/version", + "version": "3.2.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "cfca9c5f7f2694ca0c7749ffb142927d9f05250f" + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/cfca9c5f7f2694ca0c7749ffb142927d9f05250f", - "reference": "cfca9c5f7f2694ca0c7749ffb142927d9f05250f", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-xmlwriter": "*", - "php": "^7.2", - "phpunit/php-file-iterator": "^2.0.2", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0.1", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^4.1", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" - }, - "require-dev": { - "phpunit/phpunit": "^8.0" - }, - "suggest": { - "ext-xdebug": "^2.6.1" + "php": "^7.2 || ^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "7.0-dev" - } - }, "autoload": { "classmap": [ "src/" @@ -474,51 +481,65 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", - "role": "lead" + "role": "Developer" } ], - "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" - ], - "time": "2019-02-15T13:40:27+00:00" + "description": "Library for handling version information and constraints", + "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": "phpunit/php-file-iterator", - "version": "2.0.2", + "name": "phpmd/phpmd", + "version": "2.15.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "050bedf145a257b1ff02746c31894800e5122946" + "url": "https://github.com/phpmd/phpmd.git", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", - "reference": "050bedf145a257b1ff02746c31894800e5122946", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0", "shasum": "" }, "require": { - "php": "^7.1" + "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", + "ext-xml": "*", + "pdepend/pdepend": "^2.16.1", + "php": ">=5.3.9" }, "require-dev": { - "phpunit/phpunit": "^7.1" + "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", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "psr-0": { + "PHPMD\\": "src/main/php" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -526,84 +547,139 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "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": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "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": [ - "filesystem", - "iterator" + "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": "2018-09-13T20:33:42+00:00" + "time": "2023-12-11T08:22:20+00:00" }, { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, + "name": "phpstan/phpstan", + "version": "2.1.51", "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc3b523c45e714c70de2ac5113b958223b55dc59", + "reference": "dc3b523c45e714c70de2ac5113b958223b55dc59", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.4|^8.0" }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], "type": "library", "autoload": { - "classmap": [ - "src/" + "files": [ + "bootstrap.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "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": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2015-06-21T13:50:34+00:00" + "time": "2026-04-21T18:22:01+00:00" }, { - "name": "phpunit/php-timer", - "version": "2.1.1", + "name": "phpunit/php-code-coverage", + "version": "10.1.16", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b389aebe1b8b0578430bda0c7c95a829608e059", - "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { - "php": "^7.1" + "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": { - "phpunit/phpunit": "^7.0" + "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-master": "2.1-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { @@ -622,38 +698,50 @@ "role": "lead" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ - "timer" + "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" + } ], - "time": "2019-02-20T10:12:59+00:00" + "time": "2024-08-22T04:31:57+00:00" }, { - "name": "phpunit/php-token-stream", - "version": "3.0.1", + "name": "phpunit/php-file-iterator", + "version": "4.1.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18" + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18", - "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": "^7.1" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -668,71 +756,57 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", "keywords": [ - "tokenizer" + "filesystem", + "iterator" + ], + "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" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2018-10-30T05:52:18+00:00" + "time": "2023-08-31T06:24:48+00:00" }, { - "name": "phpunit/phpunit", - "version": "8.0.4", + "name": "phpunit/php-invoker", + "version": "4.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a7af0201285445c9c73c4bdf869c486e36b41604" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a7af0201285445c9c73c4bdf869c486e36b41604", - "reference": "a7af0201285445c9c73c4bdf869c486e36b41604", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.1", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.7", - "phar-io/manifest": "^1.0.2", - "phar-io/version": "^2.0", - "php": "^7.2", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^7.0", - "phpunit/php-file-iterator": "^2.0.1", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.0", - "sebastian/comparator": "^3.0", - "sebastian/diff": "^3.0", - "sebastian/environment": "^4.1", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^3.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0", - "sebastian/version": "^2.0.1" + "php": ">=8.1" }, "require-dev": { - "ext-pdo": "*" + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0" + "ext-pcntl": "*" }, - "bin": [ - "phpunit" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "8.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -751,39 +825,47 @@ "role": "lead" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ - "phpunit", - "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": "2019-02-18T09:23:05+00:00" + "time": "2023-02-03T06:56:09+00:00" }, { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.1", + "name": "phpunit/php-text-template", + "version": "3.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -798,39 +880,52 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "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": "2023-08-31T14:07:24+00:00" }, { - "name": "sebastian/comparator", - "version": "3.0.2", + "name": "phpunit/php-timer", + "version": "6.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", - "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", "shasum": "" }, "require": { - "php": "^7.1", - "sebastian/diff": "^3.0", - "sebastian/exporter": "^3.1" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^7.1" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -843,60 +938,87 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ - "comparator", - "compare", - "equality" + "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": "2018-07-12T15:12:46+00:00" + "time": "2023-02-03T06:57:52+00:00" }, { - "name": "sebastian/diff", - "version": "3.0.2", + "name": "phpunit/phpunit", + "version": "10.5.63", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "33198268dad71e926626b618f3ec3966661e4d90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", - "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", + "reference": "33198268dad71e926626b618f3ec3966661e4d90", "shasum": "" }, "require": { - "php": "^7.1" + "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": "^7.5 || ^8.0", - "symfony/process": "^2 || ^3.3 || ^4" + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" }, + "bin": [ + "phpunit" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "10.5-dev" } }, "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], "classmap": [ "src/" ] @@ -906,175 +1028,175 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" + "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": "2019-02-04T06:01:07+00:00" + "time": "2026-01-27T05:48:37+00:00" }, { - "name": "sebastian/environment", - "version": "4.1.0", + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6fda8ce1974b62b14935adc02a9ed38252eca656", - "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": "^7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.5" - }, - "suggest": { - "ext-posix": "*" + "php": ">=7.4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-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" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "Xdebug", - "environment", - "hhvm" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], - "time": "2019-02-01T05:27:49+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": "sebastian/exporter", - "version": "3.1.0", + "name": "psr/log", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^6.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Log\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "export", - "exporter" + "log", + "psr", + "psr-3" ], - "time": "2017-04-03T13:19:02+00:00" + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" }, { - "name": "sebastian/global-state", - "version": "3.0.0", + "name": "sebastian/cli-parser", + "version": "2.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", - "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", "shasum": "" }, "require": { - "php": "^7.2", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "php": ">=8.1" }, "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^8.0" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -1089,42 +1211,49 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" + "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": "2019-02-01T05:30:01+00:00" + "time": "2024-03-02T07:12:49+00:00" }, { - "name": "sebastian/object-enumerator", - "version": "3.0.3", + "name": "sebastian/code-unit", + "version": "2.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -1139,37 +1268,48 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03T12:35:26+00:00" + "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": "2023-02-03T06:58:43+00:00" }, { - "name": "sebastian/object-reflector", - "version": "1.1.1", + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "773f97c67f28de00d397be301821b06708fca0be" + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", - "reference": "773f97c67f28de00d397be301821b06708fca0be", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1187,34 +1327,48 @@ "email": "sebastian@phpunit.de" } ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29T09:07:27+00:00" + "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": "2023-02-03T06:59:15+00:00" }, { - "name": "sebastian/recursion-context", - "version": "3.0.0", + "name": "sebastian/comparator", + "version": "5.0.5", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", "shasum": "" }, "require": { - "php": "^7.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1227,44 +1381,80 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Volker Dusch", + "email": "github@wallbash.com" }, { - "name": "Adam Harvey", - "email": "aharvey@php.net" + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" } ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03T06:23:57+00:00" + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "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": "2026-01-24T09:25:16+00:00" }, { - "name": "sebastian/resource-operations", - "version": "2.0.1", + "name": "sebastian/complexity", + "version": "3.2.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", - "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", "shasum": "" }, "require": { - "php": "^7.1" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.2-dev" } }, "autoload": { @@ -1279,34 +1469,50 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "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" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2018-10-04T04:07:39+00:00" + "time": "2023-12-21T08:37:17+00:00" }, { - "name": "sebastian/version", - "version": "2.0.1", + "name": "sebastian/diff", + "version": "5.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -1321,93 +1527,217 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03T07:35:21+00:00" + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "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": "2024-03-02T07:15:17+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.10.0", + "name": "sebastian/environment", + "version": "6.1.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" }, "suggest": { - "ext-ctype": "For best performance" + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-main": "6.1-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, - "files": [ - "bootstrap.php" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "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": "2024-03-23T08:47:14+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "0735b90f4da94969541dac1da743446e276defa6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" + "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": "2018-08-06T14:22:27+00:00" + "time": "2025-09-24T06:09:11+00:00" }, { - "name": "theseer/tokenizer", - "version": "1.1.0", + "name": "sebastian/global-state", + "version": "6.0.2", "source": { "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", "shasum": "" }, "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.0" + "phpunit/phpunit": "^10.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -1419,73 +1749,1157 @@ ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "description": "Snapshotting of 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": "2024-03-02T07:19:19+00:00" }, { - "name": "webmozart/assert", - "version": "1.4.0", + "name": "sebastian/lines-of-code", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", - "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0", - "symfony/polyfill-ctype": "^1.8" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-main": "2.0-dev" } }, "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" + "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": "2023-12-21T08:38:20+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "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": "2023-02-03T07:08:32+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "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": "2023-02-03T07:06:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "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": "2025-08-10T07:50:56+00:00" + }, + { + "name": "sebastian/type", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "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": "2023-02-03T07:10:45+00:00" + }, + { + "name": "sebastian/version", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "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": "2023-02-07T11:34:05+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.13.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "shasum": "" + }, + "require": { + "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-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "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": "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://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "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.36.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.36.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.36.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", + "shasum": "" + }, + "require": { + "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": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for 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://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-04-10T17:25:58+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "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": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "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": "2018-12-25T11:19:39+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": [] + "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 5a47b47..eab4aa7 100644 --- a/src/Session.php +++ b/src/Session.php @@ -1,53 +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; - 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, - ]); + $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); } @@ -57,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"], @@ -70,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); } @@ -99,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, @@ -119,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 new file mode 100644 index 0000000..ed2ab60 --- /dev/null +++ b/src/SessionArrayWrapper.php @@ -0,0 +1,32 @@ + */ + private array $sourceArray; + + /** @param array &$sourceArray */ + public function __construct(array &$sourceArray) { + $this->sourceArray = &$sourceArray; + } + + public function get(string $key):mixed { + return $this->sourceArray[$key] ?? null; + } + + public function set(string $key, mixed $value):void { + $this->sourceArray[$key] = $value; + } + + public function contains(string $key):bool { + return isset($this->sourceArray[$key]); + } + + public function remove(string $key):void { + if(!$this->contains($key)) { + return; + } + + unset($this->sourceArray[$key]); + } +} diff --git a/src/SessionContainer.php b/src/SessionContainer.php new file mode 100644 index 0000000..68d121c --- /dev/null +++ b/src/SessionContainer.php @@ -0,0 +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 = []; + 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 { @@ -41,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 { @@ -68,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)) { @@ -85,7 +92,7 @@ public function getStore( } $namespace = implode(".", $namespaceParts); - return $store->getStore($namespace); + return $store->getStore($namespace, $createIfNotExists); } public function setStore( @@ -116,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, "."); @@ -158,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, "."); @@ -167,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]); } @@ -190,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); } @@ -212,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/unit/Helper/DataProvider/ConfigProvider.php b/test/phpunit/Helper/DataProvider/ConfigProvider.php similarity index 78% rename from test/unit/Helper/DataProvider/ConfigProvider.php rename to test/phpunit/Helper/DataProvider/ConfigProvider.php index 541d7c1..1f6d440 100644 --- a/test/unit/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 new file mode 100644 index 0000000..5ca7b31 --- /dev/null +++ b/test/phpunit/SessionArrayWrapperTest.php @@ -0,0 +1,50 @@ +contains($key)); + $sut->set($key, "test-value"); + self::assertTrue($sut->contains($key)); + } + + public function testSet() { + $sessionArray = []; + $key = uniqid(); + $value = uniqid(); + $sut = new SessionArrayWrapper($sessionArray); + $sut->set($key, $value); + self::assertEquals($value, $sessionArray[$key]); + } + + public function testGet() { + $sessionArray = []; + $key = uniqid(); + $value = uniqid(); + $sut = new SessionArrayWrapper($sessionArray); + self::assertNull($sut->get($key)); + self::assertArrayNotHasKey($key, $sessionArray); + $sut->set($key, $value); + self::assertEquals($value, $sut->get($key)); + self::assertEquals($value, $sessionArray[$key]); + } + + public function testRemove() { + $key = uniqid(); + $value = uniqid(); + $sessionArray = [ + $key => $value, + ]; + $sut = new SessionArrayWrapper($sessionArray); + self::assertTrue($sut->contains($key)); + $sut->remove($key); + self::assertFalse($sut->contains($key)); + self::assertArrayNotHasKey($key, $sessionArray); + } +} \ No newline at end of file diff --git a/test/phpunit/SessionStoreTest.php b/test/phpunit/SessionStoreTest.php new file mode 100644 index 0000000..334e873 --- /dev/null +++ b/test/phpunit/SessionStoreTest.php @@ -0,0 +1,182 @@ +createMock($className); + } + + /** @dataProvider data_randomKeyValuePairs */ + public static function testGetSetDotNotation(array $keyValuePairs, Handler $handler):void { + $session = new Session($handler); + + $sessionNamespace = "gt.test.session"; + + foreach($keyValuePairs as $key => $value) { + $fullKey = "$sessionNamespace.$key"; + $session->set($fullKey, $value); + } + + foreach($keyValuePairs as $key => $value) { + $fullKey = "$sessionNamespace.$key"; + self::assertEquals($value, $session->get($fullKey)); + } + } + + public function testRemoveSelf():void { + /** @var MockObject|SessionHandler $handler */ + $handler = $this->getMockBuilder(Handler::class) + ->getMock(); + $session = new Session($handler); + + $leafStore = $session->getStore( + "gt.test.session.trunk.leaf", + true + ); + $trunkStore = $session->getStore( + "gt.test.session.trunk" + ); + + $trunkStore->remove(); + + self::assertNull($session->getStore("gt.test.session.trunk")); + self::assertNull($session->getStore("gt.test.session.trunk.leaf")); + self::assertInstanceOf( + SessionStore::class, + $session->getStore("gt.test.session") + ); + } + + public function testGetCreatesNonExistantStore() { + /** @var MockObject|SessionHandler $handler */ + $handler = $this->getMockBuilder(Handler::class) + ->getMock(); + $session = new Session($handler); + + $trunkStore = $session->getStore( + "gt.test.session", + true + ); + $leafStore = $session->getStore( + "gt.test.session.leaf", + true + ); + + self::assertNotNull($leafStore); + } + + 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/unit/SessionTest.php b/test/phpunit/SessionTest.php similarity index 50% rename from test/unit/SessionTest.php rename to test/phpunit/SessionTest.php index 1d4fc1d..bb4ec01 100644 --- a/test/unit/SessionTest.php +++ b/test/phpunit/SessionTest.php @@ -1,12 +1,14 @@ 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"]); - $session = new Session($handler); + $handler = self::createMock(Handler::class); + new Session($handler); self::assertCount( 1, FunctionMocker::$mockCalls["session_start"] ); } - /** - * @dataProvider data_randomConfig - */ - public function testSessionStartsWithConfig(array $config):void { - $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, + ]); - $session = 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) { @@ -49,33 +125,21 @@ public function testSessionStartsWithConfig(array $config):void { } } - public function testWriteSessionDataCalled() { - $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_randomConfig */ + 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"]); } - /** - * @dataProvider data_randomString - */ - public function testGetReturnsNull(string $randomString):void { - $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 { - $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) { @@ -87,12 +151,8 @@ public function testSetGet(array $keyValuePairs):void { } } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testSetGetNamespaced(array $keyValuePairs):void { - $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) { @@ -114,43 +174,35 @@ public function testSetGetNamespaced(array $keyValuePairs):void { } } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testSetGetNamespacedSameParentNamespace(array $keyValuePairs):void { - $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 { - $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) { @@ -175,12 +227,8 @@ public function testSetGetNotExistsOtherKey(array $keyValuePairs):void { } } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testContains(array $keyValuePairs):void { - $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) { @@ -192,12 +240,8 @@ public function testContains(array $keyValuePairs):void { } } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testNotContains(array $keyValuePairs):void { - $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) { @@ -209,19 +253,15 @@ public function testNotContains(array $keyValuePairs):void { } } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testRemove(array $keyValuePairs):void { - $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); }); @@ -232,55 +272,47 @@ public function testRemove(array $keyValuePairs):void { } } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testNamespaceKeyIsRemovedFromSession(array $keyValuePairs):void { - $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 { - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); + /** @dataProvider data_randomKeyValuePairs */ + private static function testNamespaceKeyIsRemovedFromStore(array $keyValuePairs, Handler $handler): void { $session = new Session($handler); $parentNamespace = implode(".", [ @@ -309,12 +341,8 @@ public function testNamespaceKeyIsRemovedFromStore(array $keyValuePairs): void { self::assertFalse($store->contains($keyToRemove)); } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testRemoveNamespace(array $keyValuePairs) { - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); + /** @dataProvider data_randomKeyValuePairs */ + private static function testRemoveNamespace(array $keyValuePairs, Handler $handler) { $session = new Session($handler); $namespace1 = uniqid("namespace1-"); @@ -351,12 +379,8 @@ public function testRemoveNamespace(array $keyValuePairs) { self::assertFalse($session->contains($parentNamespace)); } - /** - * @dataProvider data_randomKeyValuePairs - */ - public function testRemoveSiblingNamespace(array $keyValuePairs) { - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); + /** @dataProvider data_randomKeyValuePairs */ + private static function testRemoveSiblingNamespace(array $keyValuePairs, Handler $handler) { $session = new Session($handler); $namespace0 = uniqid("namespace0-"); @@ -435,4 +459,4 @@ public function testRemoveSiblingNamespace(array $keyValuePairs) { ); } } -} \ No newline at end of file +} diff --git a/test/unit/Helper/FunctionOverride/session_id.php b/test/unit/Helper/FunctionOverride/session_id.php deleted file mode 100644 index a2c22a9..0000000 --- a/test/unit/Helper/FunctionOverride/session_id.php +++ /dev/null @@ -1,6 +0,0 @@ -getMockBuilder(Handler::class) - ->getMock(); - $session = new Session($handler); - - $sessionNamespace = "gt.test.session"; - - foreach($keyValuePairs as $key => $value) { - $fullKey = "$sessionNamespace.$key"; - $session->set($fullKey, $value); - } - - foreach($keyValuePairs as $key => $value) { - $fullKey = "$sessionNamespace.$key"; - self::assertEquals($value, $session->get($fullKey)); - } - } - - public function testRemoveSelf():void { - /** @var MockObject|SessionHandler $handler */ - $handler = $this->getMockBuilder(Handler::class) - ->getMock(); - $session = new Session($handler); - - $leafStore = $session->getStore( - "gt.test.session.trunk.leaf", - true - ); - $trunkStore = $session->getStore( - "gt.test.session.trunk" - ); - - $trunkStore->remove(); - - self::assertNull($session->getStore("gt.test.session.trunk")); - self::assertNull($session->getStore("gt.test.session.trunk.leaf")); - self::assertInstanceOf( - SessionStore::class, - $session->getStore("gt.test.session") - ); - } -} \ No newline at end of file diff --git a/test/unit/phpunit.xml b/test/unit/phpunit.xml deleted file mode 100644 index bb2e951..0000000 --- a/test/unit/phpunit.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - . - - - - - - ../../src - - - \ No newline at end of file