diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 340fdf4..d9ea196 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,45 +1,77 @@
name: CI
-on: push
+on:
+ push:
+ branches: [ master ]
+ schedule:
+ - cron: "0 6 * * 3"
+ pull_request:
+ branches: [ master ]
jobs:
all:
runs-on: ${{ matrix.operating-system }}
strategy:
+ fail-fast: false
matrix:
operating-system:
- ubuntu-latest
- # - windows-latest # Disabled - apparently checkouts have \r\n which breaks phpcs
- - macos-latest
php-versions:
- - '7.4'
- - '8.0'
- - '8.1'
- '8.2'
- '8.3'
- '8.4'
name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }}
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: mbstring, intl
- ini-values: post_max_size=256M, short_open_tag=On
- coverage: xdebug
- tools: php-cs-fixer, phpunit:7
+ coverage: pcov
+
+ - name: Get Composer Cache Directory 2
+ id: composer-cache
+ run: |
+ echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+ - uses: actions/cache@v4
+ id: actions-cache
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-composer-
+
+ - name: Cache PHP dependencies
+ uses: actions/cache@v4
+ id: vendor-cache
+ with:
+ path: vendor
+ key: ${{ runner.os }}-build-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}
- name: Composer Install
run: composer install --no-progress
- name: Code style checks
- run: ./vendor/bin/phpcs .
+ run: ./vendor/bin/phpcs
+
+ - name: PHPStan code analysis
+ run: php vendor/bin/phpstan analyze
+
+ - name: PHPinsights code analysis
+ run: php vendor/bin/phpinsights analyse --no-interaction || true
+
+ - name: Execute Rector
+ run: vendor/bin/rector --dry-run
- name: Unit tests
- run: ./vendor/bin/phpunit
+ run: ./vendor/bin/phpunit --coverage-clover=coverage.xml
- - name: PHPStan
- run: ./vendor/bin/phpstan analyze
+ - name: Send code coverage report to Codecov.io
+ uses: codecov/codecov-action@v4
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ files: coverage.xml
diff --git a/.gitignore b/.gitignore
index 5be813f..c6c60d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,4 @@ vendor/
build/
phpunit.xml
.idea/
-composer.lock
.phpunit.result.cache
diff --git a/composer.json b/composer.json
index ae9c8a9..4ceabba 100644
--- a/composer.json
+++ b/composer.json
@@ -12,15 +12,18 @@
}
],
"require": {
- "php": "^7.2 | ^8.0 | ^8.1",
+ "php" : ">=8.1",
"symfony/polyfill-mbstring": ">=1.3.1",
"ext-json": "*"
},
"require-dev": {
- "phpunit/phpunit": "^8.5",
+ "phpunit/phpunit" : "^10 || ^11 ",
"php-coveralls/php-coveralls": "*",
- "squizlabs/php_codesniffer": "3.*",
- "phpstan/phpstan": "^2.0"
+ "squizlabs/php_codesniffer" : ">=3.8",
+ "phpstan/phpstan": "^2",
+ "phpstan/phpstan-phpunit": "^2",
+ "rector/rector": "^2",
+ "nunomaduro/phpinsights": "^2"
},
"suggest": {
"ext-gmp": "Required for optimized binomial calculations (also requires PHP >= 7.3)"
@@ -30,5 +33,10 @@
},
"autoload-dev": {
"psr-4": { "ZxcvbnPhp\\Test\\": "test/" }
+ },
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ }
}
}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..7192018
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,6270 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "2cd25cbfa73580e7a9697c4654eecc8f",
+ "packages": [
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "shasum": ""
+ },
+ "require": {
+ "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.31.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-09T11:45:10+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "clue/ndjson-react",
+ "version": "v1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/clue/reactphp-ndjson.git",
+ "reference": "392dc165fce93b5bb5c637b67e59619223c931b0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0",
+ "reference": "392dc165fce93b5bb5c637b67e59619223c931b0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/stream": "^1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35",
+ "react/event-loop": "^1.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Clue\\React\\NDJson\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering"
+ }
+ ],
+ "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.",
+ "homepage": "https://github.com/clue/reactphp-ndjson",
+ "keywords": [
+ "NDJSON",
+ "json",
+ "jsonlines",
+ "newline",
+ "reactphp",
+ "streaming"
+ ],
+ "support": {
+ "issues": "https://github.com/clue/reactphp-ndjson/issues",
+ "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://clue.engineering/support",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/clue",
+ "type": "github"
+ }
+ ],
+ "time": "2022-12-23T10:58:28+00:00"
+ },
+ {
+ "name": "cmgmyr/phploc",
+ "version": "8.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cmgmyr/phploc.git",
+ "reference": "b0c4ec71f40ef84c9893e1a7212a72e1098b90f7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/cmgmyr/phploc/zipball/b0c4ec71f40ef84c9893e1a7212a72e1098b90f7",
+ "reference": "b0c4ec71f40ef84c9893e1a7212a72e1098b90f7",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "php": "^7.4 || ^8.0",
+ "phpunit/php-file-iterator": "^3.0|^4.0|^5.0",
+ "sebastian/cli-parser": "^1.0|^2.0|^3.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.2",
+ "phpunit/phpunit": "^9.0|^10.0",
+ "vimeo/psalm": "^5.7"
+ },
+ "bin": [
+ "phploc"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "8.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Chris Gmyr",
+ "email": "cmgmyr@gmail.com",
+ "role": "lead"
+ }
+ ],
+ "description": "A tool for quickly measuring the size of a PHP project.",
+ "homepage": "https://github.com/cmgmyr/phploc",
+ "support": {
+ "issues": "https://github.com/cmgmyr/phploc/issues",
+ "source": "https://github.com/cmgmyr/phploc/tree/8.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/cmgmyr",
+ "type": "github"
+ }
+ ],
+ "time": "2024-10-31T19:26:53+00:00"
+ },
+ {
+ "name": "composer/pcre",
+ "version": "3.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/pcre.git",
+ "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
+ "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan": "<1.11.10"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.12 || ^2",
+ "phpstan/phpstan-strict-rules": "^1 || ^2",
+ "phpunit/phpunit": "^8 || ^9"
+ },
+ "type": "library",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ },
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Pcre\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "PCRE wrapping library that offers type-safe preg_* replacements.",
+ "keywords": [
+ "PCRE",
+ "preg",
+ "regex",
+ "regular expression"
+ ],
+ "support": {
+ "issues": "https://github.com/composer/pcre/issues",
+ "source": "https://github.com/composer/pcre/tree/3.3.2"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-11-12T16:29:46+00:00"
+ },
+ {
+ "name": "composer/semver",
+ "version": "3.4.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/semver.git",
+ "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
+ "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.2 || ^7.0 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.11",
+ "symfony/phpunit-bridge": "^3 || ^7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Semver\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nils Adermann",
+ "email": "naderman@naderman.de",
+ "homepage": "http://www.naderman.de"
+ },
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ },
+ {
+ "name": "Rob Bast",
+ "email": "rob.bast@gmail.com",
+ "homepage": "http://robbast.nl"
+ }
+ ],
+ "description": "Semver library that offers utilities, version constraint parsing and validation.",
+ "keywords": [
+ "semantic",
+ "semver",
+ "validation",
+ "versioning"
+ ],
+ "support": {
+ "irc": "ircs://irc.libera.chat:6697/composer",
+ "issues": "https://github.com/composer/semver/issues",
+ "source": "https://github.com/composer/semver/tree/3.4.3"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-19T14:15:21+00:00"
+ },
+ {
+ "name": "composer/xdebug-handler",
+ "version": "3.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/xdebug-handler.git",
+ "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef",
+ "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef",
+ "shasum": ""
+ },
+ "require": {
+ "composer/pcre": "^1 || ^2 || ^3",
+ "php": "^7.2.5 || ^8.0",
+ "psr/log": "^1 || ^2 || ^3"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.0",
+ "phpstan/phpstan-strict-rules": "^1.1",
+ "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Composer\\XdebugHandler\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "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"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-05-06T16:37:16+00:00"
+ },
+ {
+ "name": "dealerdirect/phpcodesniffer-composer-installer",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCSStandards/composer-installer.git",
+ "reference": "4be43904336affa5c2f70744a348312336afd0da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da",
+ "reference": "4be43904336affa5c2f70744a348312336afd0da",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0 || ^2.0",
+ "php": ">=5.4",
+ "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0"
+ },
+ "require-dev": {
+ "composer/composer": "*",
+ "ext-json": "*",
+ "ext-zip": "*",
+ "php-parallel-lint/php-parallel-lint": "^1.3.1",
+ "phpcompatibility/php-compatibility": "^9.0",
+ "yoast/phpunit-polyfills": "^1.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Franck Nijhof",
+ "email": "franck.nijhof@dealerdirect.com",
+ "homepage": "http://www.frenck.nl",
+ "role": "Developer / IT Manager"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors"
+ }
+ ],
+ "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
+ "homepage": "http://www.dealerdirect.com",
+ "keywords": [
+ "PHPCodeSniffer",
+ "PHP_CodeSniffer",
+ "code quality",
+ "codesniffer",
+ "composer",
+ "installer",
+ "phpcbf",
+ "phpcs",
+ "plugin",
+ "qa",
+ "quality",
+ "standard",
+ "standards",
+ "style guide",
+ "stylecheck",
+ "tests"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPCSStandards/composer-installer/issues",
+ "source": "https://github.com/PHPCSStandards/composer-installer"
+ },
+ "time": "2023-01-05T11:28:13+00:00"
+ },
+ {
+ "name": "evenement/evenement",
+ "version": "v3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/igorw/evenement.git",
+ "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc",
+ "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9 || ^6"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Evenement\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ }
+ ],
+ "description": "Événement is a very simple event dispatching library for PHP",
+ "keywords": [
+ "event-dispatcher",
+ "event-emitter"
+ ],
+ "support": {
+ "issues": "https://github.com/igorw/evenement/issues",
+ "source": "https://github.com/igorw/evenement/tree/v3.0.2"
+ },
+ "time": "2023-08-08T05:53:35+00:00"
+ },
+ {
+ "name": "fidry/cpu-core-counter",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theofidry/cpu-core-counter.git",
+ "reference": "8520451a140d3f46ac33042715115e290cf5785f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f",
+ "reference": "8520451a140d3f46ac33042715115e290cf5785f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "require-dev": {
+ "fidry/makefile": "^0.2.0",
+ "fidry/php-cs-fixer-config": "^1.1.2",
+ "phpstan/extension-installer": "^1.2.0",
+ "phpstan/phpstan": "^1.9.2",
+ "phpstan/phpstan-deprecation-rules": "^1.0.0",
+ "phpstan/phpstan-phpunit": "^1.2.2",
+ "phpstan/phpstan-strict-rules": "^1.4.4",
+ "phpunit/phpunit": "^8.5.31 || ^9.5.26",
+ "webmozarts/strict-phpunit": "^7.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Fidry\\CpuCoreCounter\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Théo FIDRY",
+ "email": "theo.fidry@gmail.com"
+ }
+ ],
+ "description": "Tiny utility to get the number of CPU cores.",
+ "keywords": [
+ "CPU",
+ "core"
+ ],
+ "support": {
+ "issues": "https://github.com/theofidry/cpu-core-counter/issues",
+ "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theofidry",
+ "type": "github"
+ }
+ ],
+ "time": "2024-08-06T10:04:20+00:00"
+ },
+ {
+ "name": "friendsofphp/php-cs-fixer",
+ "version": "v3.66.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
+ "reference": "cde186799d8e92960c5a00c96e6214bf7f5547a9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/cde186799d8e92960c5a00c96e6214bf7f5547a9",
+ "reference": "cde186799d8e92960c5a00c96e6214bf7f5547a9",
+ "shasum": ""
+ },
+ "require": {
+ "clue/ndjson-react": "^1.0",
+ "composer/semver": "^3.4",
+ "composer/xdebug-handler": "^3.0.3",
+ "ext-filter": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "fidry/cpu-core-counter": "^1.2",
+ "php": "^7.4 || ^8.0",
+ "react/child-process": "^0.6.5",
+ "react/event-loop": "^1.0",
+ "react/promise": "^2.0 || ^3.0",
+ "react/socket": "^1.0",
+ "react/stream": "^1.0",
+ "sebastian/diff": "^4.0 || ^5.1 || ^6.0",
+ "symfony/console": "^5.4 || ^6.4 || ^7.0",
+ "symfony/event-dispatcher": "^5.4 || ^6.4 || ^7.0",
+ "symfony/filesystem": "^5.4 || ^6.4 || ^7.0",
+ "symfony/finder": "^5.4 || ^6.4 || ^7.0",
+ "symfony/options-resolver": "^5.4 || ^6.4 || ^7.0",
+ "symfony/polyfill-mbstring": "^1.31",
+ "symfony/polyfill-php80": "^1.31",
+ "symfony/polyfill-php81": "^1.31",
+ "symfony/process": "^5.4 || ^6.4 || ^7.2",
+ "symfony/stopwatch": "^5.4 || ^6.4 || ^7.0"
+ },
+ "require-dev": {
+ "facile-it/paraunit": "^1.3.1 || ^2.4",
+ "infection/infection": "^0.29.8",
+ "justinrainbow/json-schema": "^5.3 || ^6.0",
+ "keradus/cli-executor": "^2.1",
+ "mikey179/vfsstream": "^1.6.12",
+ "php-coveralls/php-coveralls": "^2.7",
+ "php-cs-fixer/accessible-object": "^1.1",
+ "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5",
+ "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5",
+ "phpunit/phpunit": "^9.6.22 || ^10.5.40 || ^11.5.2",
+ "symfony/var-dumper": "^5.4.48 || ^6.4.15 || ^7.2.0",
+ "symfony/yaml": "^5.4.45 || ^6.4.13 || ^7.2.0"
+ },
+ "suggest": {
+ "ext-dom": "For handling output formats in XML",
+ "ext-mbstring": "For handling non-UTF8 characters."
+ },
+ "bin": [
+ "php-cs-fixer"
+ ],
+ "type": "application",
+ "autoload": {
+ "psr-4": {
+ "PhpCsFixer\\": "src/"
+ },
+ "exclude-from-classmap": [
+ "src/Fixer/Internal/*"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Dariusz Rumiński",
+ "email": "dariusz.ruminski@gmail.com"
+ }
+ ],
+ "description": "A tool to automatically fix PHP code style",
+ "keywords": [
+ "Static code analysis",
+ "fixer",
+ "standards",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
+ "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.66.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/keradus",
+ "type": "github"
+ }
+ ],
+ "time": "2025-01-05T14:43:25+00:00"
+ },
+ {
+ "name": "guzzlehttp/guzzle",
+ "version": "7.9.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "d281ed313b989f213357e3be1a179f02196ac99b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
+ "reference": "d281ed313b989f213357e3be1a179f02196ac99b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/promises": "^1.5.3 || ^2.0.3",
+ "guzzlehttp/psr7": "^2.7.0",
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-client": "^1.0",
+ "symfony/deprecation-contracts": "^2.2 || ^3.0"
+ },
+ "provide": {
+ "psr/http-client-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "ext-curl": "*",
+ "guzzle/client-integration-tests": "3.0.2",
+ "php-http/message-factory": "^1.1",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20",
+ "psr/log": "^1.1 || ^2.0 || ^3.0"
+ },
+ "suggest": {
+ "ext-curl": "Required for CURL handler support",
+ "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+ "psr/log": "Required for using the Log middleware"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/functions_include.php"
+ ],
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Jeremy Lindblom",
+ "email": "jeremeamia@gmail.com",
+ "homepage": "https://github.com/jeremeamia"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "Guzzle is a PHP HTTP client library",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "psr-18",
+ "psr-7",
+ "rest",
+ "web service"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/guzzle/issues",
+ "source": "https://github.com/guzzle/guzzle/tree/7.9.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-07-24T11:22:20+00:00"
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
+ "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "Guzzle promises library",
+ "keywords": [
+ "promise"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/promises/issues",
+ "source": "https://github.com/guzzle/promises/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-10-17T10:06:22+00:00"
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "2.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
+ "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.1 || ^2.0",
+ "ralouphie/getallheaders": "^3.0"
+ },
+ "provide": {
+ "psr/http-factory-implementation": "1.0",
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "http-interop/http-factory-tests": "0.9.0",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+ },
+ "suggest": {
+ "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://sagikazarmark.hu"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/psr7/issues",
+ "source": "https://github.com/guzzle/psr7/tree/2.7.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-07-18T11:15:46+00:00"
+ },
+ {
+ "name": "justinrainbow/json-schema",
+ "version": "5.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jsonrainbow/json-schema.git",
+ "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8",
+ "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1",
+ "json-schema/json-schema-test-suite": "1.2.0",
+ "phpunit/phpunit": "^4.8.35"
+ },
+ "bin": [
+ "bin/validate-json"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "JsonSchema\\": "src/JsonSchema/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bruno Prieto Reis",
+ "email": "bruno.p.reis@gmail.com"
+ },
+ {
+ "name": "Justin Rainbow",
+ "email": "justin.rainbow@gmail.com"
+ },
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ },
+ {
+ "name": "Robert Schönthal",
+ "email": "seroscho@googlemail.com"
+ }
+ ],
+ "description": "A library to validate a json schema.",
+ "homepage": "https://github.com/justinrainbow/json-schema",
+ "keywords": [
+ "json",
+ "schema"
+ ],
+ "support": {
+ "issues": "https://github.com/jsonrainbow/json-schema/issues",
+ "source": "https://github.com/jsonrainbow/json-schema/tree/5.3.0"
+ },
+ "time": "2024-07-06T21:00:26+00:00"
+ },
+ {
+ "name": "league/container",
+ "version": "4.2.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/container.git",
+ "reference": "7ea728b013b9a156c409c6f0fc3624071b742dec"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/container/zipball/7ea728b013b9a156c409c6f0fc3624071b742dec",
+ "reference": "7ea728b013b9a156c409c6f0fc3624071b742dec",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0",
+ "psr/container": "^1.1 || ^2.0"
+ },
+ "provide": {
+ "psr/container-implementation": "^1.0"
+ },
+ "replace": {
+ "orno/di": "~2.0"
+ },
+ "require-dev": {
+ "nette/php-generator": "^3.4",
+ "nikic/php-parser": "^4.10",
+ "phpstan/phpstan": "^0.12.47",
+ "phpunit/phpunit": "^8.5.17",
+ "roave/security-advisories": "dev-latest",
+ "scrutinizer/ocular": "^1.8",
+ "squizlabs/php_codesniffer": "^3.6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-1.x": "1.x-dev",
+ "dev-2.x": "2.x-dev",
+ "dev-3.x": "3.x-dev",
+ "dev-4.x": "4.x-dev",
+ "dev-master": "4.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Container\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Phil Bennett",
+ "email": "mail@philbennett.co.uk",
+ "role": "Developer"
+ }
+ ],
+ "description": "A fast and intuitive dependency injection container.",
+ "homepage": "https://github.com/thephpleague/container",
+ "keywords": [
+ "container",
+ "dependency",
+ "di",
+ "injection",
+ "league",
+ "provider",
+ "service"
+ ],
+ "support": {
+ "issues": "https://github.com/thephpleague/container/issues",
+ "source": "https://github.com/thephpleague/container/tree/4.2.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/philipobenito",
+ "type": "github"
+ }
+ ],
+ "time": "2024-11-10T12:42:13+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.12.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845",
+ "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/collections": "<1.6.8",
+ "doctrine/common": "<2.13.3 || >=3 <3.2.2"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.6.8",
+ "doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpspec/prophecy": "^1.10",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "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.12.1"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-11-08T17:47:46+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v5.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "447a020a1f875a434d62f2a401f53b82a396e494"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494",
+ "reference": "447a020a1f875a434d62f2a401f53b82a396e494",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "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.4.0"
+ },
+ "time": "2024-12-30T11:07:19+00:00"
+ },
+ {
+ "name": "nunomaduro/phpinsights",
+ "version": "v2.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nunomaduro/phpinsights.git",
+ "reference": "5c12a8d626712de6db5e6d2db52b1eb4e9596650"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nunomaduro/phpinsights/zipball/5c12a8d626712de6db5e6d2db52b1eb4e9596650",
+ "reference": "5c12a8d626712de6db5e6d2db52b1eb4e9596650",
+ "shasum": ""
+ },
+ "require": {
+ "cmgmyr/phploc": "^8.0.3",
+ "composer/semver": "^3.4",
+ "ext-iconv": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "ext-tokenizer": "*",
+ "friendsofphp/php-cs-fixer": "^3.40.0",
+ "justinrainbow/json-schema": "^5.2.13",
+ "league/container": "^3.2|^4.2",
+ "php": "^7.4|^8.0",
+ "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "psr/container": "^1.0|^2.0.2",
+ "psr/simple-cache": "^1.0|^2.0|^3.0",
+ "sebastian/diff": "^4.0|^5.0.3|^6.0",
+ "slevomat/coding-standard": "^8.14.1",
+ "squizlabs/php_codesniffer": "^3.7.2",
+ "symfony/cache": "^5.4|^6.0|^7.0",
+ "symfony/console": "^5.4|^6.4|^7.0",
+ "symfony/finder": "^5.4|^6.0|^7.0",
+ "symfony/http-client": "^5.4|^6.0|^7.0",
+ "symfony/process": "^5.4|^6.4|^7.0"
+ },
+ "require-dev": {
+ "ergebnis/phpstan-rules": "^0.15.3",
+ "illuminate/console": "^5.8|^6.0|^7.0|^8.0|^9.20|^10.0",
+ "illuminate/support": "^5.8|^6.0|^7.0|^8.0|^9.52.16|^10.0",
+ "mockery/mockery": "^1.6.6",
+ "phpstan/phpstan-strict-rules": "^0.12.11",
+ "phpunit/phpunit": "^8.0|^9.0|^10.4.2",
+ "rector/rector": "0.11.56",
+ "symfony/var-dumper": "^5.4|^6.0|^7.0",
+ "thecodingmachine/phpstan-strict-rules": "^0.12.2"
+ },
+ "suggest": {
+ "ext-simplexml": "It is needed for the checkstyle formatter"
+ },
+ "bin": [
+ "bin/phpinsights"
+ ],
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "NunoMaduro\\PhpInsights\\Application\\Adapters\\Laravel\\InsightsServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "NunoMaduro\\PhpInsights\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nuno Maduro",
+ "email": "enunomaduro@gmail.com"
+ }
+ ],
+ "description": "Instant PHP quality checks from your console.",
+ "keywords": [
+ "Insights",
+ "code",
+ "console",
+ "php",
+ "quality",
+ "source"
+ ],
+ "support": {
+ "issues": "https://github.com/nunomaduro/phpinsights/issues",
+ "source": "https://github.com/nunomaduro/phpinsights/tree/v2.12.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/JustSteveKing",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/cmgmyr",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nunomaduro",
+ "type": "github"
+ }
+ ],
+ "time": "2024-11-11T14:42:55+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "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": "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"
+ }
+ ],
+ "time": "2024-03-03T12:33:53+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "shasum": ""
+ },
+ "require": {
+ "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"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "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": "php-coveralls/php-coveralls",
+ "version": "v2.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-coveralls/php-coveralls.git",
+ "reference": "b36fa4394e519dafaddc04ae03976bc65a25ba15"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/b36fa4394e519dafaddc04ae03976bc65a25ba15",
+ "reference": "b36fa4394e519dafaddc04ae03976bc65a25ba15",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-simplexml": "*",
+ "guzzlehttp/guzzle": "^6.0 || ^7.0",
+ "php": "^7.0 || ^8.0",
+ "psr/log": "^1.0 || ^2.0",
+ "symfony/config": "^2.1 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0",
+ "symfony/console": "^2.1 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0",
+ "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0",
+ "symfony/yaml": "^2.0.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0 || ^7.0 || >=8.0 <8.5.29 || >=9.0 <9.5.23",
+ "sanmai/phpunit-legacy-adapter": "^6.1 || ^8.0"
+ },
+ "suggest": {
+ "symfony/http-kernel": "Allows Symfony integration"
+ },
+ "bin": [
+ "bin/php-coveralls"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PhpCoveralls\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Kitamura Satoshi",
+ "email": "with.no.parachute@gmail.com",
+ "homepage": "https://www.facebook.com/satooshi.jp",
+ "role": "Original creator"
+ },
+ {
+ "name": "Takashi Matsuo",
+ "email": "tmatsuo@google.com"
+ },
+ {
+ "name": "Google Inc"
+ },
+ {
+ "name": "Dariusz Ruminski",
+ "email": "dariusz.ruminski@gmail.com",
+ "homepage": "https://github.com/keradus"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/php-coveralls/php-coveralls/graphs/contributors"
+ }
+ ],
+ "description": "PHP client library for Coveralls API",
+ "homepage": "https://github.com/php-coveralls/php-coveralls",
+ "keywords": [
+ "ci",
+ "coverage",
+ "github",
+ "test"
+ ],
+ "support": {
+ "issues": "https://github.com/php-coveralls/php-coveralls/issues",
+ "source": "https://github.com/php-coveralls/php-coveralls/tree/v2.7.0"
+ },
+ "time": "2023-11-22T10:21:01+00:00"
+ },
+ {
+ "name": "php-parallel-lint/php-parallel-lint",
+ "version": "v1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git",
+ "reference": "6db563514f27e19595a19f45a4bf757b6401194e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/6db563514f27e19595a19f45a4bf757b6401194e",
+ "reference": "6db563514f27e19595a19f45a4bf757b6401194e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": ">=5.3.0"
+ },
+ "replace": {
+ "grogy/php-parallel-lint": "*",
+ "jakub-onderka/php-parallel-lint": "*"
+ },
+ "require-dev": {
+ "nette/tester": "^1.3 || ^2.0",
+ "php-parallel-lint/php-console-highlighter": "0.* || ^1.0",
+ "squizlabs/php_codesniffer": "^3.6"
+ },
+ "suggest": {
+ "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet"
+ },
+ "bin": [
+ "parallel-lint"
+ ],
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "./src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jakub Onderka",
+ "email": "ahoj@jakubonderka.cz"
+ }
+ ],
+ "description": "This tool checks the syntax of PHP files about 20x faster than serial check.",
+ "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint",
+ "keywords": [
+ "lint",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/issues",
+ "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v1.4.0"
+ },
+ "time": "2024-03-27T12:14:49+00:00"
+ },
+ {
+ "name": "phpstan/phpdoc-parser",
+ "version": "1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpdoc-parser.git",
+ "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140",
+ "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/annotations": "^2.0",
+ "nikic/php-parser": "^4.15",
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^1.5",
+ "phpstan/phpstan-phpunit": "^1.1",
+ "phpstan/phpstan-strict-rules": "^1.0",
+ "phpunit/phpunit": "^9.5",
+ "symfony/process": "^5.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\PhpDocParser\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPDoc parser with support for nullable, intersection and generic types",
+ "support": {
+ "issues": "https://github.com/phpstan/phpdoc-parser/issues",
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0"
+ },
+ "time": "2024-10-13T11:25:22+00:00"
+ },
+ {
+ "name": "phpstan/phpstan",
+ "version": "2.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan.git",
+ "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7",
+ "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4|^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan-shim": "*"
+ },
+ "bin": [
+ "phpstan",
+ "phpstan.phar"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "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": [
+ {
+ "url": "https://github.com/ondrejmirtes",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/phpstan",
+ "type": "github"
+ }
+ ],
+ "time": "2025-01-05T16:43:48+00:00"
+ },
+ {
+ "name": "phpstan/phpstan-phpunit",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan-phpunit.git",
+ "reference": "e32ac656788a5bf3dedda89e6a2cad5643bf1a18"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/e32ac656788a5bf3dedda89e6a2cad5643bf1a18",
+ "reference": "e32ac656788a5bf3dedda89e6a2cad5643bf1a18",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0",
+ "phpstan/phpstan": "^2.0.4"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<7.0"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpunit/phpunit": "^9.6"
+ },
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "extension.neon",
+ "rules.neon"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPUnit extensions and rules for PHPStan",
+ "support": {
+ "issues": "https://github.com/phpstan/phpstan-phpunit/issues",
+ "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.3"
+ },
+ "time": "2024-12-19T09:14:43+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "11.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "418c59fd080954f8c4aa5631d9502ecda2387118"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118",
+ "reference": "418c59fd080954f8c4aa5631d9502ecda2387118",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^5.3.1",
+ "php": ">=8.2",
+ "phpunit/php-file-iterator": "^5.1.0",
+ "phpunit/php-text-template": "^4.0.1",
+ "sebastian/code-unit-reverse-lookup": "^4.0.1",
+ "sebastian/complexity": "^4.0.1",
+ "sebastian/environment": "^7.2.0",
+ "sebastian/lines-of-code": "^3.0.1",
+ "sebastian/version": "^5.0.2",
+ "theseer/tokenizer": "^1.2.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.5.0"
+ },
+ "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-main": "11.0.x-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 provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.8"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-12-11T12:34:27+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "5.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6",
+ "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.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",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "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/5.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-08-27T05:02:59+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "5.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2",
+ "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^11.0"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "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",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:07:44+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
+ "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.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": "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/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:08:43+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "7.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
+ "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.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": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "security": "https://github.com/sebastianbergmann/php-timer/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:09:35+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "11.5.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "153d0531b9f7e883c5053160cad6dd5ac28140b3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/153d0531b9f7e883c5053160cad6dd5ac28140b3",
+ "reference": "153d0531b9f7e883c5053160cad6dd5ac28140b3",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.12.1",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
+ "php": ">=8.2",
+ "phpunit/php-code-coverage": "^11.0.8",
+ "phpunit/php-file-iterator": "^5.1.0",
+ "phpunit/php-invoker": "^5.0.1",
+ "phpunit/php-text-template": "^4.0.1",
+ "phpunit/php-timer": "^7.0.1",
+ "sebastian/cli-parser": "^3.0.2",
+ "sebastian/code-unit": "^3.0.2",
+ "sebastian/comparator": "^6.2.1",
+ "sebastian/diff": "^6.0.2",
+ "sebastian/environment": "^7.2.0",
+ "sebastian/exporter": "^6.3.0",
+ "sebastian/global-state": "^7.0.2",
+ "sebastian/object-enumerator": "^6.0.1",
+ "sebastian/type": "^5.1.0",
+ "sebastian/version": "^5.0.2",
+ "staabm/side-effects-detector": "^1.0.5"
+ },
+ "suggest": {
+ "ext-soap": "To be able to generate mocks based on WSDL files"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "11.5-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ],
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "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/11.5.2"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/sponsors.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-12-21T05:51:08+00:00"
+ },
+ {
+ "name": "psr/cache",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/cache.git",
+ "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
+ "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Cache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for caching libraries",
+ "keywords": [
+ "cache",
+ "psr",
+ "psr-6"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/cache/tree/3.0.0"
+ },
+ "time": "2021-02-03T23:26:27+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "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": "psr/event-dispatcher",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/event-dispatcher.git",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\EventDispatcher\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Standard interfaces for event handling.",
+ "keywords": [
+ "events",
+ "psr",
+ "psr-14"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/event-dispatcher/issues",
+ "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
+ },
+ "time": "2019-01-08T18:20:26+00:00"
+ },
+ {
+ "name": "psr/http-client",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-client.git",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0 || ^8.0",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP clients",
+ "homepage": "https://github.com/php-fig/http-client",
+ "keywords": [
+ "http",
+ "http-client",
+ "psr",
+ "psr-18"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-client"
+ },
+ "time": "2023-09-23T14:17:50+00:00"
+ },
+ {
+ "name": "psr/http-factory",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-factory.git",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
+ "keywords": [
+ "factory",
+ "http",
+ "message",
+ "psr",
+ "psr-17",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-factory"
+ },
+ "time": "2024-04-15T12:06:14+00:00"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+ "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-message/tree/2.0"
+ },
+ "time": "2023-04-04T09:54:51+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376",
+ "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/2.0.0"
+ },
+ "time": "2021-07-14T16:41:46+00:00"
+ },
+ {
+ "name": "psr/simple-cache",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/simple-cache.git",
+ "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865",
+ "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\SimpleCache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interfaces for simple caching",
+ "keywords": [
+ "cache",
+ "caching",
+ "psr",
+ "psr-16",
+ "simple-cache"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/simple-cache/tree/3.0.0"
+ },
+ "time": "2021-10-29T13:26:27+00:00"
+ },
+ {
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders.",
+ "support": {
+ "issues": "https://github.com/ralouphie/getallheaders/issues",
+ "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+ },
+ "time": "2019-03-08T08:55:37+00:00"
+ },
+ {
+ "name": "react/cache",
+ "version": "v1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/cache.git",
+ "reference": "d47c472b64aa5608225f47965a484b75c7817d5b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b",
+ "reference": "d47c472b64aa5608225f47965a484b75c7817d5b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "react/promise": "^3.0 || ^2.0 || ^1.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "React\\Cache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Async, Promise-based cache interface for ReactPHP",
+ "keywords": [
+ "cache",
+ "caching",
+ "promise",
+ "reactphp"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/cache/issues",
+ "source": "https://github.com/reactphp/cache/tree/v1.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2022-11-30T15:59:55+00:00"
+ },
+ {
+ "name": "react/child-process",
+ "version": "v0.6.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/child-process.git",
+ "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159",
+ "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.0",
+ "react/event-loop": "^1.2",
+ "react/stream": "^1.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "react/socket": "^1.16",
+ "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "React\\ChildProcess\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Event-driven library for executing child processes with ReactPHP.",
+ "keywords": [
+ "event-driven",
+ "process",
+ "reactphp"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/child-process/issues",
+ "source": "https://github.com/reactphp/child-process/tree/v0.6.6"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2025-01-01T16:37:48+00:00"
+ },
+ {
+ "name": "react/dns",
+ "version": "v1.13.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/dns.git",
+ "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5",
+ "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "react/cache": "^1.0 || ^0.6 || ^0.5",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.2 || ^2.7 || ^1.2.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "react/async": "^4.3 || ^3 || ^2",
+ "react/promise-timer": "^1.11"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "React\\Dns\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Async DNS resolver for ReactPHP",
+ "keywords": [
+ "async",
+ "dns",
+ "dns-resolver",
+ "reactphp"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/dns/issues",
+ "source": "https://github.com/reactphp/dns/tree/v1.13.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2024-06-13T14:18:03+00:00"
+ },
+ {
+ "name": "react/event-loop",
+ "version": "v1.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/event-loop.git",
+ "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354",
+ "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+ },
+ "suggest": {
+ "ext-pcntl": "For signal handling support when using the StreamSelectLoop"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "React\\EventLoop\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
+ "keywords": [
+ "asynchronous",
+ "event-loop"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/event-loop/issues",
+ "source": "https://github.com/reactphp/event-loop/tree/v1.5.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2023-11-13T13:48:05+00:00"
+ },
+ {
+ "name": "react/promise",
+ "version": "v3.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/promise.git",
+ "reference": "8a164643313c71354582dc850b42b33fa12a4b63"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63",
+ "reference": "8a164643313c71354582dc850b42b33fa12a4b63",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "1.10.39 || 1.4.10",
+ "phpunit/phpunit": "^9.6 || ^7.5"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions_include.php"
+ ],
+ "psr-4": {
+ "React\\Promise\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+ "keywords": [
+ "promise",
+ "promises"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/promise/issues",
+ "source": "https://github.com/reactphp/promise/tree/v3.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2024-05-24T10:39:05+00:00"
+ },
+ {
+ "name": "react/socket",
+ "version": "v1.16.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/socket.git",
+ "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1",
+ "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.0",
+ "react/dns": "^1.13",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.2 || ^2.6 || ^1.2.1",
+ "react/stream": "^1.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "react/async": "^4.3 || ^3.3 || ^2",
+ "react/promise-stream": "^1.4",
+ "react/promise-timer": "^1.11"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "React\\Socket\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
+ "keywords": [
+ "Connection",
+ "Socket",
+ "async",
+ "reactphp",
+ "stream"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/socket/issues",
+ "source": "https://github.com/reactphp/socket/tree/v1.16.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2024-07-26T10:38:09+00:00"
+ },
+ {
+ "name": "react/stream",
+ "version": "v1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/stream.git",
+ "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d",
+ "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.8",
+ "react/event-loop": "^1.2"
+ },
+ "require-dev": {
+ "clue/stream-filter": "~1.2",
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "React\\Stream\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
+ "keywords": [
+ "event-driven",
+ "io",
+ "non-blocking",
+ "pipe",
+ "reactphp",
+ "readable",
+ "stream",
+ "writable"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/stream/issues",
+ "source": "https://github.com/reactphp/stream/tree/v1.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2024-06-11T12:45:25+00:00"
+ },
+ {
+ "name": "rector/rector",
+ "version": "2.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/rectorphp/rector.git",
+ "reference": "fa0cb009dc3df084bf549032ae4080a0481a2036"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/rectorphp/rector/zipball/fa0cb009dc3df084bf549032ae4080a0481a2036",
+ "reference": "fa0cb009dc3df084bf549032ae4080a0481a2036",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4|^8.0",
+ "phpstan/phpstan": "^2.1.1"
+ },
+ "conflict": {
+ "rector/rector-doctrine": "*",
+ "rector/rector-downgrade-php": "*",
+ "rector/rector-phpunit": "*",
+ "rector/rector-symfony": "*"
+ },
+ "suggest": {
+ "ext-dom": "To manipulate phpunit.xml via the custom-rule command"
+ },
+ "bin": [
+ "bin/rector"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Instant Upgrade and Automated Refactoring of any PHP code",
+ "keywords": [
+ "automation",
+ "dev",
+ "migration",
+ "refactoring"
+ ],
+ "support": {
+ "issues": "https://github.com/rectorphp/rector/issues",
+ "source": "https://github.com/rectorphp/rector/tree/2.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/tomasvotruba",
+ "type": "github"
+ }
+ ],
+ "time": "2025-01-06T10:38:36+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180",
+ "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.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",
+ "role": "lead"
+ }
+ ],
+ "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/3.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:41:36+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca",
+ "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.5"
+ },
+ "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",
+ "role": "lead"
+ }
+ ],
+ "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",
+ "security": "https://github.com/sebastianbergmann/code-unit/security/policy",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-12-12T09:59:06+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "183a9b2632194febd219bb9246eee421dad8d45e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e",
+ "reference": "183a9b2632194febd219bb9246eee421dad8d45e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.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"
+ }
+ ],
+ "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",
+ "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:45:54+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "6.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/d4e47a769525c4dd38cea90e5dcd435ddbbc7115",
+ "reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "php": ">=8.2",
+ "sebastian/diff": "^6.0",
+ "sebastian/exporter": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.4"
+ },
+ "suggest": {
+ "ext-bcmath": "For comparing BcMath\\Number objects"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.2-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": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "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/6.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-01-06T10:28:19+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "ee41d384ab1906c68852636b6de493846e13e5a0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0",
+ "reference": "ee41d384ab1906c68852636b6de493846e13e5a0",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.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": "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/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:49:50+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "6.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544",
+ "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "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/6.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:53:05+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5",
+ "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "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/7.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:54:44+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "6.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3",
+ "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=8.2",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.1-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": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "security": "https://github.com/sebastianbergmann/exporter/security/policy",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-12-05T09:17:50+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "7.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "3be331570a721f9a4b5917f4209773de17f747d7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7",
+ "reference": "3be331570a721f9a4b5917f4209773de17f747d7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "sebastian/object-reflector": "^4.0",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "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/7.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:57:36+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a",
+ "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.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",
+ "role": "lead"
+ }
+ ],
+ "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/3.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:58:38+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "6.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "f5b498e631a74204185071eb41f33f38d64608aa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa",
+ "reference": "f5b498e631a74204185071eb41f33f38d64608aa",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "sebastian/object-reflector": "^4.0",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.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",
+ "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:00:13+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9",
+ "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.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"
+ }
+ ],
+ "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",
+ "security": "https://github.com/sebastianbergmann/object-reflector/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:01:32+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "6.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "694d156164372abbd149a4b85ccda2e4670c0e16"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16",
+ "reference": "694d156164372abbd149a4b85ccda2e4670c0e16",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.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/6.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:10:34+00:00"
+ },
+ {
+ "name": "sebastian/type",
+ "version": "5.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac",
+ "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.3"
+ },
+ "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",
+ "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",
+ "security": "https://github.com/sebastianbergmann/type/security/policy",
+ "source": "https://github.com/sebastianbergmann/type/tree/5.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-09-17T13:12:04+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "5.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874",
+ "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "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",
+ "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",
+ "security": "https://github.com/sebastianbergmann/version/security/policy",
+ "source": "https://github.com/sebastianbergmann/version/tree/5.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-10-09T05:16:32+00:00"
+ },
+ {
+ "name": "slevomat/coding-standard",
+ "version": "8.15.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/slevomat/coding-standard.git",
+ "reference": "7d1d957421618a3803b593ec31ace470177d7817"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/7d1d957421618a3803b593ec31ace470177d7817",
+ "reference": "7d1d957421618a3803b593ec31ace470177d7817",
+ "shasum": ""
+ },
+ "require": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0",
+ "php": "^7.2 || ^8.0",
+ "phpstan/phpdoc-parser": "^1.23.1",
+ "squizlabs/php_codesniffer": "^3.9.0"
+ },
+ "require-dev": {
+ "phing/phing": "2.17.4",
+ "php-parallel-lint/php-parallel-lint": "1.3.2",
+ "phpstan/phpstan": "1.10.60",
+ "phpstan/phpstan-deprecation-rules": "1.1.4",
+ "phpstan/phpstan-phpunit": "1.3.16",
+ "phpstan/phpstan-strict-rules": "1.5.2",
+ "phpunit/phpunit": "8.5.21|9.6.8|10.5.11"
+ },
+ "type": "phpcodesniffer-standard",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "8.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "SlevomatCodingStandard\\": "SlevomatCodingStandard/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.",
+ "keywords": [
+ "dev",
+ "phpcs"
+ ],
+ "support": {
+ "issues": "https://github.com/slevomat/coding-standard/issues",
+ "source": "https://github.com/slevomat/coding-standard/tree/8.15.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/kukulich",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-03-09T15:20:58+00:00"
+ },
+ {
+ "name": "squizlabs/php_codesniffer",
+ "version": "3.11.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
+ "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1368f4a58c3c52114b86b1abe8f4098869cb0079",
+ "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079",
+ "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",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "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"
+ }
+ ],
+ "time": "2024-12-11T16:04:26+00:00"
+ },
+ {
+ "name": "staabm/side-effects-detector",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/staabm/side-effects-detector.git",
+ "reference": "d8334211a140ce329c13726d4a715adbddd0a163"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163",
+ "reference": "d8334211a140ce329c13726d4a715adbddd0a163",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^1.12.6",
+ "phpunit/phpunit": "^9.6.21",
+ "symfony/var-dumper": "^5.4.43",
+ "tomasvotruba/type-coverage": "1.0.0",
+ "tomasvotruba/unused-public": "1.0.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "lib/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A static analysis tool to detect side effects in PHP code",
+ "keywords": [
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/staabm/side-effects-detector/issues",
+ "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/staabm",
+ "type": "github"
+ }
+ ],
+ "time": "2024-10-20T05:08:20+00:00"
+ },
+ {
+ "name": "symfony/cache",
+ "version": "v7.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/cache.git",
+ "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/cache/zipball/e7e983596b744c4539f31e79b0350a6cf5878a20",
+ "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "psr/cache": "^2.0|^3.0",
+ "psr/log": "^1.1|^2|^3",
+ "symfony/cache-contracts": "^2.5|^3",
+ "symfony/deprecation-contracts": "^2.5|^3.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/var-exporter": "^6.4|^7.0"
+ },
+ "conflict": {
+ "doctrine/dbal": "<3.6",
+ "symfony/dependency-injection": "<6.4",
+ "symfony/http-kernel": "<6.4",
+ "symfony/var-dumper": "<6.4"
+ },
+ "provide": {
+ "psr/cache-implementation": "2.0|3.0",
+ "psr/simple-cache-implementation": "1.0|2.0|3.0",
+ "symfony/cache-implementation": "1.1|2.0|3.0"
+ },
+ "require-dev": {
+ "cache/integration-tests": "dev-master",
+ "doctrine/dbal": "^3.6|^4",
+ "predis/predis": "^1.1|^2.0",
+ "psr/simple-cache": "^1.0|^2.0|^3.0",
+ "symfony/clock": "^6.4|^7.0",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/filesystem": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/var-dumper": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Cache\\": ""
+ },
+ "classmap": [
+ "Traits/ValueWrapper.php"
+ ],
+ "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": "Provides extended PSR-6, PSR-16 (and tags) implementations",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "caching",
+ "psr6"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/cache/tree/v7.2.1"
+ },
+ "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-12-07T08:08:50+00:00"
+ },
+ {
+ "name": "symfony/cache-contracts",
+ "version": "v3.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/cache-contracts.git",
+ "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b",
+ "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/cache": "^3.0"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Cache\\": ""
+ }
+ },
+ "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 caching",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1"
+ },
+ "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:20:29+00:00"
+ },
+ {
+ "name": "symfony/config",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/config.git",
+ "reference": "bcd3c4adf0144dee5011bb35454728c38adec055"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/config/zipball/bcd3c4adf0144dee5011bb35454728c38adec055",
+ "reference": "bcd3c4adf0144dee5011bb35454728c38adec055",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/filesystem": "^7.1",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "conflict": {
+ "symfony/finder": "<6.4",
+ "symfony/service-contracts": "<2.5"
+ },
+ "require-dev": {
+ "symfony/event-dispatcher": "^6.4|^7.0",
+ "symfony/finder": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/yaml": "^6.4|^7.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.2.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-11-04T11:36:24+00:00"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v7.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
+ "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/string": "^6.4|^7.0"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<6.4",
+ "symfony/dotenv": "<6.4",
+ "symfony/event-dispatcher": "<6.4",
+ "symfony/lock": "<6.4",
+ "symfony/process": "<6.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0|2.0|3.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/event-dispatcher": "^6.4|^7.0",
+ "symfony/http-foundation": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/lock": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/stopwatch": "^6.4|^7.0",
+ "symfony/var-dumper": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "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": "Eases the creation of beautiful and testable command line interfaces",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "cli",
+ "command-line",
+ "console",
+ "terminal"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/console/tree/v7.2.1"
+ },
+ "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-12-11T03:49:26+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
+ "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.5-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.5.1"
+ },
+ "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:20:29+00:00"
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher.git",
+ "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1",
+ "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/event-dispatcher-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<6.4",
+ "symfony/service-contracts": "<2.5"
+ },
+ "provide": {
+ "psr/event-dispatcher-implementation": "1.0",
+ "symfony/event-dispatcher-implementation": "2.0|3.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/error-handler": "^6.4|^7.0",
+ "symfony/expression-language": "^6.4|^7.0",
+ "symfony/http-foundation": "^6.4|^7.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/stopwatch": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ },
+ "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 tools that allow your application components to communicate with each other by dispatching events and listening to them",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/event-dispatcher/tree/v7.2.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/event-dispatcher-contracts",
+ "version": "v3.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+ "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f",
+ "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/event-dispatcher": "^1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\EventDispatcher\\": ""
+ }
+ },
+ "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 dispatching event",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1"
+ },
+ "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:20:29+00:00"
+ },
+ {
+ "name": "symfony/filesystem",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
+ "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.8"
+ },
+ "require-dev": {
+ "symfony/process": "^6.4|^7.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.2.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-10-25T15:15:23+00:00"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v7.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "87a71856f2f56e4100373e92529eed3171695cfb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb",
+ "reference": "87a71856f2f56e4100373e92529eed3171695cfb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "symfony/filesystem": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "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": "Finds files and directories via an intuitive fluent interface",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/finder/tree/v7.2.2"
+ },
+ "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-12-30T19:00:17+00:00"
+ },
+ {
+ "name": "symfony/http-client",
+ "version": "v7.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-client.git",
+ "reference": "339ba21476eb184290361542f732ad12c97591ec"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/339ba21476eb184290361542f732ad12c97591ec",
+ "reference": "339ba21476eb184290361542f732ad12c97591ec",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "psr/log": "^1|^2|^3",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/http-client-contracts": "~3.4.4|^3.5.2",
+ "symfony/service-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "amphp/amp": "<2.5",
+ "php-http/discovery": "<1.15",
+ "symfony/http-foundation": "<6.4"
+ },
+ "provide": {
+ "php-http/async-client-implementation": "*",
+ "php-http/client-implementation": "*",
+ "psr/http-client-implementation": "1.0",
+ "symfony/http-client-implementation": "3.0"
+ },
+ "require-dev": {
+ "amphp/http-client": "^4.2.1|^5.0",
+ "amphp/http-tunnel": "^1.0|^2.0",
+ "amphp/socket": "^1.1",
+ "guzzlehttp/promises": "^1.4|^2.0",
+ "nyholm/psr7": "^1.0",
+ "php-http/httplug": "^1.0|^2.0",
+ "psr/http-client": "^1.0",
+ "symfony/amphp-http-client-meta": "^1.0|^2.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/rate-limiter": "^6.4|^7.0",
+ "symfony/stopwatch": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpClient\\": ""
+ },
+ "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": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "http"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/http-client/tree/v7.2.2"
+ },
+ "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-12-30T18:35:15+00:00"
+ },
+ {
+ "name": "symfony/http-client-contracts",
+ "version": "v3.5.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-client-contracts.git",
+ "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645",
+ "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\HttpClient\\": ""
+ },
+ "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 HTTP clients",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2"
+ },
+ "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-12-07T08:49:48+00:00"
+ },
+ {
+ "name": "symfony/options-resolver",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/options-resolver.git",
+ "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50",
+ "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\OptionsResolver\\": ""
+ },
+ "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 an improved replacement for the array_replace PHP function",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "config",
+ "configuration",
+ "options"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/options-resolver/tree/v7.2.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-11-20T11:17:29+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "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.31.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-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-grapheme",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+ "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
+ "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's grapheme_* functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "grapheme",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.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-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's Normalizer class and related functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "intl",
+ "normalizer",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.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-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
+ "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.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-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php81",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php81.git",
+ "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
+ "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php81\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.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-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
+ "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "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": "Executes commands in sub-processes",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/process/tree/v7.2.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-11-06T14:24:19+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v3.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
+ "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
+ "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.5-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.5.1"
+ },
+ "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:20:29+00:00"
+ },
+ {
+ "name": "symfony/stopwatch",
+ "version": "v7.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/stopwatch.git",
+ "reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/stopwatch/zipball/e46690d5b9d7164a6d061cab1e8d46141b9f49df",
+ "reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/service-contracts": "^2.5|^3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Stopwatch\\": ""
+ },
+ "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 a way to profile code",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/stopwatch/tree/v7.2.2"
+ },
+ "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-12-18T14:28:33+00:00"
+ },
+ {
+ "name": "symfony/string",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/string.git",
+ "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82",
+ "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-intl-grapheme": "~1.0",
+ "symfony/polyfill-intl-normalizer": "~1.0",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "symfony/translation-contracts": "<2.5"
+ },
+ "require-dev": {
+ "symfony/emoji": "^7.1",
+ "symfony/error-handler": "^6.4|^7.0",
+ "symfony/http-client": "^6.4|^7.0",
+ "symfony/intl": "^6.4|^7.0",
+ "symfony/translation-contracts": "^2.5|^3.0",
+ "symfony/var-exporter": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\String\\": ""
+ },
+ "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": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "grapheme",
+ "i18n",
+ "string",
+ "unicode",
+ "utf-8",
+ "utf8"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/string/tree/v7.2.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-11-13T13:31:26+00:00"
+ },
+ {
+ "name": "symfony/var-exporter",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-exporter.git",
+ "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1a6a89f95a46af0f142874c9d650a6358d13070d",
+ "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "symfony/property-access": "^6.4|^7.0",
+ "symfony/serializer": "^6.4|^7.0",
+ "symfony/var-dumper": "^6.4|^7.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.2.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-10-18T07:58:17+00:00"
+ },
+ {
+ "name": "symfony/yaml",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/yaml.git",
+ "reference": "099581e99f557e9f16b43c5916c26380b54abb22"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/099581e99f557e9f16b43c5916c26380b54abb22",
+ "reference": "099581e99f557e9f16b43c5916c26380b54abb22",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3.0",
+ "symfony/polyfill-ctype": "^1.8"
+ },
+ "conflict": {
+ "symfony/console": "<6.4"
+ },
+ "require-dev": {
+ "symfony/console": "^6.4|^7.0"
+ },
+ "bin": [
+ "Resources/bin/yaml-lint"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Yaml\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Loads and dumps YAML files",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/yaml/tree/v7.2.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-10-23T06:56:12+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "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.2.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:36:25+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {},
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=8.1",
+ "ext-json": "*"
+ },
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
+}
diff --git a/phpcs.xml b/phpcs.xml
index 6a2b2d3..155e9ac 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -4,6 +4,9 @@
+ src
+ test
+
diff --git a/phpinsights.php b/phpinsights.php
new file mode 100644
index 0000000..7fc0895
--- /dev/null
+++ b/phpinsights.php
@@ -0,0 +1,140 @@
+ 'default',
+
+ /*
+ |--------------------------------------------------------------------------
+ | IDE
+ |--------------------------------------------------------------------------
+ |
+ | This options allow to add hyperlinks in your terminal to quickly open
+ | files in your favorite IDE while browsing your PhpInsights report.
+ |
+ | Supported: "textmate", "macvim", "emacs", "sublime", "phpstorm",
+ | "atom", "vscode".
+ |
+ | If you have another IDE that is not in this list but which provide an
+ | url-handler, you could fill this config with a pattern like this:
+ |
+ | myide://open?url=file://%f&line=%l
+ |
+ */
+
+ 'ide' => null,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Configuration
+ |--------------------------------------------------------------------------
+ |
+ | Here you may adjust all the various `Insights` that will be used by PHP
+ | Insights. You can either add, remove or configure `Insights`. Keep in
+ | mind, that all added `Insights` must belong to a specific `Metric`.
+ |
+ */
+
+ 'exclude' => [
+ 'data/',
+ 'data-scripts/',
+ ],
+
+ 'add' => [
+ ],
+
+ 'remove' => [
+ ForbiddenSetterSniff::class,
+ DisallowMixedTypeHintSniff::class,
+ LineLengthSniff::class,
+ FunctionLengthSniff::class,
+ SuperfluousAbstractClassNamingSniff::class,
+ SuperfluousExceptionNamingSniff::class,
+ SuperfluousInterfaceNamingSniff::class,
+ ForbiddenNormalClasses::class,
+ CyclomaticComplexityIsHigh::class,
+ UnusedParameterSniff::class,
+ TodoSniff::class,
+ ForbiddenPublicPropertySniff::class,
+ RequireOnlyStandaloneIncrementAndDecrementOperatorsSniff::class,
+ ],
+
+ 'config' => [
+ // ExampleInsight::class => [
+ // 'key' => 'value',
+ // ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Requirements
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define a level you want to reach per `Insights` category.
+ | When a score is lower than the minimum level defined, then an error
+ | code will be returned. This is optional and individually defined.
+ |
+ */
+
+ 'requirements' => [
+ 'min-quality' => 100,
+ 'min-complexity' => 0,
+ 'min-architecture' => 100,
+ 'min-style' => 100,
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Threads
+ |--------------------------------------------------------------------------
+ |
+ | Here you may adjust how many threads (core) PHPInsights can use to perform
+ | the analysis. This is optional, don't provide it and the tool will guess
+ | the max core number available. It accepts null value or integer > 0.
+ |
+ */
+
+ 'threads' => null,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Timeout
+ |--------------------------------------------------------------------------
+ | Here you may adjust the timeout (in seconds) for PHPInsights to run before
+ | a ProcessTimedOutException is thrown.
+ | This accepts an int > 0. Default is 60 seconds, which is the default value
+ | of Symfony's setTimeout function.
+ |
+ */
+
+ 'timeout' => 60,
+];
diff --git a/phpstan.neon b/phpstan.neon
index ac41719..c3457b6 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,5 +1,5 @@
parameters:
- level: 0
- paths:
- - src
- - test
+ level: 8
+ paths:
+ - src
+ - test
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index ca52d2e..3d0d9ec 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,29 +1,22 @@
-
+
+
+ ./src/
+
+
+
-
- test/
-
+
+ test/
+
+ test/Matchers/AbstractMatchTest.php
+
+
-
-
- src
-
-
-
-
-
-
-
-
-
diff --git a/rector.php b/rector.php
new file mode 100644
index 0000000..17e138d
--- /dev/null
+++ b/rector.php
@@ -0,0 +1,28 @@
+withPaths([
+ __DIR__ . '/src',
+ __DIR__ . '/test',
+ ])
+ // uncomment to reach your current PHP version
+ ->withPhpSets()
+ ->withSets(
+ [
+ PHPUnitSetList::ANNOTATIONS_TO_ATTRIBUTES,
+ PHPUnitSetList::PHPUNIT_80,
+ PHPUnitSetList::PHPUNIT_90,
+ PHPUnitSetList::PHPUNIT_100,
+ PHPUnitSetList::PHPUNIT_110,
+ PHPUnitSetList::PHPUNIT_CODE_QUALITY,
+ ]
+ )
+ ->withRules([
+ AddVoidReturnTypeWhereNoReturnRector::class,
+ ]);
diff --git a/src/Feedback.php b/src/Feedback.php
index 2982e3d..9aa5ba5 100644
--- a/src/Feedback.php
+++ b/src/Feedback.php
@@ -4,7 +4,7 @@
namespace ZxcvbnPhp;
-use ZxcvbnPhp\Matchers\MatchInterface;
+use ZxcvbnPhp\Matchers\BaseMatch;
/**
* Feedback - gives some user guidance based on the strength
@@ -15,19 +15,19 @@
class Feedback
{
/**
- * @param int $score
- * @param MatchInterface[] $sequence
- * @return array
+ * @param array $sequence
+ *
+ * @return array
*/
public function getFeedback(int $score, array $sequence): array
{
// starting feedback
if (count($sequence) === 0) {
return [
- 'warning' => '',
+ 'warning' => '',
'suggestions' => [
- "Use a few words, avoid common phrases",
- "No need for symbols, digits, or uppercase letters",
+ 'Use a few words, avoid common phrases',
+ 'No need for symbols, digits, or uppercase letters',
],
];
}
@@ -35,7 +35,7 @@ public function getFeedback(int $score, array $sequence): array
// no feedback if score is good or great.
if ($score > 2) {
return [
- 'warning' => '',
+ 'warning' => '',
'suggestions' => [],
];
}
diff --git a/src/Matcher.php b/src/Matcher.php
index c843ab6..2e1445c 100644
--- a/src/Matcher.php
+++ b/src/Matcher.php
@@ -20,42 +20,54 @@ class Matcher
Matchers\YearMatch::class,
];
- private $additionalMatchers = [];
+ /**
+ * @var array
+ */
+ private array $additionalMatchers = [];
/**
* Get matches for a password.
*
* @param string $password Password string to match
- * @param array $userInputs Array of values related to the user (optional)
+ * @param array $userInputs Array of values related to the user (optional)
+ *
* @code array('Alice Smith')
+ *
* @endcode
*
- * @return MatchInterface[] Array of Match objects.
+ * @return array Array of Match objects.
*
* @see zxcvbn/src/matching.coffee::omnimatch
*/
public function getMatches(string $password, array $userInputs = []): array
{
$matches = [];
+ /** @var MatchInterface $matcher */
foreach ($this->getMatchers() as $matcher) {
$matched = $matcher::match($password, $userInputs);
- if (is_array($matched) && !empty($matched)) {
+ if ($matched !== []) {
$matches[] = $matched;
}
}
$matches = array_merge([], ...$matches);
- self::usortStable($matches, [$this, 'compareMatches']);
+ self::usortStable($matches, $this->compareMatches(...));
return $matches;
}
+ /**
+ * @param class-string $className
+ *
+ * @throws \InvalidArgumentException
+ */
public function addMatcher(string $className): self
{
- if (!is_a($className, MatchInterface::class, true)) {
- throw new \InvalidArgumentException(sprintf('Matcher class must implement %s', MatchInterface::class));
+ if (! is_a($className, BaseMatch::class, true)) {
+ throw new \InvalidArgumentException(sprintf('Matcher class must extend %s', BaseMatch::class));
}
+ // @phpstan-ignore-next-line
$this->additionalMatchers[$className] = $className;
return $this;
@@ -71,9 +83,7 @@ public function addMatcher(string $className): self
* This function taken from https://github.com/vanderlee/PHP-stable-sort-functions
* Copyright © 2015-2018 Martijn van der Lee (http://martijn.vanderlee.com). MIT License applies.
*
- * @param array $array
- * @param callable $value_compare_func
- * @return bool
+ * @param array $array
*/
public static function usortStable(array &$array, callable $value_compare_func): bool
{
@@ -81,9 +91,9 @@ public static function usortStable(array &$array, callable $value_compare_func):
foreach ($array as &$item) {
$item = [$index++, $item];
}
- $result = usort($array, function ($a, $b) use ($value_compare_func) {
+ $result = usort($array, static function ($a, $b) use ($value_compare_func) {
$result = $value_compare_func($a[1], $b[1]);
- return $result == 0 ? $a[0] - $b[0] : $result;
+ return $result === 0 ? $a[0] - $b[0] : $result;
});
foreach ($array as &$item) {
$item = $item[1];
@@ -103,13 +113,17 @@ public static function compareMatches(BaseMatch $a, BaseMatch $b): int
/**
* Load available Match objects to match against a password.
*
- * @return array Array of classes implementing MatchInterface
+ * @return array Array of classes extending BaseMatch
*/
protected function getMatchers(): array
{
+ /** @var array $additionalMatchers */
+ $additionalMatchers = array_values($this->additionalMatchers);
+
+ // @phpstan-ignore-next-line
return array_merge(
self::DEFAULT_MATCHERS,
- array_values($this->additionalMatchers)
+ $additionalMatchers
);
}
}
diff --git a/src/Matchers/BaseMatch.php b/src/Matchers/BaseMatch.php
index 97f77d3..3710f89 100644
--- a/src/Matchers/BaseMatch.php
+++ b/src/Matchers/BaseMatch.php
@@ -9,37 +9,10 @@
abstract class BaseMatch implements MatchInterface
{
- /**
- * @var
- */
- public $password;
-
- /**
- * @var
- */
- public $begin;
+ public string $pattern = '';
- /**
- * @var
- */
- public $end;
-
- /**
- * @var
- */
- public $token;
-
- /**
- * @var
- */
- public $pattern;
-
- public function __construct(string $password, int $begin, int $end, string $token)
+ public function __construct(public string $password, public int $begin, public int $end, public string $token)
{
- $this->password = $password;
- $this->begin = $begin;
- $this->end = $end;
- $this->token = $token;
}
/**
@@ -47,7 +20,8 @@ public function __construct(string $password, int $begin, int $end, string $toke
*
* @param bool $isSoleMatch
* Whether this is the only match in the password
- * @return array{'warning': string, "suggestions": string[]}
+ *
+ * @return array{'warning': string, "suggestions": array}
*/
abstract public function getFeedback(bool $isSoleMatch): array;
@@ -58,8 +32,8 @@ abstract public function getFeedback(bool $isSoleMatch): array;
* String to search.
* @param string $regex
* Regular expression with captures.
- * @param int $offset
- * @return array
+ *
+ * @return array
* Array of capture groups. Captures in a group have named indexes: 'begin', 'end', 'token'.
* e.g. fishfish /(fish)/
* array(
@@ -82,7 +56,7 @@ public static function findAll(string $string, string $regex, int $offset = 0):
$byteOffset = strlen($charsBeforeOffset);
$count = preg_match_all($regex, $string, $matches, PREG_SET_ORDER, $byteOffset);
- if (!$count) {
+ if (! $count) {
return [];
}
@@ -90,16 +64,23 @@ public static function findAll(string $string, string $regex, int $offset = 0):
foreach ($matches as $group) {
$captureBegin = 0;
$match = array_shift($group);
- $matchBegin = mb_strpos($string, $match, $offset);
+ $matchBegin = mb_strpos($string, (string) $match, $offset);
$captures = [
[
'begin' => $matchBegin,
- 'end' => $matchBegin + mb_strlen($match) - 1,
+ 'end' => $matchBegin + mb_strlen((string) $match) - 1,
'token' => $match,
],
];
foreach ($group as $capture) {
- $captureBegin = mb_strpos($match, $capture, $captureBegin);
+ $captureBeginTemp = mb_strpos((string) $match, $capture, $captureBegin);
+
+ if ($captureBeginTemp === false) {
+ continue;
+ }
+
+ $captureBegin = $captureBeginTemp;
+
$captures[] = [
'begin' => $matchBegin + $captureBegin,
'end' => $matchBegin + $captureBegin + mb_strlen($capture) - 1,
@@ -107,7 +88,7 @@ public static function findAll(string $string, string $regex, int $offset = 0):
];
}
$groups[] = $captures;
- $offset += mb_strlen($match) - 1;
+ $offset += mb_strlen((string) $match) - 1;
}
return $groups;
}
@@ -115,9 +96,6 @@ public static function findAll(string $string, string $regex, int $offset = 0):
/**
* Calculate binomial coefficient (n choose k).
*
- * @param int $n
- * @param int $k
- * @return float
* @deprecated Use {@see Binomial::binom()} instead
*/
public static function binom(int $n, int $k): float
@@ -125,27 +103,26 @@ public static function binom(int $n, int $k): float
return Binomial::binom($n, $k);
}
- abstract protected function getRawGuesses(): float;
-
public function getGuesses(): float
{
return max($this->getRawGuesses(), $this->getMinimumGuesses());
}
+ public function getGuessesLog10(): float
+ {
+ return log10($this->getGuesses());
+ }
+
+ abstract protected function getRawGuesses(): float;
+
protected function getMinimumGuesses(): float
{
- if (mb_strlen($this->token) < mb_strlen($this->password)) {
- if (mb_strlen($this->token) === 1) {
+ if (mb_strlen((string) $this->token) < mb_strlen((string) $this->password)) {
+ if (mb_strlen((string) $this->token) === 1) {
return Scorer::MIN_SUBMATCH_GUESSES_SINGLE_CHAR;
- } else {
- return Scorer::MIN_SUBMATCH_GUESSES_MULTI_CHAR;
}
+ return Scorer::MIN_SUBMATCH_GUESSES_MULTI_CHAR;
}
return 0;
}
-
- public function getGuessesLog10(): float
- {
- return log10($this->getGuesses());
- }
}
diff --git a/src/Matchers/Bruteforce.php b/src/Matchers/Bruteforce.php
index 3e08223..c2535bf 100644
--- a/src/Matchers/Bruteforce.php
+++ b/src/Matchers/Bruteforce.php
@@ -10,12 +10,12 @@ final class Bruteforce extends BaseMatch
{
public const BRUTEFORCE_CARDINALITY = 10;
- public $pattern = 'bruteforce';
+ public string $pattern = 'bruteforce';
/**
- * @param string $password
- * @param array $userInputs
- * @return Bruteforce[]
+ * @param array $userInputs
+ *
+ * @return array
*/
public static function match(string $password, array $userInputs = []): array
{
@@ -24,29 +24,28 @@ public static function match(string $password, array $userInputs = []): array
return [$match];
}
-
/**
- * @return array{'warning': string, "suggestions": string[]}
+ * @return array{'warning': string, "suggestions": array}
*/
public function getFeedback(bool $isSoleMatch): array
{
return [
- 'warning' => "",
+ 'warning' => '',
'suggestions' => [
- ]
+ ],
];
}
public function getRawGuesses(): float
{
- $guesses = pow(self::BRUTEFORCE_CARDINALITY, mb_strlen($this->token));
- if ($guesses === INF) {
+ $guesses = self::BRUTEFORCE_CARDINALITY ** mb_strlen((string) $this->token);
+ if ($guesses >= PHP_FLOAT_MAX) {
return PHP_FLOAT_MAX;
}
// small detail: make bruteforce matches at minimum one guess bigger than smallest allowed
// submatch guesses, such that non-bruteforce submatches over the same [i..j] take precedence.
- if (mb_strlen($this->token) === 1) {
+ if (mb_strlen((string) $this->token) === 1) {
$minGuesses = Scorer::MIN_SUBMATCH_GUESSES_SINGLE_CHAR + 1;
} else {
$minGuesses = Scorer::MIN_SUBMATCH_GUESSES_MULTI_CHAR + 1;
diff --git a/src/Matchers/DateMatch.php b/src/Matchers/DateMatch.php
index 6db9cf9..ebddfe1 100644
--- a/src/Matchers/DateMatch.php
+++ b/src/Matchers/DateMatch.php
@@ -18,16 +18,42 @@ class DateMatch extends BaseMatch
public const MIN_YEAR_SPACE = 20;
- public $pattern = 'date';
+ protected const DATE_NO_SEPARATOR = '/^\d{4,8}$/u';
+
+ /**
+ * (\d{1,4}) # day, month, year
+ * ([\s\/\\\\_.-]) # separator
+ * (\d{1,2}) # day, month
+ * \2 # same separator
+ * (\d{1,4}) # day, month, year
+ */
+ protected const DATE_WITH_SEPARATOR = '/^(\d{1,4})([\s\/\\\\_.-])(\d{1,2})\2(\d{1,4})$/u';
+
+ public string $pattern = 'date';
+
+ /** @var int The day portion of the date in the token. */
+ public int $day;
+
+ /** @var int The month portion of the date in the token. */
+ public int $month;
+
+ /** @var int The year portion of the date in the token. */
+ public int $year;
- private static $DATE_SPLITS = [
+ /** @var string The separator used for the date in the token. */
+ public string $separator;
+
+ /**
+ * @var array
+ */
+ private static array $DATE_SPLITS = [
4 => [ # For length-4 strings, eg 1191 or 9111, two ways to split:
[1, 2], # 1 1 91 (2nd split starts at index 1, 3rd at index 2)
[2, 3], # 91 1 1
],
5 => [
[1, 3], # 1 11 91
- [2, 3] # 11 1 91
+ [2, 3], # 11 1 91
],
6 => [
[1, 2], # 1 1 1991
@@ -46,35 +72,24 @@ class DateMatch extends BaseMatch
],
];
- protected const DATE_NO_SEPARATOR = '/^\d{4,8}$/u';
-
/**
- * (\d{1,4}) # day, month, year
- * ([\s\/\\\\_.-]) # separator
- * (\d{1,2}) # day, month
- * \2 # same separator
- * (\d{1,4}) # day, month, year
+ * @param array{'day': int, 'month': int, 'year': int, 'separator': string} $params
*/
- protected const DATE_WITH_SEPARATOR = '/^(\d{1,4})([\s\/\\\\_.-])(\d{1,2})\2(\d{1,4})$/u';
-
- /** @var int The day portion of the date in the token. */
- public $day;
-
- /** @var int The month portion of the date in the token. */
- public $month;
-
- /** @var int The year portion of the date in the token. */
- public $year;
-
- /** @var string The separator used for the date in the token. */
- public $separator;
+ public function __construct(string $password, int $begin, int $end, string $token, array $params)
+ {
+ parent::__construct($password, $begin, $end, $token);
+ $this->day = $params['day'];
+ $this->month = $params['month'];
+ $this->year = $params['year'];
+ $this->separator = $params['separator'];
+ }
/**
- * Match occurences of dates in a password
+ * Match occurrences of dates in a password
*
- * @param string $password
- * @param array $userInputs
- * @return DateMatch[]
+ * @param array $userInputs
+ *
+ * @return array
*/
public static function match(string $password, array $userInputs = []): array
{
@@ -104,45 +119,32 @@ public static function match(string $password, array $userInputs = []): array
foreach ($dates as $date) {
$matches[] = new static($password, $date['begin'], $date['end'], $date['token'], $date);
}
- Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
+ Matcher::usortStable($matches, Matcher::compareMatches(...));
return $matches;
}
/**
- * @return array{'warning': string, "suggestions": string[]}
+ * @return array{'warning': string, "suggestions": array}
*/
public function getFeedback(bool $isSoleMatch): array
{
return [
- 'warning' => "Dates are often easy to guess",
+ 'warning' => 'Dates are often easy to guess',
'suggestions' => [
- 'Avoid dates and years that are associated with you'
- ]
+ 'Avoid dates and years that are associated with you',
+ ],
];
}
- /**
- * @param string $password
- * @param int $begin
- * @param int $end
- * @param string $token
- * @param array $params An array with keys: [day, month, year, separator].
- */
- public function __construct(string $password, int $begin, int $end, string $token, array $params)
+ public static function getReferenceYear(): int
{
- parent::__construct($password, $begin, $end, $token);
- $this->day = $params['day'];
- $this->month = $params['month'];
- $this->year = $params['year'];
- $this->separator = $params['separator'];
+ return (int) date('Y');
}
/**
* Find dates with separators in a password.
*
- * @param string $password
- *
- * @return array
+ * @return array
*/
protected static function datesWithSeparators(string $password): array
{
@@ -154,14 +156,14 @@ protected static function datesWithSeparators(string $password): array
for ($end = $begin + 5; $end - $begin < 10 && $end < $length; $end++) {
$token = mb_substr($password, $begin, $end - $begin + 1);
- if (!preg_match(static::DATE_WITH_SEPARATOR, $token, $captures)) {
+ if (! preg_match(self::DATE_WITH_SEPARATOR, $token, $captures)) {
continue;
}
$date = static::checkDate([
(int) $captures[1],
(int) $captures[3],
- (int) $captures[4]
+ (int) $captures[4],
]);
if ($date === false) {
@@ -186,9 +188,7 @@ protected static function datesWithSeparators(string $password): array
/**
* Find dates without separators in a password.
*
- * @param string $password
- *
- * @return array
+ * @return array
*/
protected static function datesWithoutSeparators(string $password): array
{
@@ -200,17 +200,17 @@ protected static function datesWithoutSeparators(string $password): array
for ($end = $begin + 3; $end - $begin < 8 && $end < $length; $end++) {
$token = mb_substr($password, $begin, $end - $begin + 1);
- if (!preg_match(static::DATE_NO_SEPARATOR, $token)) {
+ if (! preg_match(self::DATE_NO_SEPARATOR, $token)) {
continue;
}
$candidates = [];
- $possibleSplits = static::$DATE_SPLITS[mb_strlen($token)];
+ $possibleSplits = self::$DATE_SPLITS[mb_strlen($token)];
foreach ($possibleSplits as $splitPositions) {
- $day = (int)mb_substr($token, 0, $splitPositions[0]);
- $month = (int)mb_substr($token, $splitPositions[0], $splitPositions[1] - $splitPositions[0]);
- $year = (int)mb_substr($token, $splitPositions[1]);
+ $day = (int) mb_substr($token, 0, $splitPositions[0]);
+ $month = (int) mb_substr($token, $splitPositions[0], $splitPositions[1] - $splitPositions[0]);
+ $year = (int) mb_substr($token, $splitPositions[1]);
$date = static::checkDate([$day, $month, $year]);
if ($date !== false) {
@@ -218,7 +218,7 @@ protected static function datesWithoutSeparators(string $password): array
}
}
- if (empty($candidates)) {
+ if ($candidates === []) {
continue;
}
@@ -250,7 +250,7 @@ protected static function datesWithoutSeparators(string $password): array
'separator' => '',
'day' => $day,
'month' => $month,
- 'year' => $year
+ 'year' => $year,
];
}
}
@@ -259,25 +259,22 @@ protected static function datesWithoutSeparators(string $password): array
}
/**
- * @param array $candidate
* @return int Returns the number of years between the detected year and the current year for a candidate.
+ *
+ * @param array $candidate
*/
protected static function getDistanceForMatchCandidate(array $candidate): int
{
- return abs((int)$candidate['year'] - static::getReferenceYear());
- }
-
- public static function getReferenceYear(): int
- {
- return (int)date('Y');
+ return abs((int) $candidate['year'] - static::getReferenceYear());
}
/**
- * @param int[] $ints Three numbers in an array representing day, month and year (not necessarily in that order).
- * @return array|bool Returns an associative array containing 'day', 'month' and 'year' keys, or false if the
+ * @param array $ints Three numbers in an array representing day, month and year (not necessarily in that order).
+ *
+ * @return array|false Returns an associative array containing 'day', 'month' and 'year' keys, or false if the
* provided date array is invalid.
*/
- protected static function checkDate(array $ints)
+ protected static function checkDate(array $ints): array|false
{
# given a 3-tuple, discard if:
# middle int is over 31 (for all dmy formats, years are never allowed in the middle)
@@ -291,25 +288,17 @@ protected static function checkDate(array $ints)
return false;
}
- $invalidYear = count(array_filter($ints, function (int $int): bool {
- return ($int >= 100 && $int < static::MIN_YEAR)
- || ($int > static::MAX_YEAR);
- }));
+ $invalidYear = count(array_filter($ints, static fn (int $int): bool => ($int >= 100 && $int < self::MIN_YEAR)
+ || ($int > self::MAX_YEAR)));
if ($invalidYear > 0) {
return false;
}
- $over12 = count(array_filter($ints, function (int $int): bool {
- return $int > 12;
- }));
- $over31 = count(array_filter($ints, function (int $int): bool {
- return $int > 31;
- }));
- $under1 = count(array_filter($ints, function (int $int): bool {
- return $int <= 0;
- }));
-
- if ($over31 >= 2 || $over12 == 3 || $under1 >= 2) {
+ $over12 = count(array_filter($ints, static fn (int $int): bool => $int > 12));
+ $over31 = count(array_filter($ints, static fn (int $int): bool => $int > 31));
+ $under1 = count(array_filter($ints, static fn (int $int): bool => $int <= 0));
+
+ if ($over31 >= 2 || $over12 === 3 || $under1 >= 2) {
return false;
}
@@ -320,12 +309,13 @@ protected static function checkDate(array $ints)
];
foreach ($possibleYearSplits as [$year, $rest]) {
- if ($year >= static::MIN_YEAR && $year <= static::MAX_YEAR) {
- if ($dm = static::mapIntsToDayMonth($rest)) {
+ if ($year >= self::MIN_YEAR && $year <= self::MAX_YEAR) {
+ $dm = static::mapIntsToDayMonth($rest);
+ if ($dm !== false) {
return [
- 'year' => $year,
+ 'year' => $year,
'month' => $dm['month'],
- 'day' => $dm['day'],
+ 'day' => $dm['day'],
];
}
# for a candidate that includes a four-digit year,
@@ -336,11 +326,12 @@ protected static function checkDate(array $ints)
}
foreach ($possibleYearSplits as [$year, $rest]) {
- if ($dm = static::mapIntsToDayMonth($rest)) {
+ $dm = static::mapIntsToDayMonth($rest);
+ if ($dm !== false) {
return [
- 'year' => static::twoToFourDigitYear($year),
+ 'year' => static::twoToFourDigitYear($year),
'month' => $dm['month'],
- 'day' => $dm['day'],
+ 'day' => $dm['day'],
];
}
}
@@ -349,17 +340,18 @@ protected static function checkDate(array $ints)
}
/**
- * @param int[] $ints Two numbers in an array representing day and month (not necessarily in that order).
- * @return array|bool Returns an associative array containing 'day' and 'month' keys, or false if any combination
+ * @param array $ints Two numbers in an array representing day and month (not necessarily in that order).
+ *
+ * @return array|false Returns an associative array containing 'day' and 'month' keys, or false if any combination
* of the two numbers does not match a day and month.
*/
- protected static function mapIntsToDayMonth(array $ints)
+ protected static function mapIntsToDayMonth(array $ints): array|false
{
foreach ([$ints, array_reverse($ints)] as [$d, $m]) {
if ($d >= 1 && $d <= 31 && $m >= 1 && $m <= 12) {
return [
- 'day' => $d,
- 'month' => $m
+ 'day' => $d,
+ 'month' => $m,
];
}
}
@@ -369,6 +361,7 @@ protected static function mapIntsToDayMonth(array $ints)
/**
* @param int $year A two digit number representing a year.
+ *
* @return int Returns the most likely four digit year for the provided number.
*/
protected static function twoToFourDigitYear(int $year): int
@@ -395,12 +388,13 @@ protected static function twoToFourDigitYear(int $year): int
* '2015_06_04', in addition to matching 2015_06_04, will also contain
* 5(!) other date matches: 15_06_04, 5_06_04, ..., even 2015 (matched as 5/1/2020)
*
- * @param array $matches An array of matches (not Match objects)
- * @return array The provided array of matches, but with matches that are strict substrings of others removed.
+ * @param array $matches An array of matches (not Match objects)
+ *
+ * @return array The provided array of matches, but with matches that are strict substrings of others removed.
*/
protected static function removeRedundantMatches(array $matches): array
{
- return array_filter($matches, function (array $match) use ($matches): bool {
+ return array_filter($matches, static function (array $match) use ($matches): bool {
foreach ($matches as $otherMatch) {
if ($match === $otherMatch) {
continue;
@@ -417,7 +411,7 @@ protected static function removeRedundantMatches(array $matches): array
protected function getRawGuesses(): float
{
// base guesses: (year distance from REFERENCE_YEAR) * num_days * num_years
- $yearSpace = max(abs($this->year - static::getReferenceYear()), static::MIN_YEAR_SPACE);
+ $yearSpace = max(abs($this->year - static::getReferenceYear()), self::MIN_YEAR_SPACE);
$guesses = $yearSpace * 365;
// add factor of 4 for separator selection (one of ~4 choices)
diff --git a/src/Matchers/DictionaryMatch.php b/src/Matchers/DictionaryMatch.php
index 7be2340..4818cec 100644
--- a/src/Matchers/DictionaryMatch.php
+++ b/src/Matchers/DictionaryMatch.php
@@ -10,38 +10,49 @@
/** @phpstan-consistent-constructor */
class DictionaryMatch extends BaseMatch
{
- public $pattern = 'dictionary';
+ protected const START_UPPER = '/^[A-Z][^A-Z]+$/u';
+ protected const END_UPPER = '/^[^A-Z]+[A-Z]$/u';
+ protected const ALL_UPPER = '/^[^a-z]+$/u';
+ protected const ALL_LOWER = '/^[^A-Z]+$/u';
+ public string $pattern = 'dictionary';
/** @var string The name of the dictionary that the token was found in. */
- public $dictionaryName;
+ public string $dictionaryName = '';
/** @var int The rank of the token in the dictionary. */
- public $rank;
+ public int $rank = 0;
/** @var string The word that was matched from the dictionary. */
- public $matchedWord;
+ public string $matchedWord = '';
/** @var bool Whether or not the matched word was reversed in the token. */
- public $reversed = false;
+ public bool $reversed = false;
/** @var bool Whether or not the token contained l33t substitutions. */
- public $l33t = false;
+ public bool $l33t = false;
- /** @var array A cache of the frequency_lists json file */
- protected static $rankedDictionaries = [];
+ /** @var array A cache of the frequency_lists json file */
+ protected static array $rankedDictionaries = [];
- protected const START_UPPER = "/^[A-Z][^A-Z]+$/u";
- protected const END_UPPER = "/^[^A-Z]+[A-Z]$/u";
- protected const ALL_UPPER = "/^[^a-z]+$/u";
- protected const ALL_LOWER = "/^[^A-Z]+$/u";
+ /**
+ * @param array{'dictionary_name'?: string, 'matched_word'?: string, 'rank'?: int} $params
+ */
+ public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
+ {
+ parent::__construct($password, $begin, $end, $token);
+
+ $this->dictionaryName = $params['dictionary_name'] ?? '';
+ $this->matchedWord = $params['matched_word'] ?? '';
+ $this->rank = $params['rank'] ?? 0;
+ }
/**
* Match occurrences of dictionary words in password.
*
- * @param string $password
- * @param array $userInputs
- * @param array $rankedDictionaries
- * @return DictionaryMatch[]
+ * @param array $userInputs
+ * @param array $rankedDictionaries
+ *
+ * @return array
*/
public static function match(string $password, array $userInputs = [], array $rankedDictionaries = []): array
{
@@ -52,10 +63,10 @@ public static function match(string $password, array $userInputs = [], array $ra
$dicts = static::getRankedDictionaries();
}
- if (!empty($userInputs)) {
+ if ($userInputs !== []) {
$dicts['user_inputs'] = [];
foreach ($userInputs as $rank => $input) {
- $input_lower = mb_strtolower($input);
+ $input_lower = mb_strtolower((string) $input);
$dicts['user_inputs'][$input_lower] = $rank + 1; // rank starts at 1, not 0
}
}
@@ -66,29 +77,12 @@ public static function match(string $password, array $userInputs = [], array $ra
$matches[] = new static($password, $result['begin'], $result['end'], $result['token'], $result);
}
}
- Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
+ Matcher::usortStable($matches, Matcher::compareMatches(...));
return $matches;
}
/**
- * @param string $password
- * @param int $begin
- * @param int $end
- * @param string $token
- * @param array $params An array with keys: [dictionary_name, matched_word, rank].
- */
- public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
- {
- parent::__construct($password, $begin, $end, $token);
- if (!empty($params)) {
- $this->dictionaryName = $params['dictionary_name'] ?? '';
- $this->matchedWord = $params['matched_word'] ?? '';
- $this->rank = $params['rank'] ?? 0;
- }
- }
-
- /**
- * @return array{'warning': string, "suggestions": string[]}
+ * @return array{warning: string, suggestions: array}
*/
public function getFeedback(bool $isSoleMatch): array
{
@@ -97,13 +91,13 @@ public function getFeedback(bool $isSoleMatch): array
$feedback = [
'warning' => $this->getFeedbackWarning($isSoleMatch),
- 'suggestions' => []
+ 'suggestions' => [],
];
- if (preg_match($startUpper, $this->token)) {
+ if (preg_match($startUpper, (string) $this->token)) {
$feedback['suggestions'][] = "Capitalization doesn't help very much";
- } elseif (preg_match($allUpper, $this->token) && mb_strtolower($this->token) != $this->token) {
- $feedback['suggestions'][] = "All-uppercase is almost as easy to guess as all-lowercase";
+ } elseif (preg_match($allUpper, (string) $this->token) && mb_strtolower((string) $this->token) !== $this->token) {
+ $feedback['suggestions'][] = 'All-uppercase is almost as easy to guess as all-lowercase';
}
return $feedback;
@@ -113,15 +107,16 @@ public function getFeedbackWarning(bool $isSoleMatch): string
{
switch ($this->dictionaryName) {
case 'passwords':
- if ($isSoleMatch && !$this->l33t && !$this->reversed) {
+ if ($isSoleMatch && ! $this->l33t && ! $this->reversed) {
if ($this->rank <= 10) {
return 'This is a top-10 common password';
- } elseif ($this->rank <= 100) {
+ }
+ if ($this->rank <= 100) {
return 'This is a top-100 common password';
- } else {
- return 'This is a very common password';
}
- } elseif ($this->getGuessesLog10() <= 4) {
+ return 'This is a very common password';
+ }
+ if ($this->getGuessesLog10() <= 4) {
return 'This is similar to a commonly used password';
}
break;
@@ -135,9 +130,8 @@ public function getFeedbackWarning(bool $isSoleMatch): string
case 'female_names':
if ($isSoleMatch) {
return 'Names and surnames by themselves are easy to guess';
- } else {
- return 'Common names and surnames are easy to guess';
}
+ return 'Common names and surnames are easy to guess';
}
return '';
@@ -146,9 +140,9 @@ public function getFeedbackWarning(bool $isSoleMatch): string
/**
* Attempts to find the provided password (as well as all possible substrings) in a dictionary.
*
- * @param string $password
- * @param array $dict
- * @return array
+ * @param array $dict
+ *
+ * @return array
*/
protected static function dictionaryMatch(string $password, array $dict): array
{
@@ -179,12 +173,17 @@ protected static function dictionaryMatch(string $password, array $dict): array
/**
* Load ranked frequency dictionaries.
*
- * @return array
+ * @return array
*/
protected static function getRankedDictionaries(): array
{
- if (empty(self::$rankedDictionaries)) {
- $json = file_get_contents(dirname(__FILE__) . '/frequency_lists.json');
+ if (self::$rankedDictionaries === []) {
+ $json = file_get_contents(__DIR__ . '/frequency_lists.json');
+
+ if ($json === false) {
+ throw new \Exception('Failed to read frequency_lists.json file');
+ }
+
$data = json_decode($json, true);
$rankedLists = [];
@@ -208,15 +207,15 @@ protected function getRawGuesses(): float
protected function getUppercaseVariations(): float
{
$word = $this->token;
- if (preg_match(self::ALL_LOWER, $word) || mb_strtolower($word) === $word) {
+ if (preg_match(self::ALL_LOWER, (string) $word) || mb_strtolower((string) $word) === $word) {
return 1;
}
// a capitalized word is the most common capitalization scheme,
// so it only doubles the search space (uncapitalized + capitalized).
// allcaps and end-capitalized are common enough too, underestimate as 2x factor to be safe.
- foreach (array(self::START_UPPER, self::END_UPPER, self::ALL_UPPER) as $regex) {
- if (preg_match($regex, $word)) {
+ foreach ([self::START_UPPER, self::END_UPPER, self::ALL_UPPER] as $regex) {
+ if (preg_match($regex, (string) $word)) {
return 2;
}
}
@@ -224,12 +223,17 @@ protected function getUppercaseVariations(): float
// otherwise calculate the number of ways to capitalize U+L uppercase+lowercase letters
// with U uppercase letters or less. or, if there's more uppercase than lower (for eg. PASSwORD),
// the number of ways to lowercase U+L letters with L lowercase letters or less.
- $uppercase = count(array_filter(preg_split('//u', $word, -1, PREG_SPLIT_NO_EMPTY), 'ctype_upper'));
- $lowercase = count(array_filter(preg_split('//u', $word, -1, PREG_SPLIT_NO_EMPTY), 'ctype_lower'));
+ $splitWord = preg_split('//u', (string) $word, -1, PREG_SPLIT_NO_EMPTY);
$variations = 0;
- for ($i = 1; $i <= min($uppercase, $lowercase); $i++) {
- $variations += Binomial::binom($uppercase + $lowercase, $i);
+ if ($splitWord !== false) {
+ $uppercase = count(array_filter($splitWord, 'ctype_upper'));
+ $lowercase = count(array_filter($splitWord, 'ctype_lower'));
+
+ $min = min($uppercase, $lowercase);
+ for ($i = 1; $i <= $min; $i++) {
+ $variations += Binomial::binom($uppercase + $lowercase, $i);
+ }
}
return $variations;
}
diff --git a/src/Matchers/L33tMatch.php b/src/Matchers/L33tMatch.php
index 9163706..d3fdfdb 100644
--- a/src/Matchers/L33tMatch.php
+++ b/src/Matchers/L33tMatch.php
@@ -9,44 +9,56 @@
/**
* Class L33tMatch extends DictionaryMatch to translate l33t into dictionary words for matching.
+ *
* @package ZxcvbnPhp\Matchers
*/
class L33tMatch extends DictionaryMatch
{
- /** @var array An array of substitutions made to get from the token to the dictionary word. */
- public $sub = [];
+ /** @var array An array of substitutions made to get from the token to the dictionary word. */
+ public array $sub = [];
/** @var string A user-readable string that shows which substitutions were detected. */
- public $subDisplay;
+ public string $subDisplay;
/** @var bool Whether or not the token contained l33t substitutions. */
- public $l33t = true;
+ public bool $l33t = true;
/**
- * Match occurences of l33t words in password to dictionary words.
+ * @param array{'sub'?: array, 'sub_display'?: string} $params
+ */
+ public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
+ {
+ parent::__construct($password, $begin, $end, $token, $params);
+
+ $this->sub = $params['sub'] ?? [];
+ $this->subDisplay = $params['sub_display'] ?? '';
+ }
+
+ /**
+ * Match occurrences of l33t words in password to dictionary words.
+ *
+ * @param array $userInputs
+ * @param array $rankedDictionaries
*
- * @param string $password
- * @param array $userInputs
- * @param array $rankedDictionaries
- * @return L33tMatch[]
+ * @return array
*/
public static function match(string $password, array $userInputs = [], array $rankedDictionaries = []): array
{
// Translate l33t password and dictionary match the translated password.
$maps = array_filter(static::getL33tSubstitutions(static::getL33tSubtable($password)));
- if (empty($maps)) {
+ if ($maps === []) {
return [];
}
$matches = [];
- if (!$rankedDictionaries) {
+ if ($rankedDictionaries === []) {
$rankedDictionaries = static::getRankedDictionaries();
}
foreach ($maps as $map) {
$translatedWord = static::translate($password, $map);
- /** @var L33tMatch[] $results */
+ /** @var array $results */
$results = parent::match($translatedWord, $userInputs, $rankedDictionaries);
foreach ($results as $match) {
$token = mb_substr($password, $match->begin, $match->end - $match->begin + 1);
@@ -65,9 +77,9 @@ public static function match(string $password, array $userInputs = [], array $ra
$display = [];
foreach ($map as $i => $t) {
- if (mb_strpos($token, (string)$i) !== false) {
+ if (mb_strpos($token, (string) $i) !== false) {
$match->sub[$i] = $t;
- $display[] = "$i -> $t";
+ $display[] = "{$i} -> {$t}";
}
}
$match->token = $token;
@@ -77,28 +89,12 @@ public static function match(string $password, array $userInputs = [], array $ra
}
}
- Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
+ Matcher::usortStable($matches, Matcher::compareMatches(...));
return $matches;
}
/**
- * @param string $password
- * @param int $begin
- * @param int $end
- * @param string $token
- * @param array $params An array with keys: [sub, sub_display].
- */
- public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
- {
- parent::__construct($password, $begin, $end, $token, $params);
- if (!empty($params)) {
- $this->sub = $params['sub'] ?? [];
- $this->subDisplay = $params['sub_display'] ?? null;
- }
- }
-
- /**
- * @return array{'warning': string, "suggestions": string[]}
+ * @return array{'warning': string, "suggestions": array}
*/
public function getFeedback(bool $isSoleMatch): array
{
@@ -110,15 +106,16 @@ public function getFeedback(bool $isSoleMatch): array
}
/**
- * @param string $string
- * @param array $map
- * @return string
+ * @param array $map
*/
protected static function translate(string $string, array $map): string
{
return str_replace(array_keys($map), array_values($map), $string);
}
+ /**
+ * @return array>
+ */
protected static function getL33tTable(): array
{
return [
@@ -137,18 +134,25 @@ protected static function getL33tTable(): array
];
}
+ /**
+ * @return array>
+ */
protected static function getL33tSubtable(string $password): array
{
+ $subTable = [];
+
// The preg_split call below is a multibyte compatible version of str_split
- $passwordChars = array_unique(preg_split('//u', $password, -1, PREG_SPLIT_NO_EMPTY));
+ $splitItems = preg_split('//u', $password, -1, PREG_SPLIT_NO_EMPTY);
- $subTable = [];
+ if ($splitItems !== false) {
+ $passwordChars = array_unique($splitItems);
- $table = static::getL33tTable();
- foreach ($table as $letter => $substitutions) {
- foreach ($substitutions as $sub) {
- if (in_array($sub, $passwordChars)) {
- $subTable[$letter][] = $sub;
+ $table = static::getL33tTable();
+ foreach ($table as $letter => $substitutions) {
+ foreach ($substitutions as $sub) {
+ if (in_array($sub, $passwordChars)) {
+ $subTable[$letter][] = $sub;
+ }
}
}
}
@@ -156,22 +160,32 @@ protected static function getL33tSubtable(string $password): array
return $subTable;
}
+ /**
+ * @param array> $subtable
+ *
+ * @return array
+ */
protected static function getL33tSubstitutions(array $subtable): array
{
$keys = array_keys($subtable);
$substitutions = self::substitutionTableHelper($subtable, $keys, [[]]);
// Converts the substitution arrays from [ [a, b], [c, d] ] to [ a => b, c => d ]
- $substitutions = array_map(function (array $subArray): array {
- return array_combine(array_column($subArray, 0), array_column($subArray, 1));
- }, $substitutions);
+ $substitutions = array_map(static fn (array $subArray): array => array_combine(array_column($subArray, 0), array_column($subArray, 1)), $substitutions);
return $substitutions;
}
+ /**
+ * @param array> $table
+ * @param array $keys
+ * @param array $subs
+ *
+ * @return array
+ */
protected static function substitutionTableHelper(array $table, array $keys, array $subs): array
{
- if (empty($keys)) {
+ if ($keys === []) {
return $subs;
}
@@ -217,26 +231,25 @@ protected function getL33tVariations(): float
$variations = 1;
foreach ($this->sub as $substitution => $letter) {
- $characters = preg_split('//u', mb_strtolower($this->token), -1, PREG_SPLIT_NO_EMPTY);
-
- $subbed = count(array_filter($characters, function ($character) use ($substitution) {
- return (string)$character === (string)$substitution;
- }));
- $unsubbed = count(array_filter($characters, function ($character) use ($letter) {
- return (string)$character === (string)$letter;
- }));
-
- if ($subbed === 0 || $unsubbed === 0) {
- // for this sub, password is either fully subbed (444) or fully unsubbed (aaa)
- // treat that as doubling the space (attacker needs to try fully subbed chars in addition to
- // unsubbed.)
- $variations *= 2;
- } else {
- $possibilities = 0;
- for ($i = 1; $i <= min($subbed, $unsubbed); $i++) {
- $possibilities += Binomial::binom($subbed + $unsubbed, $i);
+ $characters = preg_split('//u', mb_strtolower((string) $this->token), -1, PREG_SPLIT_NO_EMPTY);
+
+ if ($characters !== false) {
+ $subbed = count(array_filter($characters, static fn ($character) => (string) $character === (string) $substitution));
+ $unsubbed = count(array_filter($characters, static fn ($character) => (string) $character === (string) $letter));
+
+ if ($subbed === 0 || $unsubbed === 0) {
+ // for this sub, password is either fully subbed (444) or fully unsubbed (aaa)
+ // treat that as doubling the space (attacker needs to try fully subbed chars in addition to
+ // unsubbed.)
+ $variations *= 2;
+ } else {
+ $possibilities = 0;
+ $min = min($subbed, $unsubbed);
+ for ($i = 1; $i <= $min; $i++) {
+ $possibilities += Binomial::binom($subbed + $unsubbed, $i);
+ }
+ $variations *= $possibilities;
}
- $variations *= $possibilities;
}
}
return $variations;
diff --git a/src/Matchers/MatchInterface.php b/src/Matchers/MatchInterface.php
index 24f1944..e6a4f73 100644
--- a/src/Matchers/MatchInterface.php
+++ b/src/Matchers/MatchInterface.php
@@ -10,11 +10,13 @@ interface MatchInterface
* Match this password.
*
* @param string $password Password to check for match
- * @param array $userInputs Array of values related to the user (optional)
+ * @param array $userInputs Array of values related to the user (optional)
+ *
* @code array('Alice Smith')
+ *
* @endcode
*
- * @return array|BaseMatch[] Array of Match objects
+ * @return array Array of Match objects
*/
public static function match(string $password, array $userInputs = []): array;
diff --git a/src/Matchers/RepeatMatch.php b/src/Matchers/RepeatMatch.php
index 1b23074..c549797 100644
--- a/src/Matchers/RepeatMatch.php
+++ b/src/Matchers/RepeatMatch.php
@@ -14,26 +14,39 @@ class RepeatMatch extends BaseMatch
public const LAZY_MATCH = '/(.+?)\1+/u';
public const ANCHORED_LAZY_MATCH = '/^(.+?)\1+$/u';
- public $pattern = 'repeat';
+ public string $pattern = 'repeat';
- /** @var MatchInterface[] An array of matches for the repeated section itself. */
- public $baseMatches = [];
+ /** @var array An array of matches for the repeated section itself. */
+ public array $baseMatches = [];
/** @var int The number of guesses required for the repeated section itself. */
- public $baseGuesses;
+ public int $baseGuesses;
/** @var int The number of times the repeated section is repeated. */
- public $repeatCount;
+ public int $repeatCount;
/** @var string The string that was repeated in the token. */
- public $repeatedChar;
+ public string $repeatedChar;
+
+ /**
+ * @param array{'repeated_char'?: string, 'base_guesses'?: int, 'base_matches'?: array, 'repeat_count'?: int} $params
+ */
+ public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
+ {
+ parent::__construct($password, $begin, $end, $token);
+
+ $this->repeatedChar = $params['repeated_char'] ?? '';
+ $this->baseGuesses = isset($params['base_guesses']) ? (int) $params['base_guesses'] : 0;
+ $this->baseMatches = $params['base_matches'] ?? [];
+ $this->repeatCount = isset($params['repeat_count']) ? (int) $params['repeat_count'] : 0;
+ }
/**
* Match 3 or more repeated characters.
*
- * @param string $password
- * @param array $userInputs
- * @return RepeatMatch[]
+ * @param array $userInputs
+ *
+ * @return array
*/
public static function match(string $password, array $userInputs = []): array
{
@@ -44,14 +57,16 @@ public static function match(string $password, array $userInputs = []): array
$greedyMatches = self::findAll($password, self::GREEDY_MATCH, $lastIndex);
$lazyMatches = self::findAll($password, self::LAZY_MATCH, $lastIndex);
- if (empty($greedyMatches)) {
+ if ($greedyMatches === []) {
break;
}
- if (mb_strlen($greedyMatches[0][0]['token']) > mb_strlen($lazyMatches[0][0]['token'])) {
+ if (mb_strlen((string) $greedyMatches[0][0]['token']) > mb_strlen((string) $lazyMatches[0][0]['token'])) {
$match = $greedyMatches[0];
- preg_match(self::ANCHORED_LAZY_MATCH, $match[0]['token'], $anchoredMatch);
- $repeatedChar = $anchoredMatch[1];
+ $repeatedChar = '';
+ if (preg_match(self::ANCHORED_LAZY_MATCH, (string) $match[0]['token'], $anchoredMatch)) {
+ $repeatedChar = $anchoredMatch[1];
+ }
} else {
$match = $lazyMatches[0];
$repeatedChar = $match[1]['token'];
@@ -64,7 +79,7 @@ public static function match(string $password, array $userInputs = []): array
$baseMatches = $baseAnalysis['sequence'];
$baseGuesses = $baseAnalysis['guesses'];
- $repeatCount = mb_strlen($match[0]['token']) / mb_strlen($repeatedChar);
+ $repeatCount = mb_strlen((string) $match[0]['token']) / mb_strlen((string) $repeatedChar);
$matches[] = new static(
$password,
@@ -73,9 +88,9 @@ public static function match(string $password, array $userInputs = []): array
$match[0]['token'],
[
'repeated_char' => $repeatedChar,
- 'base_guesses' => $baseGuesses,
- 'base_matches' => $baseMatches,
- 'repeat_count' => $repeatCount,
+ 'base_guesses' => $baseGuesses,
+ 'base_matches' => $baseMatches,
+ 'repeat_count' => $repeatCount,
]
);
@@ -86,40 +101,22 @@ public static function match(string $password, array $userInputs = []): array
}
/**
- * @return array{'warning': string, "suggestions": string[]}
+ * @return array{'warning': string, "suggestions": array}
*/
public function getFeedback(bool $isSoleMatch): array
{
- $warning = mb_strlen($this->repeatedChar) == 1
+ $warning = mb_strlen($this->repeatedChar) === 1
? 'Repeats like "aaa" are easy to guess'
: 'Repeats like "abcabcabc" are only slightly harder to guess than "abc"';
return [
- 'warning' => $warning,
+ 'warning' => $warning,
'suggestions' => [
'Avoid repeated words and characters',
],
];
}
- /**
- * @param string $password
- * @param int $begin
- * @param int $end
- * @param string $token
- * @param array $params An array with keys: [repeated_char, base_guesses, base_matches, repeat_count].
- */
- public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
- {
- parent::__construct($password, $begin, $end, $token);
- if (!empty($params)) {
- $this->repeatedChar = $params['repeated_char'] ?? '';
- $this->baseGuesses = $params['base_guesses'] ?? 0;
- $this->baseMatches = $params['base_matches'] ?? [];
- $this->repeatCount = $params['repeat_count'] ?? 0;
- }
- }
-
protected function getRawGuesses(): float
{
return $this->baseGuesses * $this->repeatCount;
diff --git a/src/Matchers/ReverseDictionaryMatch.php b/src/Matchers/ReverseDictionaryMatch.php
index a935bff..cc4bef8 100644
--- a/src/Matchers/ReverseDictionaryMatch.php
+++ b/src/Matchers/ReverseDictionaryMatch.php
@@ -9,19 +9,19 @@
class ReverseDictionaryMatch extends DictionaryMatch
{
/** @var bool Whether or not the matched word was reversed in the token. */
- public $reversed = true;
+ public bool $reversed = true;
/**
- * Match occurences of reversed dictionary words in password.
+ * Match occurrences of reversed dictionary words in password.
*
- * @param $password
- * @param array $userInputs
- * @param array $rankedDictionaries
- * @return ReverseDictionaryMatch[]
+ * @param array $userInputs
+ * @param array $rankedDictionaries
+ *
+ * @return array
*/
public static function match(string $password, array $userInputs = [], array $rankedDictionaries = []): array
{
- /** @var ReverseDictionaryMatch[] $matches */
+ /** @var array $matches */
$matches = parent::match(self::mbStrRev($password), $userInputs, $rankedDictionaries);
foreach ($matches as $match) {
$tempBegin = $match->begin;
@@ -32,23 +32,18 @@ public static function match(string $password, array $userInputs = [], array $ra
$match->begin = mb_strlen($password) - 1 - $match->end;
$match->end = mb_strlen($password) - 1 - $tempBegin;
}
- Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
+ Matcher::usortStable($matches, Matcher::compareMatches(...));
return $matches;
}
- protected function getRawGuesses(): float
- {
- return parent::getRawGuesses() * 2;
- }
-
/**
- * @return array{'warning': string, "suggestions": string[]}
+ * @return array{'warning': string, "suggestions": array}
*/
public function getFeedback(bool $isSoleMatch): array
{
$feedback = parent::getFeedback($isSoleMatch);
- if (mb_strlen($this->token) >= 4) {
+ if (mb_strlen((string) $this->token) >= 4) {
$feedback['suggestions'][] = "Reversed words aren't much harder to guess";
}
@@ -58,7 +53,11 @@ public function getFeedback(bool $isSoleMatch): array
public static function mbStrRev(string $string, ?string $encoding = null): string
{
if ($encoding === null) {
- $encoding = mb_detect_encoding($string) ?: 'UTF-8';
+ $encoding = mb_detect_encoding($string);
+
+ if ($encoding === false) {
+ $encoding = 'UTF-8';
+ }
}
$length = mb_strlen($string, $encoding);
$reversed = '';
@@ -68,4 +67,9 @@ public static function mbStrRev(string $string, ?string $encoding = null): strin
return $reversed;
}
+
+ protected function getRawGuesses(): float
+ {
+ return parent::getRawGuesses() * 2;
+ }
}
diff --git a/src/Matchers/SequenceMatch.php b/src/Matchers/SequenceMatch.php
index 17a8ea8..e72696f 100644
--- a/src/Matchers/SequenceMatch.php
+++ b/src/Matchers/SequenceMatch.php
@@ -9,23 +9,35 @@ class SequenceMatch extends BaseMatch
{
public const MAX_DELTA = 5;
- public $pattern = 'sequence';
+ public string $pattern = 'sequence';
/** @var string The name of the detected sequence. */
- public $sequenceName;
+ public string $sequenceName;
/** @var int The number of characters in the complete sequence space. */
- public $sequenceSpace;
+ public int $sequenceSpace;
/** @var bool True if the sequence is ascending, and false if it is descending. */
- public $ascending;
+ public bool $ascending;
+
+ /**
+ * @param array{'sequenceName'?: string, 'sequenceSpace'?: int, 'ascending'?: bool} $params
+ */
+ public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
+ {
+ parent::__construct($password, $begin, $end, $token);
+
+ $this->sequenceName = $params['sequenceName'] ?? '';
+ $this->sequenceSpace = $params['sequenceSpace'] ?? 0;
+ $this->ascending = $params['ascending'] ?? false;
+ }
/**
* Match sequences of three or more characters.
*
- * @param string $password
- * @param array $userInputs
- * @return SequenceMatch[]
+ * @param array $userInputs Array of values related to the user (optional)
+ *
+ * @return array
*/
public static function match(string $password, array $userInputs = []): array
{
@@ -58,7 +70,10 @@ public static function match(string $password, array $userInputs = []): array
return $matches;
}
- public static function findSequenceMatch(string $password, int $begin, int $end, int $delta, array &$matches)
+ /**
+ * @param array $matches
+ */
+ public static function findSequenceMatch(string $password, int $begin, int $end, int $delta, array &$matches): void
{
if ($end - $begin > 1 || abs($delta) === 1) {
if (abs($delta) > 0 && abs($delta) <= self::MAX_DELTA) {
@@ -87,41 +102,24 @@ public static function findSequenceMatch(string $password, int $begin, int $end,
}
/**
- * @return array{'warning': string, "suggestions": string[]}
+ * @return array{'warning': string, "suggestions": array}
*/
public function getFeedback(bool $isSoleMatch): array
{
return [
- 'warning' => "Sequences like abc or 6543 are easy to guess",
+ 'warning' => 'Sequences like abc or 6543 are easy to guess',
'suggestions' => [
- 'Avoid sequences'
- ]
+ 'Avoid sequences',
+ ],
];
}
- /**
- * @param string $password
- * @param int $begin
- * @param int $end
- * @param string $token
- * @param array $params An array with keys: [sequenceName, sequenceSpace, ascending].
- */
- public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
- {
- parent::__construct($password, $begin, $end, $token);
- if (!empty($params)) {
- $this->sequenceName = $params['sequenceName'] ?? '';
- $this->sequenceSpace = $params['sequenceSpace'] ?? 0;
- $this->ascending = $params['ascending'] ?? false;
- }
- }
-
protected function getRawGuesses(): float
{
- $firstCharacter = mb_substr($this->token, 0, 1);
+ $firstCharacter = mb_substr((string) $this->token, 0, 1);
$guesses = 0;
- if (in_array($firstCharacter, array('a', 'A', 'z', 'Z', '0', '1', '9'), true)) {
+ if (in_array($firstCharacter, ['a', 'A', 'z', 'Z', '0', '1', '9'], true)) {
$guesses += 4; // lower guesses for obvious starting points
} elseif (ctype_digit($firstCharacter)) {
$guesses += 10; // digits
@@ -131,12 +129,12 @@ protected function getRawGuesses(): float
$guesses += 26;
}
- if (!$this->ascending) {
+ if (! $this->ascending) {
// need to try a descending sequence in addition to every ascending sequence ->
// 2x guesses
$guesses *= 2;
}
- return $guesses * mb_strlen($this->token);
+ return $guesses * mb_strlen((string) $this->token);
}
}
diff --git a/src/Matchers/SpatialMatch.php b/src/Matchers/SpatialMatch.php
index 1a94603..bda9fce 100644
--- a/src/Matchers/SpatialMatch.php
+++ b/src/Matchers/SpatialMatch.php
@@ -18,33 +18,43 @@ class SpatialMatch extends BaseMatch
public const KEYBOARD_AVERAGE_DEGREES = 4.5957446809; // 432 / 94
public const KEYPAD_AVERAGE_DEGREES = 5.0666666667; // 76 / 15
- public $pattern = 'spatial';
+ public string $pattern = 'spatial';
/** @var int The number of characters the shift key was held for in the token. */
- public $shiftedCount;
+ public int $shiftedCount = 0;
/** @var int The number of turns on the keyboard required to complete the token. */
- public $turns;
+ public int $turns = 0;
/** @var string The keyboard layout that the token is a spatial match on. */
- public $graph;
+ public string $graph;
- /** @var array A cache of the adjacency_graphs json file */
- protected static $adjacencyGraphs = [];
+ /** @var array A cache of the adjacency_graphs json file */
+ protected static array $adjacencyGraphs = [];
+
+ /**
+ * @param array{'graph'?: string, 'shifted_count'?: int, 'turns'?: int} $params
+ */
+ public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
+ {
+ parent::__construct($password, $begin, $end, $token);
+ $this->graph = $params['graph'] ?? 'qwerty';
+ $this->shiftedCount = $params['shifted_count'] ?? 0;
+ $this->turns = $params['turns'] ?? 0;
+ }
/**
* Match spatial patterns based on keyboard layouts (e.g. qwerty, dvorak, keypad).
*
- * @param string $password
- * @param array $userInputs
- * @param array $graphs
- * @return SpatialMatch[]
+ * @param array $userInputs
+ * @param array $graphs
+ *
+ * @return array
*/
public static function match(string $password, array $userInputs = [], array $graphs = []): array
{
-
$matches = [];
- if (!$graphs) {
+ if (! $graphs) {
$graphs = static::getAdjacencyGraphs();
}
foreach ($graphs as $name => $graph) {
@@ -54,50 +64,64 @@ public static function match(string $password, array $userInputs = [], array $gr
$matches[] = new static($password, $result['begin'], $result['end'], $result['token'], $result);
}
}
- Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
+ Matcher::usortStable($matches, Matcher::compareMatches(...));
return $matches;
}
/**
- * @return array{'warning': string, "suggestions": string[]}
+ * @return array{'warning': string, "suggestions": array}
*/
public function getFeedback(bool $isSoleMatch): array
{
- $warning = $this->turns == 1
+ $warning = $this->turns === 1
? 'Straight rows of keys are easy to guess'
: 'Short keyboard patterns are easy to guess';
return [
'warning' => $warning,
'suggestions' => [
- 'Use a longer keyboard pattern with more turns'
- ]
+ 'Use a longer keyboard pattern with more turns',
+ ],
];
}
/**
- * @param string $password
- * @param int $begin
- * @param int $end
- * @param string $token
- * @param array $params An array with keys: [graph (required), shifted_count, turns].
+ * Load adjacency graphs.
+ *
+ * @return array
*/
- public function __construct(string $password, int $begin, int $end, string $token, array $params = [])
+ public static function getAdjacencyGraphs(): array
{
- parent::__construct($password, $begin, $end, $token);
- $this->graph = $params['graph'];
- if (!empty($params)) {
- $this->shiftedCount = $params['shifted_count'] ?? null;
- $this->turns = $params['turns'] ?? null;
+ if (self::$adjacencyGraphs === []) {
+ $json = file_get_contents(__DIR__ . '/adjacency_graphs.json');
+
+ if ($json === false) {
+ throw new \Exception('Failed to read adjacency_graphs.json file');
+ }
+
+ $data = json_decode($json, true);
+
+ // This seems pointless, but the data file is not guaranteed to be in any particular order.
+ // We want to be in the exact order below so as to match most closely with upstream, because when a match
+ // can be found in multiple graphs (such as 789), the one that's listed first is that one that will be picked.
+ $data = [
+ 'qwerty' => $data['qwerty'],
+ 'dvorak' => $data['dvorak'],
+ 'keypad' => $data['keypad'],
+ 'mac_keypad' => $data['mac_keypad'],
+ ];
+ self::$adjacencyGraphs = $data;
}
+
+ return self::$adjacencyGraphs;
}
/**
* Match spatial patterns in a adjacency graph.
- * @param string $password
- * @param array $graph
- * @param string $graphName
- * @return array
+ *
+ * @param array $graph
+ *
+ * @return array
*/
protected static function graphMatch(string $password, array $graph, string $graphName): array
{
@@ -168,7 +192,7 @@ protected static function graphMatch(string $password, array $graph, string $gra
'end' => $j - 1,
'token' => mb_substr($password, $i, $j - $i),
'turns' => $turns,
- 'shifted_count' => $shiftedCount
+ 'shifted_count' => $shiftedCount,
];
}
// ...and then start a new search for the rest of the password.
@@ -183,42 +207,11 @@ protected static function graphMatch(string $password, array $graph, string $gra
/**
* Get the index of a string a character first
- *
- * @param string $string
- * @param string $char
- *
- * @return int
*/
protected static function indexOf(string $string, string $char): int
{
$pos = mb_strpos($string, $char);
- return ($pos === false ? -1 : $pos);
- }
-
- /**
- * Load adjacency graphs.
- *
- * @return array
- */
- public static function getAdjacencyGraphs(): array
- {
- if (empty(self::$adjacencyGraphs)) {
- $json = file_get_contents(dirname(__FILE__) . '/adjacency_graphs.json');
- $data = json_decode($json, true);
-
- // This seems pointless, but the data file is not guaranteed to be in any particular order.
- // We want to be in the exact order below so as to match most closely with upstream, because when a match
- // can be found in multiple graphs (such as 789), the one that's listed first is that one that will be picked.
- $data = [
- 'qwerty' => $data['qwerty'],
- 'dvorak' => $data['dvorak'],
- 'keypad' => $data['keypad'],
- 'mac_keypad' => $data['mac_keypad'],
- ];
- self::$adjacencyGraphs = $data;
- }
-
- return self::$adjacencyGraphs;
+ return $pos === false ? -1 : $pos;
}
protected function getRawGuesses(): float
@@ -232,14 +225,14 @@ protected function getRawGuesses(): float
}
$guesses = 0;
- $length = mb_strlen($this->token);
+ $length = mb_strlen((string) $this->token);
$turns = $this->turns;
// estimate the number of possible patterns w/ length L or less with t turns or less.
for ($i = 2; $i <= $length; $i++) {
$possibleTurns = min($turns, $i - 1);
for ($j = 1; $j <= $possibleTurns; $j++) {
- $guesses += Binomial::binom($i - 1, $j - 1) * $startingPosition * pow($averageDegree, $j);
+ $guesses += Binomial::binom($i - 1, $j - 1) * $startingPosition * $averageDegree ** $j;
}
}
@@ -253,7 +246,8 @@ protected function getRawGuesses(): float
$guesses *= 2;
} else {
$variations = 0;
- for ($i = 1; $i <= min($shifted, $unshifted); $i++) {
+ $min = min($shifted, $unshifted);
+ for ($i = 1; $i <= $min; $i++) {
$variations += Binomial::binom($shifted + $unshifted, $i);
}
$guesses *= $variations;
diff --git a/src/Matchers/YearMatch.php b/src/Matchers/YearMatch.php
index 84f711c..d0d272e 100644
--- a/src/Matchers/YearMatch.php
+++ b/src/Matchers/YearMatch.php
@@ -10,45 +10,44 @@ final class YearMatch extends BaseMatch
{
public const NUM_YEARS = 119;
- public $pattern = 'regex';
- public $regexName = 'recent_year';
+ public string $pattern = 'regex';
+ public string $regexName = 'recent_year';
/**
* Match occurrences of years in a password
*
- * @param string $password
- * @param array $userInputs
- * @return YearMatch[]
+ * @param array $userInputs Array of values related to the user (optional)
+ *
+ * @return array
*/
public static function match(string $password, array $userInputs = []): array
{
$matches = [];
- $groups = static::findAll($password, "/(19\d\d|20\d\d)/u");
+ $groups = self::findAll($password, "/(19\d\d|20\d\d)/u");
foreach ($groups as $captures) {
$matches[] = new static($password, $captures[1]['begin'], $captures[1]['end'], $captures[1]['token']);
}
- Matcher::usortStable($matches, [Matcher::class, 'compareMatches']);
+ Matcher::usortStable($matches, Matcher::compareMatches(...));
return $matches;
}
-
/**
- * @return array{'warning': string, "suggestions": string[]}
+ * @return array{'warning': string, "suggestions": array}
*/
public function getFeedback(bool $isSoleMatch): array
{
return [
- 'warning' => "Recent years are easy to guess",
+ 'warning' => 'Recent years are easy to guess',
'suggestions' => [
'Avoid recent years',
'Avoid years that are associated with you',
- ]
+ ],
];
}
protected function getRawGuesses(): float
{
- $yearSpace = abs($this->token - DateMatch::getReferenceYear());
+ $yearSpace = abs((int) $this->token - DateMatch::getReferenceYear());
return max($yearSpace, DateMatch::MIN_YEAR_SPACE);
}
}
diff --git a/src/Math/Binomial.php b/src/Math/Binomial.php
index 94e882f..62264d1 100644
--- a/src/Math/Binomial.php
+++ b/src/Math/Binomial.php
@@ -4,25 +4,21 @@
namespace ZxcvbnPhp\Math;
-use ZxcvbnPhp\Math\Impl\BinomialProviderPhp73Gmp;
use ZxcvbnPhp\Math\Impl\BinomialProviderFloat64;
use ZxcvbnPhp\Math\Impl\BinomialProviderInt64;
+use ZxcvbnPhp\Math\Impl\BinomialProviderPhp73Gmp;
class Binomial
{
- private static $provider = null;
+ private static ?BinomialProvider $provider = null;
private function __construct()
{
- throw new \LogicException(__CLASS__ . " is static");
+ throw new \LogicException(self::class . ' is static');
}
/**
* Calculate binomial coefficient (n choose k).
- *
- * @param int $n
- * @param int $k
- * @return float
*/
public static function binom(int $n, int $k): float
{
@@ -39,15 +35,15 @@ public static function getProvider(): BinomialProvider
}
/**
- * @return string[]
+ * @return array
*/
public static function getUsableProviderClasses(): array
{
// In order of priority. The first provider with a value of true will be used.
$possibleProviderClasses = [
BinomialProviderPhp73Gmp::class => function_exists('gmp_binomial'),
- BinomialProviderInt64::class => PHP_INT_SIZE >= 8,
- BinomialProviderFloat64::class => PHP_FLOAT_DIG >= 15,
+ BinomialProviderInt64::class => PHP_INT_SIZE >= 8,
+ BinomialProviderFloat64::class => PHP_FLOAT_DIG >= 15,
];
$possibleProviderClasses = array_filter($possibleProviderClasses);
@@ -59,12 +55,18 @@ private static function initProvider(): BinomialProvider
{
$providerClasses = self::getUsableProviderClasses();
- if (!$providerClasses) {
- throw new \LogicException("No valid providers");
+ if ($providerClasses === []) {
+ throw new \LogicException('No valid providers');
}
$bestProviderClass = reset($providerClasses);
- return new $bestProviderClass();
+ $provider = new $bestProviderClass();
+
+ if (! $provider instanceof BinomialProvider) {
+ throw new \LogicException('Inval provider class: ' . $bestProviderClass);
+ }
+
+ return $provider;
}
}
diff --git a/src/Math/BinomialProvider.php b/src/Math/BinomialProvider.php
index fbec156..239103a 100644
--- a/src/Math/BinomialProvider.php
+++ b/src/Math/BinomialProvider.php
@@ -8,10 +8,6 @@ interface BinomialProvider
{
/**
* Calculate binomial coefficient (n choose k).
- *
- * @param int $n
- * @param int $k
- * @return float
*/
public function binom(int $n, int $k): float;
}
diff --git a/src/Math/Impl/AbstractBinomialProvider.php b/src/Math/Impl/AbstractBinomialProvider.php
index fe2a043..da18e9d 100644
--- a/src/Math/Impl/AbstractBinomialProvider.php
+++ b/src/Math/Impl/AbstractBinomialProvider.php
@@ -11,7 +11,7 @@ abstract class AbstractBinomialProvider implements BinomialProvider
public function binom(int $n, int $k): float
{
if ($k < 0 || $n < 0) {
- throw new \DomainException("n and k must be non-negative");
+ throw new \DomainException('n and k must be non-negative');
}
if ($k > $n) {
diff --git a/src/Math/Impl/AbstractBinomialProviderWithFallback.php b/src/Math/Impl/AbstractBinomialProviderWithFallback.php
index 1487723..49e014d 100644
--- a/src/Math/Impl/AbstractBinomialProviderWithFallback.php
+++ b/src/Math/Impl/AbstractBinomialProviderWithFallback.php
@@ -6,14 +6,11 @@
abstract class AbstractBinomialProviderWithFallback extends AbstractBinomialProvider
{
- /**
- * @var AbstractBinomialProvider|null
- */
- private $fallback = null;
+ private ?AbstractBinomialProvider $fallback = null;
protected function calculate(int $n, int $k): float
{
- return $this->tryCalculate($n, $k) ?? $this->getFallbackProvider()->calculate($n, $k);
+ return $this->tryCalculate($n, $k) ?? $this->getFallbackProvider()->calculate($n, $k);
}
abstract protected function tryCalculate(int $n, int $k): ?float;
diff --git a/src/Math/Impl/BinomialProviderInt64.php b/src/Math/Impl/BinomialProviderInt64.php
index 6016172..b5672a1 100644
--- a/src/Math/Impl/BinomialProviderInt64.php
+++ b/src/Math/Impl/BinomialProviderInt64.php
@@ -4,8 +4,6 @@
namespace ZxcvbnPhp\Math\Impl;
-use TypeError;
-
class BinomialProviderInt64 extends AbstractBinomialProviderWithFallback
{
protected function initFallbackProvider(): AbstractBinomialProvider
@@ -15,19 +13,15 @@ protected function initFallbackProvider(): AbstractBinomialProvider
protected function tryCalculate(int $n, int $k): ?float
{
- try {
- $c = 1;
-
- for ($i = 1; $i <= $k; $i++, $n--) {
- // We're aiming for $c * $n / $i, but the $c * $n part could overflow, so use $c / $i * $n instead. The caveat here is that in
- // order to get a precise answer, we need to avoid floats, which means we need to deal with whole part and the remainder
- // separately.
- $c = intdiv($c, $i) * $n + intdiv($c % $i * $n, $i);
- }
+ $c = 1;
- return (float)$c;
- } catch (TypeError $ex) {
- return null;
+ for ($i = 1; $i <= $k; $i++, $n--) {
+ // We're aiming for $c * $n / $i, but the $c * $n part could overflow, so use $c / $i * $n instead. The caveat here is that in
+ // order to get a precise answer, we need to avoid floats, which means we need to deal with whole part and the remainder
+ // separately.
+ $c = intdiv($c, $i) * $n + intdiv($c % $i * $n, $i);
}
+
+ return (float) $c;
}
}
diff --git a/src/Math/Impl/BinomialProviderPhp73Gmp.php b/src/Math/Impl/BinomialProviderPhp73Gmp.php
index 2d5fa3d..317d8fa 100644
--- a/src/Math/Impl/BinomialProviderPhp73Gmp.php
+++ b/src/Math/Impl/BinomialProviderPhp73Gmp.php
@@ -12,6 +12,6 @@ class BinomialProviderPhp73Gmp extends AbstractBinomialProvider
*/
protected function calculate(int $n, int $k): float
{
- return (float)gmp_strval(gmp_binomial($n, $k));
+ return (float) gmp_strval(gmp_binomial($n, $k));
}
}
diff --git a/src/Scorer.php b/src/Scorer.php
index 7cbd258..8dc6d7c 100644
--- a/src/Scorer.php
+++ b/src/Scorer.php
@@ -4,9 +4,8 @@
namespace ZxcvbnPhp;
-use ZxcvbnPhp\Matchers\Bruteforce;
use ZxcvbnPhp\Matchers\BaseMatch;
-use ZxcvbnPhp\Matchers\MatchInterface;
+use ZxcvbnPhp\Matchers\Bruteforce;
/**
* scorer - takes a list of potential matches, ranks and evaluates them,
@@ -20,9 +19,13 @@ class Scorer
public const MIN_SUBMATCH_GUESSES_SINGLE_CHAR = 10;
public const MIN_SUBMATCH_GUESSES_MULTI_CHAR = 50;
- protected $password;
- protected $excludeAdditive;
- protected $optimal = [];
+ protected string $password = '';
+ protected bool $excludeAdditive = false;
+
+ /**
+ * @var array
+ */
+ protected array $optimal = [];
/**
* ------------------------------------------------------------------------------
@@ -56,10 +59,9 @@ class Scorer
* sequences before length-3. assuming at minimum D guesses per pattern type,
* D^(l-1) approximates Sum(D^i for i in [1..l-1]
*
- * @param string $password
- * @param MatchInterface[] $matches
- * @param bool $excludeAdditive
- * @return array Returns an array with these keys: [password, guesses, guesses_log10, sequence]
+ * @param array $matches
+ *
+ * @return array{'password': string, 'guesses': float, 'guesses_log10': float, 'sequence': mixed}
*/
public function getMostGuessableMatchSequence(string $password, array $matches, bool $excludeAdditive = false): array
{
@@ -77,11 +79,7 @@ public function getMostGuessableMatchSequence(string $password, array $matches,
// small detail: for deterministic output, sort each sublist by i.
foreach ($matchesByEndIndex as &$matches) {
- usort($matches, function ($a, $b) {
- /** @var $a BaseMatch */
- /** @var $b BaseMatch */
- return $a->begin - $b->begin;
- });
+ usort($matches, static fn (BaseMatch $a, BaseMatch $b) => $a->begin - $b->begin);
}
$this->optimal = [
@@ -104,7 +102,7 @@ public function getMostGuessableMatchSequence(string $password, array $matches,
foreach ($matchesByEndIndex[$k] as $match) {
if ($match->begin > 0) {
foreach ($this->optimal['m'][$match->begin - 1] as $l => $null) {
- $l = (int)$l;
+ $l = (int) $l;
$this->update($match, $l + 1);
}
} else {
@@ -114,7 +112,6 @@ public function getMostGuessableMatchSequence(string $password, array $matches,
$this->bruteforceUpdate($k);
}
-
if ($length === 0) {
$guesses = 1.0;
$optimalSequence = [];
@@ -135,8 +132,6 @@ public function getMostGuessableMatchSequence(string $password, array $matches,
/**
* helper: considers whether a length-l sequence ending at match m is better (fewer guesses)
* than previously encountered sequences, updating state if so.
- * @param BaseMatch $match
- * @param int $length
*/
protected function update(BaseMatch $match, int $length): void
{
@@ -155,8 +150,8 @@ protected function update(BaseMatch $match, int $length): void
// calculate the minimization func
$g = $this->factorial($length) * $pi;
- if (!$this->excludeAdditive) {
- $g += pow(self::MIN_GUESSES_BEFORE_GROWING_SEQUENCE, $length - 1);
+ if (! $this->excludeAdditive) {
+ $g += self::MIN_GUESSES_BEFORE_GROWING_SEQUENCE ** ($length - 1);
}
// update state if new best.
@@ -184,7 +179,6 @@ protected function update(BaseMatch $match, int $length): void
/**
* helper: evaluate bruteforce matches ending at k
- * @param int $end
*/
protected function bruteforceUpdate(int $end): void
{
@@ -198,7 +192,7 @@ protected function bruteforceUpdate(int $end): void
for ($i = 1; $i <= $end; $i++) {
$match = $this->makeBruteforceMatch($i, $end);
foreach ($this->optimal['m'][$i - 1] as $l => $lastM) {
- $l = (int)$l;
+ $l = (int) $l;
// corner: an optimal sequence will never have two adjacent bruteforce matches.
// it is strictly better to have a single bruteforce match spanning the same region:
@@ -215,19 +209,16 @@ protected function bruteforceUpdate(int $end): void
/**
* helper: make bruteforce match objects spanning i to j, inclusive.
- * @param int $begin
- * @param int $end
- * @return Bruteforce
*/
protected function makeBruteforceMatch(int $begin, int $end): Bruteforce
{
- return new Bruteforce($this->password, $begin, $end, mb_substr($this->password, $begin, $end - $begin + 1));
+ return new Bruteforce($this->password, $begin, $end, mb_substr((string) $this->password, $begin, $end - $begin + 1));
}
/**
* helper: step backwards through optimal.m starting at the end, constructing the final optimal match sequence.
- * @param int $n
- * @return MatchInterface[]
+ *
+ * @return array
*/
protected function unwind(int $n): array
{
@@ -257,8 +248,6 @@ protected function unwind(int $n): array
/**
* unoptimized, called only on small n
- * @param int $n
- * @return int
*/
protected function factorial(int $n): int
{
diff --git a/src/TimeEstimator.php b/src/TimeEstimator.php
index bf67de9..ddbea79 100644
--- a/src/TimeEstimator.php
+++ b/src/TimeEstimator.php
@@ -13,16 +13,15 @@
class TimeEstimator
{
/**
- * @param int|float $guesses
- * @return array
+ * @return array
*/
- public function estimateAttackTimes(float $guesses): array
+ public function estimateAttackTimes(int|float $guesses): array
{
$crack_times_seconds = [
'online_throttling_100_per_hour' => $guesses / (100 / 3600),
'online_no_throttling_10_per_second' => $guesses / 10,
'offline_slow_hashing_1e4_per_second' => $guesses / 1e4,
- 'offline_fast_hashing_1e10_per_second' => $guesses / 1e10
+ 'offline_fast_hashing_1e10_per_second' => $guesses / 1e10,
];
$crack_times_display = array_map(
@@ -33,7 +32,7 @@ public function estimateAttackTimes(float $guesses): array
return [
'crack_times_seconds' => $crack_times_seconds,
'crack_times_display' => $crack_times_display,
- 'score' => $this->guessesToScore($guesses)
+ 'score' => $this->guessesToScore($guesses),
];
}
@@ -68,7 +67,7 @@ protected function guessesToScore(float $guesses): int
protected function displayTime(float $seconds): string
{
- $callback = function (float $seconds): array {
+ $callback = static function (float $seconds): array {
$minute = 60;
$hour = $minute * 60;
$day = $hour * 24;
@@ -82,32 +81,32 @@ protected function displayTime(float $seconds): string
if ($seconds < $minute) {
$base = round($seconds);
- return [$base, "$base second"];
+ return [$base, "{$base} second"];
}
if ($seconds < $hour) {
$base = round($seconds / $minute);
- return [$base, "$base minute"];
+ return [$base, "{$base} minute"];
}
if ($seconds < $day) {
$base = round($seconds / $hour);
- return [$base, "$base hour"];
+ return [$base, "{$base} hour"];
}
if ($seconds < $month) {
$base = round($seconds / $day);
- return [$base, "$base day"];
+ return [$base, "{$base} day"];
}
if ($seconds < $year) {
$base = round($seconds / $month);
- return [$base, "$base month"];
+ return [$base, "{$base} month"];
}
if ($seconds < $century) {
$base = round($seconds / $year);
- return [$base, "$base year"];
+ return [$base, "{$base} year"];
}
return [null, 'centuries'];
diff --git a/src/Zxcvbn.php b/src/Zxcvbn.php
index 697dd76..10a6e42 100644
--- a/src/Zxcvbn.php
+++ b/src/Zxcvbn.php
@@ -11,34 +11,25 @@
*/
class Zxcvbn
{
- /**
- * @var
- */
- protected $matcher;
+ protected Matcher $matcher;
- /**
- * @var
- */
- protected $scorer;
+ protected Scorer $scorer;
- /**
- * @var
- */
- protected $timeEstimator;
+ protected TimeEstimator $timeEstimator;
- /**
- * @var
- */
- protected $feedback;
+ protected Feedback $feedback;
public function __construct()
{
- $this->matcher = new \ZxcvbnPhp\Matcher();
- $this->scorer = new \ZxcvbnPhp\Scorer();
- $this->timeEstimator = new \ZxcvbnPhp\TimeEstimator();
- $this->feedback = new \ZxcvbnPhp\Feedback();
+ $this->matcher = new Matcher();
+ $this->scorer = new Scorer();
+ $this->timeEstimator = new TimeEstimator();
+ $this->feedback = new Feedback();
}
+ /**
+ * @param class-string $className
+ */
public function addMatcher(string $className): self
{
$this->matcher->addMatcher($className);
@@ -49,10 +40,10 @@ public function addMatcher(string $className): self
/**
* Calculate password strength via non-overlapping minimum entropy patterns.
*
- * @param string $password Password to measure
- * @param array $userInputs Optional user inputs
+ * @param string $password Password to measure
+ * @param array $userInputs Optional user inputs
*
- * @return array Strength result array with keys:
+ * @return array Strength result array with keys:
* password
* entropy
* match_sequence
@@ -63,9 +54,7 @@ public function passwordStrength(string $password, array $userInputs = []): arra
$timeStart = microtime(true);
$sanitizedInputs = array_map(
- function ($input) {
- return mb_strtolower((string) $input);
- },
+ static fn ($input) => mb_strtolower((string) $input),
$userInputs
);
@@ -82,8 +71,8 @@ function ($input) {
$result,
$attackTimes,
[
- 'feedback' => $feedback,
- 'calc_time' => microtime(true) - $timeStart
+ 'feedback' => $feedback,
+ 'calc_time' => microtime(true) - $timeStart,
]
);
}
diff --git a/test/FeedbackTest.php b/test/FeedbackTest.php
index 4140c87..e60d3ce 100644
--- a/test/FeedbackTest.php
+++ b/test/FeedbackTest.php
@@ -12,47 +12,46 @@
class FeedbackTest extends TestCase
{
- /** @var Feedback */
- private $feedback;
+ private Feedback $feedback;
public function setUp(): void
{
$this->feedback = new Feedback();
}
- public function testFeedbackForEmptyPassword()
+ public function testFeedbackForEmptyPassword(): void
{
$feedback = $this->feedback->getFeedback(0, []);
- $this->assertSame('', $feedback['warning'], "default warning");
+ $this->assertSame('', $feedback['warning'], 'default warning');
$this->assertContains(
'Use a few words, avoid common phrases',
$feedback['suggestions'],
- "default suggestion #1"
+ 'default suggestion #1'
);
$this->assertContains(
'No need for symbols, digits, or uppercase letters',
$feedback['suggestions'],
- "default suggestion #1"
+ 'default suggestion #1'
);
}
- public function testHighScoringSequence()
+ public function testHighScoringSequence(): void
{
$match = new Bruteforce('a', 0, 1, 'a');
$feedback = $this->feedback->getFeedback(3, [$match]);
- $this->assertSame('', $feedback['warning'], "no warning for good score");
- $this->assertEmpty($feedback['suggestions'], "no suggestions for good score");
+ $this->assertSame('', $feedback['warning'], 'no warning for good score');
+ $this->assertEmpty($feedback['suggestions'], 'no suggestions for good score');
}
- public function testLongestMatchGetsFeedback()
+ public function testLongestMatchGetsFeedback(): void
{
$match1 = new SequenceMatch('abcd26-01-1991', 0, 4, 'abcd');
$match2 = new DateMatch('abcd26-01-1991', 4, 14, '26-01-1991', [
- 'day' => 26,
- 'month' => 1,
- 'year' => 1991,
+ 'day' => 26,
+ 'month' => 1,
+ 'year' => 1991,
'separator' => '-',
]);
$feedback = $this->feedback->getFeedback(1, [$match1, $match2]);
@@ -60,26 +59,26 @@ public function testLongestMatchGetsFeedback()
$this->assertSame(
'Dates are often easy to guess',
$feedback['warning'],
- "warning provided for the longest match"
+ 'warning provided for the longest match'
);
$this->assertContains(
'Avoid dates and years that are associated with you',
$feedback['suggestions'],
- "suggestion provided for the longest match"
+ 'suggestion provided for the longest match'
);
$this->assertNotContains(
'Avoid sequences',
$feedback['suggestions'],
- "no suggestion provided for the shorter match"
+ 'no suggestion provided for the shorter match'
);
}
- public function testDefaultSuggestion()
+ public function testDefaultSuggestion(): void
{
$match = new DateMatch('26-01-1991', 0, 10, '26-01-1991', [
- 'day' => 26,
- 'month' => 1,
- 'year' => 1991,
+ 'day' => 26,
+ 'month' => 1,
+ 'year' => 1991,
'separator' => '-',
]);
$feedback = $this->feedback->getFeedback(1, [$match]);
@@ -87,21 +86,21 @@ public function testDefaultSuggestion()
$this->assertContains(
'Add another word or two. Uncommon words are better.',
$feedback['suggestions'],
- "default suggestion provided"
+ 'default suggestion provided'
);
$this->assertCount(2, $feedback['suggestions'], "default suggestion doesn\'t override existing suggestion");
}
- public function testBruteforceFeedback()
+ public function testBruteforceFeedback(): void
{
$match = new Bruteforce('qkcriv', 0, 6, 'qkcriv');
$feedback = $this->feedback->getFeedback(1, [$match]);
- $this->assertSame('', $feedback['warning'], "bruteforce match has no warning");
+ $this->assertSame('', $feedback['warning'], 'bruteforce match has no warning');
$this->assertSame(
['Add another word or two. Uncommon words are better.'],
$feedback['suggestions'],
- "bruteforce match only has the default suggestion"
+ 'bruteforce match only has the default suggestion'
);
}
}
diff --git a/test/MatcherTest.php b/test/MatcherTest.php
index 80c4c04..329ec12 100644
--- a/test/MatcherTest.php
+++ b/test/MatcherTest.php
@@ -4,34 +4,36 @@
namespace ZxcvbnPhp\Test;
+use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use ZxcvbnPhp\Matcher;
use ZxcvbnPhp\Matchers\Bruteforce;
use ZxcvbnPhp\Matchers\DictionaryMatch;
+use ZxcvbnPhp\Matchers\RepeatMatch;
-/**
- * @covers \ZxcvbnPhp\Matcher
- */
+#[CoversClass(Matcher::class)]
class MatcherTest extends TestCase
{
- public function testGetMatches()
+ public function testGetMatches(): void
{
$matcher = new Matcher();
$matches = $matcher->getMatches('jjj');
+ $this->assertInstanceOf(RepeatMatch::class, $matches[0]);
$this->assertSame('repeat', $matches[0]->pattern, 'Pattern incorrect');
$this->assertCount(1, $matches);
$matches = $matcher->getMatches('jjjjj');
+ $this->assertInstanceOf(RepeatMatch::class, $matches[0]);
$this->assertSame('repeat', $matches[0]->pattern, 'Pattern incorrect');
}
- public function testEmptyString()
+ public function testEmptyString(): void
{
$matcher = new Matcher();
$this->assertEmpty($matcher->getMatches(''), "doesn't match ''");
}
- public function testMultiplePatterns()
+ public function testMultiplePatterns(): void
{
$matcher = new Matcher();
$password = 'r0sebudmaelstrom11/20/91aaaa';
@@ -40,7 +42,7 @@ public function testMultiplePatterns()
['dictionary', [ 0, 6]],
['dictionary', [ 7, 15]],
['date', [16, 23]],
- ['repeat', [24, 27]]
+ ['repeat', [24, 27]],
];
$matches = $matcher->getMatches($password);
@@ -51,33 +53,32 @@ public function testMultiplePatterns()
}
}
- $this->assertEmpty($expectedMatches, "matches multiple patterns");
+ $this->assertEmpty($expectedMatches, 'matches multiple patterns');
}
/**
* There's a similar test in DictionaryTest for this as well, but this specific test is for ensuring that the
* user input gets passed from the Matcher class through to DictionaryMatch function.
*/
- public function testUserDefinedWords()
+ public function testUserDefinedWords(): void
{
$matcher = new Matcher();
$matches = $matcher->getMatches('_wQbgL491', ['PJnD', 'WQBG', 'ZhwZ']);
- $this->assertInstanceOf(DictionaryMatch::class, $matches[0], "user input match is correct class");
- $this->assertSame('wQbg', $matches[0]->token, "user input match has correct token");
+ $this->assertInstanceOf(DictionaryMatch::class, $matches[0], 'user input match is correct class');
+ $this->assertSame('wQbg', $matches[0]->token, 'user input match has correct token');
}
- public function testAddMatcherWillThrowException()
+ public function testAddMatcherWillThrowException(): void
{
$this->expectException(\InvalidArgumentException::class);
$matcher = new Matcher();
+ // @phpstan-ignore-next-line
$matcher->addMatcher('invalid className');
-
- $this->expectNotToPerformAssertions();
}
- public function testAddMatcherWillReturnSelf()
+ public function testAddMatcherWillReturnSelf(): void
{
$matcher = new Matcher();
$result = $matcher->addMatcher(Bruteforce::class);
diff --git a/test/Matchers/AbstractMatchTest.php b/test/Matchers/AbstractMatchTest.php
index f1d6645..b7b2a65 100644
--- a/test/Matchers/AbstractMatchTest.php
+++ b/test/Matchers/AbstractMatchTest.php
@@ -5,6 +5,7 @@
namespace ZxcvbnPhp\Test\Matchers;
use PHPUnit\Framework\TestCase;
+use ZxcvbnPhp\Matchers\BaseMatch;
abstract class AbstractMatchTest extends TestCase
{
@@ -15,31 +16,31 @@ abstract class AbstractMatchTest extends TestCase
*
* @see test-matching.coffee
*
- * @param string $pattern
- * @param array $prefixes
- * @param array $suffixes
- * @return array a list of triplets [variant, i, j] where [i,j] is the start/end of the pattern, inclusive
+ * @param array $prefixes
+ * @param array $suffixes
+ *
+ * @return array a list of triplets [variant, i, j] where [i,j] is the start/end of the pattern, inclusive
*/
- protected function generatePasswords($pattern, $prefixes, $suffixes)
+ protected function generatePasswords(string $pattern, array $prefixes, array $suffixes): array
{
$output = [];
- if (!in_array('', $prefixes)) {
+ if (! in_array('', $prefixes)) {
array_unshift($prefixes, '');
}
- if (!in_array('', $suffixes)) {
+ if (! in_array('', $suffixes)) {
array_unshift($suffixes, '');
}
foreach ($prefixes as $prefix) {
foreach ($suffixes as $suffix) {
- $i = strlen($prefix);
- $j = strlen($prefix) + strlen($pattern) - 1;
+ $i = strlen((string) $prefix);
+ $j = strlen((string) $prefix) + strlen($pattern) - 1;
$output[] = [
$prefix . $pattern . $suffix,
$i,
- $j
+ $j,
];
}
}
@@ -48,54 +49,53 @@ protected function generatePasswords($pattern, $prefixes, $suffixes)
}
/**
- * [checkMatches description]
* @param string $prefix This is prepended to the message of any checks that are run
- * @param array $matches [description]
- * @param array|string $patternNames array of pattern names, or a single pattern which will be repeated
- * @param array $patterns [description]
- * @param array $ijs [description]
- * @param array $props [description]
+ * @param array $matches
+ * @param array|string $patternNames array of pattern names, or a single pattern which will be repeated
+ * @param array $patterns
+ * @param array $ijs
+ * @param array $props
*/
protected function checkMatches(
- $prefix,
- $matches,
- $patternNames,
- $patterns,
- $ijs,
- $props
- ) {
+ string $prefix,
+ array $matches,
+ array|string $patternNames,
+ array $patterns,
+ array $ijs,
+ array $props
+ ): void {
if (is_string($patternNames)) {
# shortcut: if checking for a list of the same type of patterns,
# allow passing a string 'pat' instead of array ['pat', 'pat', ...]
$patternNames = array_fill(0, count($patterns), $patternNames);
}
- $this->assertSame(
+ $this->assertCount(
count($patterns),
- count($matches),
- $prefix . ": matches.length == " . count($patterns)
+ $matches,
+ $prefix . ': matches.length == ' . count($patterns)
);
foreach ($patterns as $k => $pattern) {
$match = $matches[$k];
$patternName = $patternNames[$k];
$pattern = $patterns[$k];
- list($i, $j) = $ijs[$k];
+ [$i, $j] = $ijs[$k];
$this->assertSame(
$patternName,
$match->pattern,
- "$prefix matches[$k].pattern == '$patternName'"
+ "{$prefix} matches[{$k}].pattern == '{$patternName}'"
);
$this->assertSame(
[$i, $j],
[$match->begin, $match->end],
- "$prefix matches[$k] should have [i, j] of [$i, $j]"
+ "{$prefix} matches[{$k}] should have [i, j] of [{$i}, {$j}]"
);
$this->assertSame(
$pattern,
$match->token,
- "$prefix matches[$k].token == '$pattern'"
+ "{$prefix} matches[{$k}].token == '{$pattern}'"
);
foreach ($props as $propName => $propList) {
@@ -104,7 +104,7 @@ protected function checkMatches(
$this->assertSame(
$propList[$k],
$match->$propName,
- "$prefix matches[$k].$propName == $propMessage"
+ "{$prefix} matches[{$k}].{$propName} == {$propMessage}"
);
}
}
diff --git a/test/Matchers/BruteforceTest.php b/test/Matchers/BruteforceTest.php
index 7d5c7cd..aa39fcd 100644
--- a/test/Matchers/BruteforceTest.php
+++ b/test/Matchers/BruteforceTest.php
@@ -8,12 +8,12 @@
class BruteforceTest extends AbstractMatchTest
{
- public function testMatch()
+ public function testMatch(): void
{
$password = 'uH2nvQbugW';
$this->checkMatches(
- "matches entire string",
+ 'matches entire string',
Bruteforce::match($password),
'bruteforce',
[$password],
@@ -22,12 +22,12 @@ public function testMatch()
);
}
- public function testMultibyteMatch()
+ public function testMultibyteMatch(): void
{
$password = '中华人民共和国';
$this->checkMatches(
- "matches entire string with multibyte characters",
+ 'matches entire string with multibyte characters',
Bruteforce::match($password),
'bruteforce',
[$password],
@@ -36,17 +36,17 @@ public function testMultibyteMatch()
);
}
- public function testGuessesMax()
+ public function testGuessesMax(): void
{
$token = str_repeat('a', 1000);
$match = new Bruteforce($token, 0, 999, $token);
- $this->assertNotEquals(INF, $match->getGuesses(), "long string doesn't return infinite guesses");
+ $this->assertNotSame(INF, $match->getGuesses(), "long string doesn't return infinite guesses");
}
- public function testGuessesMultibyteCharacter()
+ public function testGuessesMultibyteCharacter(): void
{
$token = '🙂'; // smiley face emoji
$match = new Bruteforce($token, 0, 1, $token);
- $this->assertSame(11.0, $match->getGuesses(), "multibyte character treated as one character");
+ $this->assertEqualsWithDelta(11.0, $match->getGuesses(), PHP_FLOAT_EPSILON, 'multibyte character treated as one character');
}
}
diff --git a/test/Matchers/DateTest.php b/test/Matchers/DateTest.php
index 794c9c5..ec30e91 100644
--- a/test/Matchers/DateTest.php
+++ b/test/Matchers/DateTest.php
@@ -4,33 +4,30 @@
namespace ZxcvbnPhp\Test\Matchers;
+use Iterator;
+use PHPUnit\Framework\Attributes\DataProvider;
use ZxcvbnPhp\Matchers\DateMatch;
class DateTest extends AbstractMatchTest
{
- public function separatorProvider()
+ public static function separatorProvider(): Iterator
{
- return [
- [''],
- [' '],
- ['-'],
- ['/'],
- ['\\'],
- ['_'],
- ['.'],
- ];
+ yield [''];
+ yield [' '];
+ yield ['-'];
+ yield ['/'];
+ yield ['\\'];
+ yield ['_'];
+ yield ['.'];
}
- /**
- * @dataProvider separatorProvider
- * @param string $sep
- */
- public function testSeparators($sep)
+ #[DataProvider('separatorProvider')]
+ public function testSeparators(string $sep): void
{
$password = "13{$sep}2{$sep}1921";
$this->checkMatches(
- "matches dates that use '$sep' as a separator",
+ "matches dates that use '{$sep}' as a separator",
DateMatch::match($password),
'date',
[$password],
@@ -44,71 +41,64 @@ public function testSeparators($sep)
);
}
- public function testDateOrders()
+ public function testDateOrders(): void
{
- list($d, $m, $y) = [8, 8, 88];
+ [$d, $m, $y] = [8, 8, 88];
$orders = ['mdy', 'dmy', 'ymd', 'ydm'];
foreach ($orders as $order) {
$password = str_replace(
['y', 'm', 'd'],
- [$y, $m, $d],
+ [(string) $y, (string) $m, (string) $d],
$order
);
$this->checkMatches(
- "matches dates with $order format",
+ "matches dates with {$order} format",
DateMatch::match($password),
'date',
[ $password ],
[[ 0, strlen($password) - 1 ]],
[
'separator' => [''],
- 'year' => [1988],
- 'month' => [8],
- 'day' => [8],
+ 'year' => [1988],
+ 'month' => [8],
+ 'day' => [8],
]
);
}
}
- public function testMatchesClosestToReferenceYear()
+ public function testMatchesClosestToReferenceYear(): void
{
$password = '111504';
$this->checkMatches(
- "matches the date with year closest to REFERENCE_YEAR when ambiguous",
+ 'matches the date with year closest to REFERENCE_YEAR when ambiguous',
DateMatch::match($password),
'date',
[ $password ],
[[ 0, strlen($password) - 1 ]],
[
'separator' => [''],
- 'year' => [2004], // picks '04' -> 2004 as year, not '1504'
- 'month' => [11],
- 'day' => [15],
+ 'year' => [2004], // picks '04' -> 2004 as year, not '1504'
+ 'month' => [11],
+ 'day' => [15],
]
);
}
- public function normalDateProvider()
+ public static function normalDateProvider(): Iterator
{
- return [
- [1, 1, 1999],
- [11, 8, 2000],
- [9, 12, 2005],
- [22, 11, 1551]
- ];
+ yield [1, 1, 1999];
+ yield [11, 8, 2000];
+ yield [9, 12, 2005];
+ yield [22, 11, 1551];
}
- /**
- * @dataProvider normalDateProvider
- * @param int $day
- * @param int $month
- * @param int $year
- */
- public function testNormalDatesWithoutSeparator($day, $month, $year)
+ #[DataProvider('normalDateProvider')]
+ public function testNormalDatesWithoutSeparator(int $day, int $month, int $year): void
{
$password = "{$year}{$month}{$day}";
$this->checkMatches(
- "matches $password without a separator",
+ "matches {$password} without a separator",
DateMatch::match($password),
'date',
[$password],
@@ -120,17 +110,12 @@ public function testNormalDatesWithoutSeparator($day, $month, $year)
);
}
- /**
- * @dataProvider normalDateProvider
- * @param int $day
- * @param int $month
- * @param int $year
- */
- public function testNormalDatesWithSeparator($day, $month, $year)
+ #[DataProvider('normalDateProvider')]
+ public function testNormalDatesWithSeparator(int $day, int $month, int $year): void
{
$password = "{$year}.{$month}.{$day}";
$this->checkMatches(
- "matches $password with a separator",
+ "matches {$password} with a separator",
DateMatch::match($password),
'date',
[$password],
@@ -142,157 +127,157 @@ public function testNormalDatesWithSeparator($day, $month, $year)
);
}
- public function testMatchesZeroPaddedDates()
+ public function testMatchesZeroPaddedDates(): void
{
- $password = "02/02/02";
+ $password = '02/02/02';
$this->checkMatches(
- "matches zero-padded dates",
+ 'matches zero-padded dates',
DateMatch::match($password),
'date',
[ $password ],
[[ 0, strlen($password) - 1 ]],
[
'separator' => ['/'],
- 'year' => [2002],
- 'month' => [2],
- 'day' => [2],
+ 'year' => [2002],
+ 'month' => [2],
+ 'day' => [2],
]
);
}
- public function testFullDateMatched()
+ public function testFullDateMatched(): void
{
- $password = "2018-01-20";
+ $password = '2018-01-20';
$this->checkMatches(
- "matches full date and not just year",
+ 'matches full date and not just year',
DateMatch::match($password),
'date',
[ $password ],
[[ 0, strlen($password) - 1 ]],
[
'separator' => ['-'],
- 'year' => [2018],
- 'month' => [1],
- 'day' => [20],
+ 'year' => [2018],
+ 'month' => [1],
+ 'day' => [20],
]
);
}
- public function testMatchesEmbeddedDates()
+ public function testMatchesEmbeddedDates(): void
{
$prefixes = ['a', 'ab'];
$suffixes = ['!'];
$pattern = '1/1/91';
- foreach ($this->generatePasswords($pattern, $prefixes, $suffixes) as list($password, $i, $j)) {
+ foreach ($this->generatePasswords($pattern, $prefixes, $suffixes) as [$password, $i, $j]) {
$this->checkMatches(
- "matches embedded dates",
+ 'matches embedded dates',
DateMatch::match($password),
'date',
[$pattern],
[[$i, $j]],
[
- 'year' => [1991],
+ 'year' => [1991],
'month' => [1],
- 'day' => [1]
+ 'day' => [1],
]
);
}
}
- public function testMatchesOverlappingDates()
+ public function testMatchesOverlappingDates(): void
{
- $password = "12/20/1991.12.20";
+ $password = '12/20/1991.12.20';
$this->checkMatches(
- "matches overlapping dates",
+ 'matches overlapping dates',
DateMatch::match($password),
'date',
[ '12/20/1991', '1991.12.20' ],
[[ 0, 9 ], [ 6, 15 ]],
[
'separator' => ['/', '.'],
- 'year' => [1991, 1991],
- 'month' => [12, 12],
- 'day' => [20, 20],
+ 'year' => [1991, 1991],
+ 'month' => [12, 12],
+ 'day' => [20, 20],
]
);
}
- public function testMatchesDatesPadded()
+ public function testMatchesDatesPadded(): void
{
- $password = "912/20/919";
+ $password = '912/20/919';
$this->checkMatches(
- "matches dates padded by non-ambiguous digits",
+ 'matches dates padded by non-ambiguous digits',
DateMatch::match($password),
'date',
[ '12/20/91' ],
[[ 1, 8 ]],
[
'separator' => ['/'],
- 'year' => [1991],
- 'month' => [12],
- 'day' => [20],
+ 'year' => [1991],
+ 'month' => [12],
+ 'day' => [20],
]
);
}
- public function testReferenceYearImplementation()
+ public function testReferenceYearImplementation(): void
{
- $this->assertSame((int)date('Y'), DateMatch::getReferenceYear(), "reference year implementation");
+ $this->assertSame((int) date('Y'), DateMatch::getReferenceYear(), 'reference year implementation');
}
- public function testNonDateThatLooksLikeDate()
+ public function testNonDateThatLooksLikeDate(): void
{
- $this->assertEmpty(DateMatch::match('30-31-00'), "no match on invalid date");
+ $this->assertEmpty(DateMatch::match('30-31-00'), 'no match on invalid date');
}
- public function testGuessDistanceFromReferenceYear()
+ public function testGuessDistanceFromReferenceYear(): void
{
$token = '1123';
$match = new DateMatch($token, 0, strlen($token) - 1, $token, [
'separator' => '',
'year' => 1923,
'month' => 1,
- 'day' => 1
+ 'day' => 1,
]);
$expected = 365.0 * abs(DateMatch::getReferenceYear() - $match->year);
$this->assertSame(
$expected,
$match->getGuesses(),
- "guesses for $token is 365 * distance_from_ref_year"
+ "guesses for {$token} is 365 * distance_from_ref_year"
);
}
- public function testGuessMinYearSpace()
+ public function testGuessMinYearSpace(): void
{
$token = '112010';
$match = new DateMatch($token, 0, strlen($token) - 1, $token, [
'separator' => '',
'year' => 2010,
'month' => 1,
- 'day' => 1
+ 'day' => 1,
]);
$expected = 7300.0; // 365 * DateMatch::MIN_YEAR_SPACE;
- $this->assertSame($expected, $match->getGuesses(), "recent years assume MIN_YEAR_SPACE");
+ $this->assertSame($expected, $match->getGuesses(), 'recent years assume MIN_YEAR_SPACE');
}
- public function testGuessWithSeparator()
+ public function testGuessWithSeparator(): void
{
$token = '1/1/2010';
$match = new DateMatch($token, 0, strlen($token) - 1, $token, [
'separator' => '/',
'year' => 2010,
'month' => 1,
- 'day' => 1
+ 'day' => 1,
]);
$expected = 29200.0; // 365 * DateMatch::MIN_YEAR_SPACE * 4;
- $this->assertSame($expected, $match->getGuesses(), "extra guesses are added for separators");
+ $this->assertSame($expected, $match->getGuesses(), 'extra guesses are added for separators');
}
- public function testFeedback()
+ public function testFeedback(): void
{
$token = '26/01/1990';
$match = new DateMatch($token, 0, strlen($token) - 1, $token, [
@@ -306,12 +291,12 @@ public function testFeedback()
$this->assertSame(
'Dates are often easy to guess',
$feedback['warning'],
- "date match gives correct warning"
+ 'date match gives correct warning'
);
$this->assertContains(
'Avoid dates and years that are associated with you',
$feedback['suggestions'],
- "date match gives correct suggestion"
+ 'date match gives correct suggestion'
);
}
}
diff --git a/test/Matchers/DictionaryTest.php b/test/Matchers/DictionaryTest.php
index 8c2804d..4f8b4b0 100644
--- a/test/Matchers/DictionaryTest.php
+++ b/test/Matchers/DictionaryTest.php
@@ -4,11 +4,16 @@
namespace ZxcvbnPhp\Test\Matchers;
+use Iterator;
+use PHPUnit\Framework\Attributes\DataProvider;
use ZxcvbnPhp\Matchers\DictionaryMatch;
class DictionaryTest extends AbstractMatchTest
{
- protected static $testDicts = [
+ /**
+ * @var array
+ */
+ protected static array $testDicts = [
'd1' => [
'motherboard' => 1,
'mother' => 2,
@@ -21,29 +26,24 @@ class DictionaryTest extends AbstractMatchTest
'8' => 2,
'99' => 3,
'$' => 4,
- 'asdf1234&*' => 5,
+ 'asdf1234&*' => 5,
],
];
/**
- * @return string[][]
+ * @return Iterator
*/
- public function madeUpWordsProvider(): array
+ public static function madeUpWordsProvider(): Iterator
{
- return [
- ['jjj'],
- ['kdncpqw'],
- ];
+ yield ['jjj'];
+ yield ['kdncpqw'];
}
- /**
- * @dataProvider madeUpWordsProvider
- * @param string $password
- */
+ #[DataProvider('madeUpWordsProvider')]
public function testWordsNotInDictionary(string $password): void
{
$matches = DictionaryMatch::match($password);
- $this->assertEmpty($matches, "does not match non-dictionary words");
+ $this->assertEmpty($matches, 'does not match non-dictionary words');
}
public function testContainingWords(): void
@@ -52,7 +52,7 @@ public function testContainingWords(): void
$patterns = ['mother', 'motherboard', 'board'];
$this->checkMatches(
- "matches words that contain other words: $password",
+ "matches words that contain other words: {$password}",
DictionaryMatch::match($password, [], self::$testDicts),
'dictionary',
$patterns,
@@ -71,7 +71,7 @@ public function testOverlappingWords(): void
$patterns = ['abcd', 'cdef'];
$this->checkMatches(
- "matches multiple words when they overlap",
+ 'matches multiple words when they overlap',
DictionaryMatch::match($password, [], self::$testDicts),
'dictionary',
$patterns,
@@ -90,7 +90,7 @@ public function testUppercasingIgnored(): void
$patterns = ['BoaRd', 'Z'];
$this->checkMatches(
- "ignores uppercasing",
+ 'ignores uppercasing',
DictionaryMatch::match($password, [], self::$testDicts),
'dictionary',
$patterns,
@@ -111,7 +111,7 @@ public function testWordsSurroundedByNonWords(): void
foreach ($this->generatePasswords($pattern, $prefixes, $suffixes) as [$password, $i, $j]) {
$this->checkMatches(
- "identifies words surrounded by non-words",
+ 'identifies words surrounded by non-words',
DictionaryMatch::match($password, [], self::$testDicts),
'dictionary',
[$pattern],
@@ -129,14 +129,14 @@ public function testAllDictionaryWords(): void
{
foreach (self::$testDicts as $dictionaryName => $dict) {
foreach ($dict as $word => $rank) {
- $word = (string)$word;
+ $word = (string) $word;
if ($word === 'motherboard') {
continue; // skip words that contain others
}
$this->checkMatches(
- "matches against all words in provided dictionaries",
+ 'matches against all words in provided dictionaries',
DictionaryMatch::match($word, [], self::$testDicts),
'dictionary',
[$word],
@@ -157,7 +157,7 @@ public function testDefaultDictionary(): void
$patterns = [$password];
$this->checkMatches(
- "default dictionaries",
+ 'default dictionaries',
DictionaryMatch::match($password),
'dictionary',
$patterns,
@@ -176,12 +176,10 @@ public function testUserProvidedInput(): void
$patterns = ['foo', 'bar'];
$matches = DictionaryMatch::match($password, ['foo', 'bar']);
- $matches = array_values(array_filter($matches, function ($match) {
- return $match->dictionaryName === 'user_inputs';
- }));
+ $matches = array_values(array_filter($matches, static fn ($match) => $match->dictionaryName === 'user_inputs'));
$this->checkMatches(
- "matches with provided user input dictionary",
+ 'matches with provided user input dictionary',
$matches,
'dictionary',
$patterns,
@@ -197,7 +195,7 @@ public function testUserProvidedInputInNoOtherDictionary(): void
{
$password = '39kx9.1x0!3n6';
$this->checkMatches(
- "matches with provided user input dictionary",
+ 'matches with provided user input dictionary',
DictionaryMatch::match($password, [$password]),
'dictionary',
[$password],
@@ -213,13 +211,13 @@ public function testMatchesInMultipleDictionaries(): void
{
$password = 'pass';
$this->checkMatches(
- "matches words in multiple dictionaries",
+ 'matches words in multiple dictionaries',
DictionaryMatch::match($password),
'dictionary',
['pass', 'as', 'ass'],
[[0, 3], [1, 2], [1, 3]],
[
- 'dictionaryName' => ['passwords', 'english_wikipedia', 'us_tv_and_film']
+ 'dictionaryName' => ['passwords', 'english_wikipedia', 'us_tv_and_film'],
]
);
}
@@ -227,49 +225,47 @@ public function testMatchesInMultipleDictionaries(): void
public function testGuessesBaseRank(): void
{
$match = new DictionaryMatch('aaaaa', 0, 5, 'aaaaaa', ['rank' => 32]);
- $this->assertSame(32.0, $match->getGuesses(), "base guesses == the rank");
+ $this->assertEqualsWithDelta(32.0, $match->getGuesses(), PHP_FLOAT_EPSILON, 'base guesses == the rank');
}
public function testGuessesCapitalization(): void
{
$match = new DictionaryMatch('AAAaaa', 0, 5, 'AAAaaa', ['rank' => 32]);
$expected = 32.0 * 41; // rank * uppercase variations
- $this->assertSame($expected, $match->getGuesses(), "extra guesses are added for capitalization");
+ $this->assertSame($expected, $match->getGuesses(), 'extra guesses are added for capitalization');
}
/**
- * @return array[]
+ * @return Iterator
*/
- public function uppercaseVariationProvider(): array
+ public static function uppercaseVariationProvider(): Iterator
{
- return [
- [ '', 1 ],
- [ 'a', 1 ],
- [ 'A', 2 ],
- [ 'abcdef', 1 ],
- [ 'Abcdef', 2 ],
- [ 'abcdeF', 2 ],
- [ 'ABCDEF', 2 ],
- [ 'aBcdef', 6 ], // 6 choose 1
- [ 'aBcDef', 21 ], // 6 choose 1 + 6 choose 2
- [ 'ABCDEf', 6 ], // 6 choose 1
- [ 'aBCDEf', 21 ], // 6 choose 1 + 6 choose 2
- [ 'ABCdef', 41 ], // 6 choose 1 + 6 choose 2 + 6 choose 3
- ];
+ yield [ '', 1 ];
+ yield [ 'a', 1 ];
+ yield [ 'A', 2 ];
+ yield [ 'abcdef', 1 ];
+ yield [ 'Abcdef', 2 ];
+ yield [ 'abcdeF', 2 ];
+ yield [ 'ABCDEF', 2 ];
+ yield [ 'aBcdef', 6 ];
+ // 6 choose 1
+ yield [ 'aBcDef', 21 ];
+ // 6 choose 1 + 6 choose 2
+ yield [ 'ABCDEf', 6 ];
+ // 6 choose 1
+ yield [ 'aBCDEf', 21 ];
+ // 6 choose 1 + 6 choose 2
+ yield [ 'ABCdef', 41 ];
}
- /**
- * @dataProvider uppercaseVariationProvider
- * @param string $token
- * @param float $expectedGuesses
- */
+ #[DataProvider('uppercaseVariationProvider')]
public function testGuessesUppercaseVariations(string $token, float $expectedGuesses): void
{
$match = new DictionaryMatch($token, 0, strlen($token) - 1, $token, ['rank' => 1]);
$this->assertSame(
$expectedGuesses,
$match->getGuesses(),
- "guess multiplier of $token is $expectedGuesses"
+ "guess multiplier of {$token} is {$expectedGuesses}"
);
}
@@ -279,7 +275,7 @@ public function testFeedbackTop10Password(): void
$this->assertSame(
'This is a top-10 common password',
$feedback['warning'],
- "dictionary match warns about top-10 password"
+ 'dictionary match warns about top-10 password'
);
}
@@ -289,7 +285,7 @@ public function testFeedbackTop100Password(): void
$this->assertSame(
'This is a top-100 common password',
$feedback['warning'],
- "dictionary match warns about top-100 password"
+ 'dictionary match warns about top-100 password'
);
}
@@ -299,7 +295,7 @@ public function testFeedbackTopPasswordSoleMatch(): void
$this->assertSame(
'This is a very common password',
$feedback['warning'],
- "dictionary match warns about common password"
+ 'dictionary match warns about common password'
);
}
@@ -309,7 +305,7 @@ public function testFeedbackTopPasswordNotSoleMatch(): void
$this->assertSame(
'This is similar to a commonly used password',
$feedback['warning'],
- "dictionary match warns about common password (not a sole match)"
+ 'dictionary match warns about common password (not a sole match)'
);
}
@@ -319,7 +315,7 @@ public function testFeedbackTopPasswordNotSoleMatchRankTooLow(): void
$this->assertSame(
'',
$feedback['warning'],
- "no warning for a non-sole match in the password dictionary"
+ 'no warning for a non-sole match in the password dictionary'
);
}
@@ -329,7 +325,7 @@ public function testFeedbackWikipediaWordSoleMatch(): void
$this->assertSame(
'A word by itself is easy to guess',
$feedback['warning'],
- "dictionary match warns about Wikipedia word (sole match)"
+ 'dictionary match warns about Wikipedia word (sole match)'
);
}
@@ -349,7 +345,7 @@ public function testFeedbackNameSoleMatch(): void
$this->assertSame(
'Names and surnames by themselves are easy to guess',
$feedback['warning'],
- "dictionary match warns about surname (sole match)"
+ 'dictionary match warns about surname (sole match)'
);
}
@@ -359,7 +355,7 @@ public function testFeedbackNameNonSoleMatch(): void
$this->assertSame(
'Common names and surnames are easy to guess',
$feedback['warning'],
- "dictionary match warns about surname (not a sole match)"
+ 'dictionary match warns about surname (not a sole match)'
);
}
@@ -369,7 +365,7 @@ public function testFeedbackTvAndFilmDictionary(): void
$this->assertSame(
'',
$feedback['warning'],
- "no warning for match from us_tv_and_film dictionary"
+ 'no warning for match from us_tv_and_film dictionary'
);
}
@@ -379,7 +375,7 @@ public function testFeedbackAllUppercaseWord(): void
$this->assertContains(
'All-uppercase is almost as easy to guess as all-lowercase',
$feedback['suggestions'],
- "dictionary match gives suggestion for all-uppercase word"
+ 'dictionary match gives suggestion for all-uppercase word'
);
}
@@ -389,22 +385,18 @@ public function testFeedbackWordStartsWithUppercase(): void
$this->assertContains(
'Capitalization doesn\'t help very much',
$feedback['suggestions'],
- "dictionary match gives suggestion for word starting with uppercase"
+ 'dictionary match gives suggestion for word starting with uppercase'
);
}
/**
- * @param string $token
- * @param string $dictionary
- * @param int $rank
- * @param bool $soleMatch
- * @return array
+ * @return array
*/
private function getFeedbackForToken(string $token, string $dictionary, int $rank, bool $soleMatch): array
{
$match = new DictionaryMatch($token, 0, strlen($token) - 1, $token, [
'dictionary_name' => $dictionary,
- 'rank' => $rank
+ 'rank' => $rank,
]);
return $match->getFeedback($soleMatch);
}
diff --git a/test/Matchers/L33tTest.php b/test/Matchers/L33tTest.php
index 509462e..2fac695 100644
--- a/test/Matchers/L33tTest.php
+++ b/test/Matchers/L33tTest.php
@@ -4,46 +4,40 @@
namespace ZxcvbnPhp\Test\Matchers;
+use Iterator;
+use PHPUnit\Framework\Attributes\DataProvider;
use ReflectionClass;
use ZxcvbnPhp\Matchers\L33tMatch;
-use ZxcvbnPhp\Matchers\BaseMatch;
class L33tTest extends AbstractMatchTest
{
- protected $testTable = [
+ /**
+ * @var array
+ */
+ protected array $testTable = [
'a' => ['4', '@'],
'c' => ['(', '{', '[', '<'],
'g' => ['6', '9'],
'o' => ['0'],
];
- // Generally we only need to test the public interface of the matchers, but it can be useful
- // to occasionally test protected methods to ensure consistency with upstream.
- protected static function callProtectedMethod(string $name, array $args)
- {
- $class = new ReflectionClass('\\ZxcvbnPhp\\Test\\Matchers\\MockL33tMatch');
- $method = $class->getMethod($name);
- $method->setAccessible(true);
- return $method->invokeArgs(null, $args);
- }
-
public function testReducesL33tTable(): void
{
$cases = [
- '' => [] ,
- 'abcdefgo123578!#$&*)]}>' => [] ,
- 'a' => [] ,
- '4' => [
- 'a' => ['4']
+ '' => [],
+ 'abcdefgo123578!#$&*)]}>' => [],
+ 'a' => [],
+ '4' => [
+ 'a' => ['4'],
],
- '4@' => [
- 'a' => ['4', '@']
+ '4@' => [
+ 'a' => ['4', '@'],
],
'4({60' => [
'a' => ['4'],
- 'c' => ['(','{'],
+ 'c' => ['(','{'],
'g' => ['6'],
- 'o' => ['0']
+ 'o' => ['0'],
],
];
@@ -51,7 +45,7 @@ public function testReducesL33tTable(): void
$this->assertSame(
$expected,
static::callProtectedMethod('getL33tSubtable', [$pw]),
- "reduces l33t table to only the substitutions that a password might be employing"
+ 'reduces l33t table to only the substitutions that a password might be employing'
);
}
}
@@ -61,25 +55,27 @@ public function testEnumeratesL33tSubstitutions(): void
$cases = [
[
[],
- [[]]
+ [[]],
],
[
['a' => ['@']], // subtable
- [['@' => 'a']] ], // expected result
+ [['@' => 'a']],
+ ], // expected result
[
['a' => ['@', '4']],
- [['@' => 'a'], ['4' => 'a']] ],
+ [['@' => 'a'], ['4' => 'a']],
+ ],
[
['a' => ['@', '4'], 'c' => ['(']],
- [['@' => 'a', '(' => 'c'], ['4' => 'a', '(' => 'c']]
- ]
+ [['@' => 'a', '(' => 'c'], ['4' => 'a', '(' => 'c']],
+ ],
];
foreach ($cases as $case) {
$this->assertSame(
$case[1],
static::callProtectedMethod('getL33tSubstitutions', [$case[0]]),
- "enumerates the different sets of l33t substitutions a password might be using"
+ 'enumerates the different sets of l33t substitutions a password might be using'
);
}
}
@@ -120,63 +116,59 @@ public function testCapitalizedDictionaryWordsWithL33tCharactersAfter(): void
);
}
- public function commonCaseProvider(): array
+ /**
+ * @return Iterator
+ */
+ public static function commonCaseProvider(): Iterator
{
- return [
- [
- 'password' => 'p4ssword',
- 'pattern' => 'p4ssword',
- 'word' => 'password',
- 'dictionary_name' => 'words',
- 'rank' => 3,
- 'ij' => [0, 7],
- 'sub' => ['4' => 'a']
- ],
- [
- 'password' => 'p@ssw0rd',
- 'pattern' => 'p@ssw0rd',
- 'word' => 'password',
- 'dictionary_name' => 'words',
- 'rank' => 3,
- 'ij' => [0, 7],
- 'sub' => ['@' => 'a', '0' => 'o']
- ],
- [
- 'password' => 'aSdfO{G0asDfO',
- 'pattern' => '{G0',
- 'word' => 'cgo',
- 'dictionary_name' => 'words2',
- 'rank' => 1,
- 'ij' => [5, 7],
- 'sub' => ['{' => 'c', '0' => 'o']
- ],
+ yield [
+ 'password' => 'p4ssword',
+ 'pattern' => 'p4ssword',
+ 'word' => 'password',
+ 'dictionary' => 'words',
+ 'rank' => 3,
+ 'ij' => [0, 7],
+ 'sub' => ['4' => 'a'],
+ ];
+ yield [
+ 'password' => 'p@ssw0rd',
+ 'pattern' => 'p@ssw0rd',
+ 'word' => 'password',
+ 'dictionary' => 'words',
+ 'rank' => 3,
+ 'ij' => [0, 7],
+ 'sub' => ['@' => 'a', '0' => 'o'],
+ ];
+ yield [
+ 'password' => 'aSdfO{G0asDfO',
+ 'pattern' => '{G0',
+ 'word' => 'cgo',
+ 'dictionary' => 'words2',
+ 'rank' => 1,
+ 'ij' => [5, 7],
+ 'sub' => ['{' => 'c', '0' => 'o'],
];
}
/**
- * @dataProvider commonCaseProvider
- * @param string $password
- * @param string $pattern
- * @param string $word
- * @param string $dictionary
- * @param int $rank
- * @param int[] $ij
- * @param array $substitutions
+ * @param array $ij
+ * @param array $sub
*/
- public function testCommonL33tSubstitutions(string $password, string $pattern, string $word, string $dictionary, int $rank, array $ij, array $substitutions): void
+ #[DataProvider('commonCaseProvider')]
+ public function testCommonL33tSubstitutions(string $password, string $pattern, string $word, string $dictionary, int $rank, array $ij, array $sub): void
{
$this->checkMatches(
- "matches against common l33t substitutions",
+ 'matches against common l33t substitutions',
MockL33tMatch::match($password),
'dictionary',
[$pattern],
[$ij],
[
'l33t' => [true],
- 'sub' => [$substitutions],
+ 'sub' => [$sub],
'matchedWord' => [$word],
'rank' => [$rank],
- 'dictionaryName' => [$dictionary]
+ 'dictionaryName' => [$dictionary],
]
);
}
@@ -184,20 +176,20 @@ public function testCommonL33tSubstitutions(string $password, string $pattern, s
public function testOverlappingL33tPatterns(): void
{
$this->checkMatches(
- "matches against overlapping l33t patterns",
+ 'matches against overlapping l33t patterns',
MockL33tMatch::match('@a(go{G0'),
'dictionary',
['@a(', '(go', '{G0'],
[[0,2], [2,4], [5,7]],
[
- 'l33t' => [true, true, true],
- 'sub' => [
- ['@' => 'a', '(' => 'c'],
- ['(' => 'c'],
- ['{' => 'c', '0' => 'o']
- ],
- 'matchedWord' => ['aac', 'cgo', 'cgo'],
- 'rank' => [1, 1, 1],
+ 'l33t' => [true, true, true],
+ 'sub' => [
+ ['@' => 'a', '(' => 'c'],
+ ['(' => 'c'],
+ ['{' => 'c', '0' => 'o'],
+ ],
+ 'matchedWord' => ['aac', 'cgo', 'cgo'],
+ 'rank' => [1, 1, 1],
'dictionaryName' => ['words', 'words2', 'words2'],
]
);
@@ -245,82 +237,82 @@ public function testSubstitutionSubsets(): void
* The character '1' can map to both 'i' and 'l' - there was previously a bug that prevented it from matching
* against the latter
*/
- public function testSubstitutionOfCharacterL()
+ public function testSubstitutionOfCharacterL(): void
{
$this->checkMatches(
- "matches against overlapping l33t patterns",
+ 'matches against overlapping l33t patterns',
L33tMatch::match('marie1'),
'dictionary',
['marie1', 'arie1'],
[[0,5], [1,5]],
[
- 'l33t' => [true, true],
- 'sub' => [['1' => 'l'], ['1' => 'l'],],
- 'matchedWord' => ['mariel', 'ariel'],
+ 'l33t' => [true, true],
+ 'sub' => [['1' => 'l'], ['1' => 'l']],
+ 'matchedWord' => ['mariel', 'ariel'],
]
);
}
- public function testGuessesL33t()
+ public function testGuessesL33t(): void
{
$match = new L33tMatch('aaa@@@', 0, 5, 'aaa@@@', [
'rank' => 32,
- 'sub' => array('@' => 'a')
+ 'sub' => ['@' => 'a'],
]);
$expected = 32.0 * 41; // rank * l33t variations
- $this->assertSame($expected, $match->getGuesses(), "guesses are doubled when word is reversed");
+ $this->assertSame($expected, $match->getGuesses(), 'guesses are doubled when word is reversed');
}
- public function testGuessesL33tAndUppercased()
+ public function testGuessesL33tAndUppercased(): void
{
$match = new L33tMatch('AaA@@@', 0, 5, 'AaA@@@', [
'rank' => 32,
- 'sub' => ['@' => 'a']
+ 'sub' => ['@' => 'a'],
]);
$expected = 32.0 * 41 * 3; // rank * l33t variations * uppercase variations
$this->assertSame(
$expected,
$match->getGuesses(),
- "extra guesses are added for both capitalization and common l33t substitutions"
+ 'extra guesses are added for both capitalization and common l33t substitutions'
);
}
- public function variationsProvider(): array
+ /**
+ * @return Iterator
+ */
+ public static function variationsProvider(): Iterator
{
- return [
- [ '', 1, [] ],
- [ 'a', 1, [] ],
- [ '4', 2, ['4' => 'a'] ],
- [ '4pple', 2, ['4' => 'a'] ],
- [ 'abcet', 1, [] ],
- [ '4bcet', 2, ['4' => 'a'] ],
- [ 'a8cet', 2, ['8' => 'b'] ],
- [ 'abce+', 2, ['+' => 't'] ],
- [ '48cet', 4, ['4' => 'a', '8' => 'b'] ],
- ['a4a4aa', /* binom(6, 2) */ 15 + /* binom(6, 1) */ 6, ['4' => 'a']],
- ['4a4a44', /* binom(6, 2) */ 15 + /* binom(6, 1) */ 6, ['4' => 'a']],
- ['a44att+', (/* binom(4, 2) */ 6 + /* binom(4, 1) */ 4) * /* binom(3, 1) */ 3, ['4' => 'a', '+' => 't']],
- ];
+ yield [ '', 1, [] ];
+ yield [ 'a', 1, [] ];
+ yield [ '4', 2, ['4' => 'a'] ];
+ yield [ '4pple', 2, ['4' => 'a'] ];
+ yield [ 'abcet', 1, [] ];
+ yield [ '4bcet', 2, ['4' => 'a'] ];
+ yield [ 'a8cet', 2, ['8' => 'b'] ];
+ yield [ 'abce+', 2, ['+' => 't'] ];
+ yield [ '48cet', 4, ['4' => 'a', '8' => 'b'] ];
+ yield ['a4a4aa', /* binom(6, 2) */ 15 + /* binom(6, 1) */ 6, ['4' => 'a']];
+ yield ['4a4a44', /* binom(6, 2) */ 15 + /* binom(6, 1) */ 6, ['4' => 'a']];
+ yield ['a44att+', (/* binom(4, 2) */ 6 + /* binom(4, 1) */ 4) * /* binom(3, 1) */ 3, ['4' => 'a', '+' => 't']];
}
/**
- * @dataProvider variationsProvider
- * @param string $token
- * @param float $expectedGuesses
- * @param array $substitutions
+ * @param array $substitutions
*/
+ #[DataProvider('variationsProvider')]
public function testGuessesL33tVariations(string $token, float $expectedGuesses, array $substitutions): void
{
$match = new L33tMatch($token, 0, strlen($token) - 1, $token, ['rank' => 1, 'sub' => $substitutions]);
$this->assertSame(
$expectedGuesses,
$match->getGuesses(),
- "extra l33t guesses of $token is $expectedGuesses"
+ "extra l33t guesses of {$token} is {$expectedGuesses}"
);
}
/**
* This test is not strictly needed as it's testing an internal detail, but it's included to match an upstream test.
+ *
* @link https://github.com/dropbox/zxcvbn/blob/master/test/test-scoring.coffee#L357
*/
public function testCapitalisationNotAffectingL33t(): void
@@ -356,7 +348,7 @@ public function testFeedback(): void
$this->assertContains(
'Predictable substitutions like \'@\' instead of \'a\' don\'t help very much',
$feedback['suggestions'],
- "l33t match gives correct suggestion"
+ 'l33t match gives correct suggestion'
);
}
@@ -376,4 +368,20 @@ public function testFeedbackTop100Password(): void
"l33t match doesn't give top-100 warning"
);
}
+
+ /**
+ * Generally we only need to test the public interface of the matchers, but it can be useful
+ * to occasionally test protected methods to ensure consistency with upstream.
+ *
+ * @param array $args
+ *
+ * @return array
+ */
+ protected static function callProtectedMethod(string $name, array $args): array
+ {
+ $class = new ReflectionClass(MockL33tMatch::class);
+ $method = $class->getMethod($name);
+ $method->setAccessible(true);
+ return $method->invokeArgs(null, $args);
+ }
}
diff --git a/test/Matchers/MockL33tMatch.php b/test/Matchers/MockL33tMatch.php
index 5474dd3..d6ac517 100644
--- a/test/Matchers/MockL33tMatch.php
+++ b/test/Matchers/MockL33tMatch.php
@@ -8,6 +8,9 @@
class MockL33tMatch extends L33tMatch
{
+ /**
+ * @return array
+ */
protected static function getRankedDictionaries(): array
{
return [
@@ -19,10 +22,13 @@ protected static function getRankedDictionaries(): array
],
'words2' => [
'cgo' => 1,
- ]
+ ],
];
}
+ /**
+ * @return array
+ */
protected static function getL33tTable(): array
{
return [
diff --git a/test/Matchers/MockMatch.php b/test/Matchers/MockMatch.php
index 869b7e6..18bfcf3 100644
--- a/test/Matchers/MockMatch.php
+++ b/test/Matchers/MockMatch.php
@@ -8,20 +8,18 @@
class MockMatch extends BaseMatch
{
- /** @var float */
- protected $guesses;
-
- public function __construct(int $begin, int $end, float $guesses)
+ public function __construct(int $begin, int $end, protected float $guesses)
{
parent::__construct('', $begin, $end, '');
- $this->guesses = $guesses;
}
/**
* Get feedback to a user based on the match.
+ *
* @param bool $isSoleMatch
* Whether this is the only match in the password
- * @return array{'warning': string, "suggestions": string[]}
+ *
+ * @return array{warning: string, suggestions: array}
*/
public function getFeedback(bool $isSoleMatch): array
{
@@ -41,12 +39,15 @@ public function getRawGuesses(): float
*
* @param string $password
* Password to check for match.
- * @param array $userInputs
+ * @param array $userInputs
* Array of values related to the user (optional).
+ *
* @code
* array('Alice Smith')
+ *
* @endcode
- * @return array
+ *
+ * @return array
* Array of Match objects
*/
public static function match(string $password, array $userInputs = []): array
diff --git a/test/Matchers/RepeatTest.php b/test/Matchers/RepeatTest.php
index 9d5ba01..de9e266 100644
--- a/test/Matchers/RepeatTest.php
+++ b/test/Matchers/RepeatTest.php
@@ -4,28 +4,28 @@
namespace ZxcvbnPhp\Test\Matchers;
+use Iterator;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\DataProvider;
use ZxcvbnPhp\Matcher;
-use ZxcvbnPhp\Matchers\Bruteforce;
use ZxcvbnPhp\Matchers\RepeatMatch;
use ZxcvbnPhp\Matchers\SequenceMatch;
use ZxcvbnPhp\Scorer;
-/**
- * @covers \ZxcvbnPhp\Matchers\RepeatMatch
- */
+#[CoversClass(RepeatMatch::class)]
class RepeatTest extends AbstractMatchTest
{
- public function testEmpty()
+ public function testEmpty(): void
{
foreach (['', '#'] as $password) {
$this->assertEmpty(
RepeatMatch::match($password),
- "doesn't match length-" . strlen($password) . " repeat patterns"
+ "doesn't match length-" . strlen($password) . ' repeat patterns'
);
}
}
- public function testSingleCharacterEmbeddedRepeats()
+ public function testSingleCharacterEmbeddedRepeats(): void
{
$prefixes = ['@', 'y4@'];
$suffixes = ['u', 'u%7'];
@@ -33,7 +33,7 @@ public function testSingleCharacterEmbeddedRepeats()
foreach ($this->generatePasswords($pattern, $prefixes, $suffixes) as [$password, $i, $j]) {
$this->checkMatches(
- "matches embedded repeat patterns",
+ 'matches embedded repeat patterns',
RepeatMatch::match($password),
'repeat',
[$pattern],
@@ -46,33 +46,33 @@ public function testSingleCharacterEmbeddedRepeats()
}
}
- public function testSingleCharacterRepeats()
+ public function testSingleCharacterRepeats(): void
{
foreach ([3, 12] as $length) {
foreach (['a', 'Z', '4', '&'] as $chr) {
$pattern = str_repeat($chr, $length);
$this->checkMatches(
- "matches repeats with base character '$chr'",
+ "matches repeats with base character '{$chr}'",
RepeatMatch::match($pattern),
'repeat',
[$pattern],
[[0, strlen($pattern) - 1]],
[
'repeatedChar' => [$chr],
- 'repeatCount' => [$length]
+ 'repeatCount' => [$length],
]
);
}
}
}
- public function testAdjacentRepeats()
+ public function testAdjacentRepeats(): void
{
$str = 'BBB1111aaaaa@@@@@@';
$patterns = ['BBB','1111','aaaaa','@@@@@@'];
$this->checkMatches(
- "matches multiple adjacent repeats",
+ 'matches multiple adjacent repeats',
RepeatMatch::match($str),
'repeat',
$patterns,
@@ -84,7 +84,7 @@ public function testAdjacentRepeats()
);
}
- public function testMultipleNonadjacentRepeeats()
+ public function testMultipleNonadjacentRepeeats(): void
{
$str = '2818BBBbzsdf1111@*&@!aaaaaEUDA@@@@@@1729';
$patterns = ['BBB','1111','aaaaa','@@@@@@'];
@@ -101,7 +101,7 @@ public function testMultipleNonadjacentRepeeats()
);
}
- public function testMultiCharacterRepeats()
+ public function testMultiCharacterRepeats(): void
{
$pattern = 'abab';
$this->checkMatches(
@@ -117,7 +117,7 @@ public function testMultiCharacterRepeats()
);
}
- public function testGreedyMultiCharacterRepeats()
+ public function testGreedyMultiCharacterRepeats(): void
{
$pattern = 'aabaab';
$this->checkMatches(
@@ -133,7 +133,7 @@ public function testGreedyMultiCharacterRepeats()
);
}
- public function testFrequentlyRepeatedMultiCharacterRepeats()
+ public function testFrequentlyRepeatedMultiCharacterRepeats(): void
{
$pattern = 'abababab';
$this->checkMatches(
@@ -149,7 +149,7 @@ public function testFrequentlyRepeatedMultiCharacterRepeats()
);
}
- public function testBaseGuesses()
+ public function testBaseGuesses(): void
{
$pattern = 'abcabc';
$this->checkMatches(
@@ -161,12 +161,12 @@ public function testBaseGuesses()
[
'repeatedChar' => ['abc'],
'repeatCount' => [2],
- 'baseGuesses' => [13.0]
+ 'baseGuesses' => [13],
]
);
}
- public function testMultibyteRepeat()
+ public function testMultibyteRepeat(): void
{
$pattern = '🙂🙂🙂';
@@ -178,12 +178,12 @@ public function testMultibyteRepeat()
[[0, 2]],
[
'repeatedChar' => ['🙂'],
- 'repeatCount' => [3]
+ 'repeatCount' => [3],
]
);
}
- public function testRepeatAfterMultibyteCharacters()
+ public function testRepeatAfterMultibyteCharacters(): void
{
$pattern = 'niñabella';
@@ -195,22 +195,22 @@ public function testRepeatAfterMultibyteCharacters()
[[7, 8]],
[
'repeatedChar' => ['l'],
- 'repeatCount' => [2]
+ 'repeatCount' => [2],
]
);
}
- public function testBaseMatches()
+ public function testBaseMatches(): void
{
$pattern = 'abcabc';
$match = RepeatMatch::match($pattern)[0];
$baseMatches = $match->baseMatches;
- $this->assertSame(1, count($baseMatches));
+ $this->assertCount(1, $baseMatches);
$this->assertInstanceOf(SequenceMatch::class, $baseMatches[0]);
}
- public function testBaseMatchesRecursive()
+ public function testBaseMatchesRecursive(): void
{
$pattern = 'mqmqmqltltltmqmqmqltltlt';
$match = RepeatMatch::match($pattern)[0];
@@ -224,7 +224,7 @@ public function testBaseMatchesRecursive()
$this->assertSame('lt', $baseMatches[1]->repeatedChar);
}
- public function testDuplicateRepeatsInPassword()
+ public function testDuplicateRepeatsInPassword(): void
{
$pattern = 'scoobydoo';
$this->checkMatches(
@@ -235,35 +235,27 @@ public function testDuplicateRepeatsInPassword()
[[2, 3], [7, 8]],
[
'repeatedChar' => ['o', 'o'],
- 'repeatCount' => [2, 2]
+ 'repeatCount' => [2, 2],
]
);
}
- public function guessesProvider()
+ public static function guessesProvider(): Iterator
{
- return array(
- [ 'aa', 'a', 2, 24],
- [ '999', '9', 3, 36],
- [ '$$$$', '$', 4, 48],
- [ 'abab', 'ab', 2, 18],
- [ 'batterystaplebatterystaplebatterystaple', 'batterystaple', 3, 85277994]
- );
+ yield [ 'aa', 'a', 2, 24];
+ yield [ '999', '9', 3, 36];
+ yield [ '$$$$', '$', 4, 48];
+ yield [ 'abab', 'ab', 2, 18];
+ yield [ 'batterystaplebatterystaplebatterystaple', 'batterystaple', 3, 85277994];
}
- /**
- * @dataProvider guessesProvider
- * @param string $token
- * @param string $repeatedChar
- * @param int $repeatCount
- * @param float $expectedGuesses
- */
+ #[DataProvider('guessesProvider')]
public function testGuesses(string $token, string $repeatedChar, int $repeatCount, float $expectedGuesses): void
{
$scorer = new Scorer();
$matcher = new Matcher();
$baseAnalysis = $scorer->getMostGuessableMatchSequence($repeatedChar, $matcher->getMatches($repeatedChar));
- $baseGuesses = $baseAnalysis['guesses'];
+ $baseGuesses = (int) $baseAnalysis['guesses'];
$match = new RepeatMatch($token, 0, strlen($token) - 1, $token, [
'repeated_char' => $repeatedChar,
@@ -271,10 +263,10 @@ public function testGuesses(string $token, string $repeatedChar, int $repeatCoun
'base_guesses' => $baseGuesses,
]);
- self::assertSame($expectedGuesses, $match->getGuesses(), "the repeat pattern {$token} has guesses of {$expectedGuesses}");
+ $this->assertSame($expectedGuesses, $match->getGuesses(), "the repeat pattern {$token} has guesses of {$expectedGuesses}");
}
- public function testFeedbackSingleCharacterRepeat()
+ public function testFeedbackSingleCharacterRepeat(): void
{
$token = 'bbbbbb';
$match = new RepeatMatch($token, 0, strlen($token) - 1, $token, [
@@ -286,16 +278,16 @@ public function testFeedbackSingleCharacterRepeat()
$this->assertSame(
'Repeats like "aaa" are easy to guess',
$feedback['warning'],
- "one repeated character gives correct warning"
+ 'one repeated character gives correct warning'
);
$this->assertContains(
'Avoid repeated words and characters',
$feedback['suggestions'],
- "one repeated character gives correct suggestion"
+ 'one repeated character gives correct suggestion'
);
}
- public function testFeedbackMultipleCharacterRepeat()
+ public function testFeedbackMultipleCharacterRepeat(): void
{
$token = 'bababa';
$match = new RepeatMatch($token, 0, strlen($token) - 1, $token, [
@@ -307,12 +299,12 @@ public function testFeedbackMultipleCharacterRepeat()
$this->assertSame(
'Repeats like "abcabcabc" are only slightly harder to guess than "abc"',
$feedback['warning'],
- "multiple repeated characters gives correct warning"
+ 'multiple repeated characters gives correct warning'
);
$this->assertContains(
'Avoid repeated words and characters',
$feedback['suggestions'],
- "multiple repeated characters gives correct suggestion"
+ 'multiple repeated characters gives correct suggestion'
);
}
}
diff --git a/test/Matchers/ReverseDictionaryTest.php b/test/Matchers/ReverseDictionaryTest.php
index a0ef9d7..4571993 100644
--- a/test/Matchers/ReverseDictionaryTest.php
+++ b/test/Matchers/ReverseDictionaryTest.php
@@ -8,7 +8,10 @@
class ReverseDictionaryTest extends AbstractMatchTest
{
- protected static $testDicts = [
+ /**
+ * @var array
+ */
+ protected static array $testDicts = [
'd1' => [
'123' => 1,
'321' => 2,
@@ -17,12 +20,12 @@ class ReverseDictionaryTest extends AbstractMatchTest
],
];
- public function testReversedDictionaryWordWithCustomDictionary()
+ public function testReversedDictionaryWordWithCustomDictionary(): void
{
$password = '0123456789';
$this->checkMatches(
- "matches against reversed words in custom dictionary",
+ 'matches against reversed words in custom dictionary',
ReverseDictionaryMatch::match($password, [], self::$testDicts),
'dictionary',
['123', '456'],
@@ -36,14 +39,14 @@ public function testReversedDictionaryWordWithCustomDictionary()
);
}
- public function testGuessesReversed()
+ public function testGuessesReversed(): void
{
$match = new ReverseDictionaryMatch('aaa', 0, 2, 'aaa', ['rank' => 32]);
$expected = 32.0 * 2; // rank * reversed
- $this->assertSame($expected, $match->getGuesses(), "guesses are doubled when word is reversed");
+ $this->assertSame($expected, $match->getGuesses(), 'guesses are doubled when word is reversed');
}
- public function testFeedback()
+ public function testFeedback(): void
{
$token = 'ytisrevinu';
$match = new ReverseDictionaryMatch($token, 0, strlen($token) - 1, $token, [
@@ -60,11 +63,11 @@ public function testFeedback()
$this->assertContains(
'Reversed words aren\'t much harder to guess',
$feedback['suggestions'],
- "reverse dictionary match gives correct suggestion"
+ 'reverse dictionary match gives correct suggestion'
);
}
- public function testFeedbackTop100Password()
+ public function testFeedbackTop100Password(): void
{
$token = 'retunh';
$match = new ReverseDictionaryMatch($token, 0, strlen($token) - 1, $token, [
@@ -80,7 +83,7 @@ public function testFeedbackTop100Password()
);
}
- public function testFeedbackShortToken()
+ public function testFeedbackShortToken(): void
{
$token = 'eht';
$match = new ReverseDictionaryMatch($token, 0, strlen($token) - 1, $token, [
@@ -92,7 +95,7 @@ public function testFeedbackShortToken()
$this->assertSame(
'A word by itself is easy to guess',
$feedback['warning'],
- "reverse dictionary match still gives warning for short token"
+ 'reverse dictionary match still gives warning for short token'
);
$this->assertNotContains(
'Reversed words aren\'t much harder to guess',
diff --git a/test/Matchers/SequenceTest.php b/test/Matchers/SequenceTest.php
index 9220092..bb3fa03 100644
--- a/test/Matchers/SequenceTest.php
+++ b/test/Matchers/SequenceTest.php
@@ -4,45 +4,44 @@
namespace ZxcvbnPhp\Test\Matchers;
+use Iterator;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\DataProvider;
use ZxcvbnPhp\Matchers\SequenceMatch;
-/**
- * @covers \ZxcvbnPhp\Matchers\SequenceMatch
- */
+#[CoversClass(SequenceMatch::class)]
class SequenceTest extends AbstractMatchTest
{
- public function shortPasswordProvider()
+ /**
+ * @return Iterator
+ */
+ public static function shortPasswordProvider(): Iterator
{
- return [
- [''],
- ['a'],
- ['1'],
- ];
+ yield [''];
+ yield ['a'];
+ yield ['1'];
}
- /**
- * @dataProvider shortPasswordProvider
- * @param $password
- */
- public function testShortPassword($password)
+ #[DataProvider('shortPasswordProvider')]
+ public function testShortPassword(string $password): void
{
$matches = SequenceMatch::match($password);
- $this->assertEmpty($matches, "doesn't match length-" . strlen($password) . " sequences");
+ $this->assertEmpty($matches, "doesn't match length-" . strlen((string) $password) . ' sequences');
}
- public function testNonSequence()
+ public function testNonSequence(): void
{
$password = 'password';
$matches = SequenceMatch::match($password);
$this->assertEmpty($matches, "doesn't match password that's not a sequence");
}
- public function testOverlappingPatterns()
+ public function testOverlappingPatterns(): void
{
$password = 'abcbabc';
$this->checkMatches(
- "matches overlapping patterns",
+ 'matches overlapping patterns',
SequenceMatch::match($password),
'sequence',
['abc', 'cba', 'abc'],
@@ -53,56 +52,52 @@ public function testOverlappingPatterns()
);
}
- public function testEmbeddedSequencePatterns()
+ public function testEmbeddedSequencePatterns(): void
{
$prefixes = ['!', '22'];
$suffixes = ['!', '22'];
$pattern = 'jihg';
- foreach ($this->generatePasswords($pattern, $prefixes, $suffixes) as list($password, $i, $j)) {
+ foreach ($this->generatePasswords($pattern, $prefixes, $suffixes) as [$password, $i, $j]) {
$this->checkMatches(
- "matches embedded sequence patterns",
+ 'matches embedded sequence patterns',
SequenceMatch::match($password),
'sequence',
[$pattern],
[[$i, $j]],
[
- 'sequenceName' => ['lower'],
+ 'sequenceName' => ['lower'],
'ascending' => [false],
]
);
}
}
- public function sequenceProvider()
+ /**
+ * @return Iterator
+ */
+ public static function sequenceProvider(): Iterator
{
- return [
- ['ABC', 'upper', true],
- ['CBA', 'upper', false],
- ['PQR', 'upper', true],
- ['RQP', 'upper', false],
- ['XYZ', 'upper', true],
- ['ZYX', 'upper', false],
- ['abcd', 'lower', true],
- ['dcba', 'lower', false],
- ['jihg', 'lower', false],
- ['wxyz', 'lower', true],
- ['zxvt', 'lower', false],
- ['0369', 'digits', true],
- ['97531', 'digits', false]
- ];
+ yield ['ABC', 'upper', true];
+ yield ['CBA', 'upper', false];
+ yield ['PQR', 'upper', true];
+ yield ['RQP', 'upper', false];
+ yield ['XYZ', 'upper', true];
+ yield ['ZYX', 'upper', false];
+ yield ['abcd', 'lower', true];
+ yield ['dcba', 'lower', false];
+ yield ['jihg', 'lower', false];
+ yield ['wxyz', 'lower', true];
+ yield ['zxvt', 'lower', false];
+ yield ['0369', 'digits', true];
+ yield ['97531', 'digits', false];
}
- /**
- * @dataProvider sequenceProvider
- * @param string $password
- * @param string $name
- * @param bool $ascending
- */
- public function testSequenceInformation($password, $name, $ascending)
+ #[DataProvider('sequenceProvider')]
+ public function testSequenceInformation(string $password, string $name, bool $ascending): void
{
$this->checkMatches(
- "matches " . $password . " as a " . $name . " sequence",
+ 'matches ' . $password . ' as a ' . $name . ' sequence',
SequenceMatch::match($password),
'sequence',
[$password],
@@ -114,11 +109,11 @@ public function testSequenceInformation($password, $name, $ascending)
);
}
- public function testMultipleMatches()
+ public function testMultipleMatches(): void
{
$password = 'pass123wordZYX';
$this->checkMatches(
- "matches password with multiple sequences",
+ 'matches password with multiple sequences',
SequenceMatch::match($password),
'sequence',
['123', 'ZYX'],
@@ -130,7 +125,7 @@ public function testMultipleMatches()
);
}
- public function testMultibytePassword()
+ public function testMultibytePassword(): void
{
$pattern = 'muÃeca';
@@ -147,7 +142,7 @@ public function testMultibytePassword()
);
}
- public function testMultibyteSequence()
+ public function testMultibyteSequence(): void
{
$pattern = 'αβγδεζ';
@@ -164,34 +159,34 @@ public function testMultibyteSequence()
);
}
- public function guessProvider()
+ /**
+ * @return Iterator
+ */
+ public static function guessProvider(): Iterator
{
- return array(
- array('ab', true, 4 * 2), // obvious start * len-2
- array('XYZ', true, 26 * 3), // base26 * len-3
- array('4567', true, 10 * 4), // base10 * len-4
- array('7654', false, 10 * 4 * 2), // base10 * len-4 * descending
- array('ZYX', false, 4 * 3 * 2), // obvious start * len-3 * descending
- );
+ yield ['ab', true, 4 * 2];
+ // obvious start * len-2
+ yield ['XYZ', true, 26 * 3];
+ // base26 * len-3
+ yield ['4567', true, 10 * 4];
+ // base10 * len-4
+ yield ['7654', false, 10 * 4 * 2];
+ // base10 * len-4 * descending
+ yield ['ZYX', false, 4 * 3 * 2];
}
- /**
- * @dataProvider guessProvider
- * @param string $token
- * @param bool $ascending
- * @param float $expectedGuesses
- */
+ #[DataProvider('guessProvider')]
public function testGuesses(string $token, bool $ascending, float $expectedGuesses): void
{
$match = new SequenceMatch($token, 0, strlen($token) - 1, $token, ['ascending' => $ascending]);
$this->assertSame(
$expectedGuesses,
$match->getGuesses(),
- "the sequence pattern '$token' has guesses of $expectedGuesses"
+ "the sequence pattern '{$token}' has guesses of {$expectedGuesses}"
);
}
- public function testFeedback()
+ public function testFeedback(): void
{
$token = 'rstuvw';
$match = new SequenceMatch($token, 0, strlen($token) - 1, $token, ['ascending' => true]);
@@ -200,12 +195,12 @@ public function testFeedback()
$this->assertSame(
'Sequences like abc or 6543 are easy to guess',
$feedback['warning'],
- "sequence gives correct warning"
+ 'sequence gives correct warning'
);
$this->assertSame(
['Avoid sequences'],
$feedback['suggestions'],
- "sequence gives correct suggestion"
+ 'sequence gives correct suggestion'
);
}
}
diff --git a/test/Matchers/SpatialTest.php b/test/Matchers/SpatialTest.php
index 44d5304..0015e4b 100644
--- a/test/Matchers/SpatialTest.php
+++ b/test/Matchers/SpatialTest.php
@@ -4,32 +4,26 @@
namespace ZxcvbnPhp\Test\Matchers;
-use ZxcvbnPhp\Matchers\BaseMatch;
+use Iterator;
+use PHPUnit\Framework\Attributes\DataProvider;
use ZxcvbnPhp\Matchers\SpatialMatch;
use ZxcvbnPhp\Math\Binomial;
-/**
- * @covers \ZxcvbnPhp\Matchers\SpatialMatch
- */
+#[\PHPUnit\Framework\Attributes\CoversClass(\ZxcvbnPhp\Matchers\SpatialMatch::class)]
class SpatialTest extends AbstractMatchTest
{
/**
- * @return string[][]
+ * @return Iterator
*/
- public function shortPatternDataProvider(): array
+ public static function shortPatternDataProvider(): Iterator
{
- return [
- [''],
- ['/'],
- ['qw'],
- ['*/'],
- ];
+ yield [''];
+ yield ['/'];
+ yield ['qw'];
+ yield ['*/'];
}
- /**
- * @dataProvider shortPatternDataProvider
- * @param string $password
- */
+ #[DataProvider('shortPatternDataProvider')]
public function testShortPatterns(string $password): void
{
$this->assertSame(
@@ -50,14 +44,14 @@ public function testNoPattern(): void
public function testSurroundedPattern(): void
{
- $pattern = "6tfGHJ";
+ $pattern = '6tfGHJ';
$password = "rz!{$pattern}%z";
// for testing, make a subgraph that contains a single keyboard
$graphs = ['qwerty' => SpatialMatch::getAdjacencyGraphs()['qwerty']];
$this->checkMatches(
- "matches against spatial patterns surrounded by non-spatial patterns",
+ 'matches against spatial patterns surrounded by non-spatial patterns',
SpatialMatch::match($password, [], $graphs),
'spatial',
[$pattern],
@@ -70,39 +64,34 @@ public function testSurroundedPattern(): void
);
}
- public function spatialDataProvider(): array
+ /**
+ * @return Iterator
+ */
+ public static function spatialDataProvider(): Iterator
{
- return [
- ['12345', 'qwerty', 1, 0],
- ['@WSX', 'qwerty', 1, 4],
- ['6tfGHJ', 'qwerty', 2, 3],
- ['hGFd', 'qwerty', 1, 2],
- ['/;p09876yhn', 'qwerty', 3, 0],
- ['Xdr%', 'qwerty', 1, 2],
- ['159-', 'keypad', 1, 0],
- ['*84', 'keypad', 1, 0],
- ['/8520', 'keypad', 1, 0],
- ['369', 'keypad', 1, 0],
- ['/963.', 'mac_keypad', 1, 0],
- ['*-632.0214', 'mac_keypad', 9, 0],
- ['aoEP%yIxkjq:', 'dvorak', 4, 5],
- [';qoaOQ:Aoq;a', 'dvorak', 11, 4],
- ];
+ yield ['12345', 'qwerty', 1, 0];
+ yield ['@WSX', 'qwerty', 1, 4];
+ yield ['6tfGHJ', 'qwerty', 2, 3];
+ yield ['hGFd', 'qwerty', 1, 2];
+ yield ['/;p09876yhn', 'qwerty', 3, 0];
+ yield ['Xdr%', 'qwerty', 1, 2];
+ yield ['159-', 'keypad', 1, 0];
+ yield ['*84', 'keypad', 1, 0];
+ yield ['/8520', 'keypad', 1, 0];
+ yield ['369', 'keypad', 1, 0];
+ yield ['/963.', 'mac_keypad', 1, 0];
+ yield ['*-632.0214', 'mac_keypad', 9, 0];
+ yield ['aoEP%yIxkjq:', 'dvorak', 4, 5];
+ yield [';qoaOQ:Aoq;a', 'dvorak', 11, 4];
}
- /**
- * @dataProvider spatialDataProvider
- * @param string $password
- * @param string $keyboard
- * @param int $turns
- * @param int $shifts
- */
+ #[DataProvider('spatialDataProvider')]
public function testSpatialPatterns(string $password, string $keyboard, int $turns, int $shifts): void
{
$graphs = [$keyboard => SpatialMatch::getAdjacencyGraphs()[$keyboard]];
$this->checkMatches(
- "matches '$password' as a $keyboard pattern",
+ "matches '{$password}' as a {$keyboard} pattern",
SpatialMatch::match($password, [], $graphs),
'spatial',
[$password],
@@ -117,9 +106,9 @@ public function testSpatialPatterns(string $password, string $keyboard, int $tur
public function testShiftedCountForMultipleMatches(): void
{
- $password = "!QAZ1qaz";
+ $password = '!QAZ1qaz';
$this->checkMatches(
- "shifted count is correct for two matches in a row",
+ 'shifted count is correct for two matches in a row',
SpatialMatch::match($password),
'spatial',
['!QAZ', '1qaz'],
@@ -132,16 +121,6 @@ public function testShiftedCountForMultipleMatches(): void
);
}
- protected function getBaseGuessCount(string $token): float
- {
- // KEYBOARD_STARTING_POSITIONS * KEYBOARD_AVERAGE_DEGREE * (length - 1)
- // - 1 term because: not counting spatial patterns of length 1
- // eg for length==6, multiplier is 5 for needing to try len2,len3,..,len6
- return SpatialMatch::KEYBOARD_STARTING_POSITION
- * SpatialMatch::KEYBOARD_AVERAGE_DEGREES
- * (strlen($token) - 1);
- }
-
public function testGuessesBasic(): void
{
$token = 'zxcvbn';
@@ -154,7 +133,7 @@ public function testGuessesBasic(): void
$this->assertSame(
$this->getBaseGuessCount($token),
$match->getGuesses(),
- "with no turns or shifts, guesses is starts * degree * (len-1)"
+ 'with no turns or shifts, guesses is starts * degree * (len-1)'
);
}
@@ -170,7 +149,7 @@ public function testGuessesShifted(): void
$this->assertSame(
$this->getBaseGuessCount($token) * (Binomial::binom(6, 2) + Binomial::binom(6, 1)),
$match->getGuesses(),
- "guesses is added for shifted keys, similar to capitals in dictionary matching"
+ 'guesses is added for shifted keys, similar to capitals in dictionary matching'
);
}
@@ -186,31 +165,24 @@ public function testGuessesEverythingShifted(): void
$this->assertSame(
$this->getBaseGuessCount($token) * 2,
$match->getGuesses(),
- "when everything is shifted, guesses are double"
+ 'when everything is shifted, guesses are double'
);
}
/**
- * @return array[]
+ * @return Iterator
*/
- public function complexGuessProvider(): array
+ public static function complexGuessProvider(): Iterator
{
- return [
- ['6yhgf', 2, 19596],
- ['asde3w', 3, 203315],
- ['zxcft6yh', 3, 558460],
- ['xcvgy7uj', 3, 558460],
- ['ertghjm,.', 5, 30160744],
- ['qwerfdsazxcv', 5, 175281377],
- ];
+ yield ['6yhgf', 2, 19596];
+ yield ['asde3w', 3, 203315];
+ yield ['zxcft6yh', 3, 558460];
+ yield ['xcvgy7uj', 3, 558460];
+ yield ['ertghjm,.', 5, 30160744];
+ yield ['qwerfdsazxcv', 5, 175281377];
}
- /**
- * @dataProvider complexGuessProvider
- * @param string $token
- * @param int $turns
- * @param float $expected
- */
+ #[DataProvider('complexGuessProvider')]
public function testGuessesComplexCase(string $token, int $turns, float $expected): void
{
$match = new SpatialMatch($token, 0, strlen($token) - 1, $token, [
@@ -220,13 +192,12 @@ public function testGuessesComplexCase(string $token, int $turns, float $expecte
]);
$actual = $match->getGuesses();
- $this->assertIsFloat($actual);
$this->assertEqualsWithDelta(
$expected,
$actual,
1.0,
- "spatial guesses accounts for turn positions, directions and starting keys"
+ 'spatial guesses accounts for turn positions, directions and starting keys'
);
}
@@ -243,12 +214,12 @@ public function testFeedbackStraightLine(): void
$this->assertSame(
'Straight rows of keys are easy to guess',
$feedback['warning'],
- "spatial match in straight line gives correct warning"
+ 'spatial match in straight line gives correct warning'
);
$this->assertContains(
'Use a longer keyboard pattern with more turns',
$feedback['suggestions'],
- "spatial match in straight line gives correct suggestion"
+ 'spatial match in straight line gives correct suggestion'
);
}
@@ -265,12 +236,22 @@ public function testFeedbackWithTurns(): void
$this->assertSame(
'Short keyboard patterns are easy to guess',
$feedback['warning'],
- "spatial match with turns gives correct warning"
+ 'spatial match with turns gives correct warning'
);
$this->assertContains(
'Use a longer keyboard pattern with more turns',
$feedback['suggestions'],
- "spatial match with turns gives correct suggestion"
+ 'spatial match with turns gives correct suggestion'
);
}
+
+ protected function getBaseGuessCount(string $token): float
+ {
+ // KEYBOARD_STARTING_POSITIONS * KEYBOARD_AVERAGE_DEGREE * (length - 1)
+ // - 1 term because: not counting spatial patterns of length 1
+ // eg for length==6, multiplier is 5 for needing to try len2,len3,..,len6
+ return SpatialMatch::KEYBOARD_STARTING_POSITION
+ * SpatialMatch::KEYBOARD_AVERAGE_DEGREES
+ * (strlen($token) - 1);
+ }
}
diff --git a/test/Matchers/YearTest.php b/test/Matchers/YearTest.php
index 19a094d..31e3034 100644
--- a/test/Matchers/YearTest.php
+++ b/test/Matchers/YearTest.php
@@ -4,37 +4,33 @@
namespace ZxcvbnPhp\Test\Matchers;
+use Iterator;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\DataProvider;
use ZxcvbnPhp\Matchers\DateMatch;
use ZxcvbnPhp\Matchers\YearMatch;
-/**
- * @covers \ZxcvbnPhp\Matchers\YearMatch
- */
+#[CoversClass(YearMatch::class)]
class YearTest extends AbstractMatchTest
{
- public function testNoMatchForNonYear()
+ public function testNoMatchForNonYear(): void
{
$password = 'password';
$this->assertEmpty(YearMatch::match($password));
}
- public function recentYearProvider()
+ public static function recentYearProvider(): Iterator
{
- return [
- ['1922'],
- ['2001'],
- ['2017']
- ];
+ yield ['1922'];
+ yield ['2001'];
+ yield ['2017'];
}
- /**
- * @dataProvider recentYearProvider
- * @param $password
- */
- public function testRecentYears($password)
+ #[DataProvider('recentYearProvider')]
+ public function testRecentYears(string $password): void
{
$this->checkMatches(
- "matches recent year",
+ 'matches recent year',
YearMatch::match($password),
'regex',
[$password],
@@ -43,34 +39,29 @@ public function testRecentYears($password)
);
}
- public function nonRecentYearProvider()
+ public static function nonRecentYearProvider(): Iterator
{
- return [
- ['1420'],
- ['1899'],
- ['2345']
- ];
+ yield ['1420'];
+ yield ['1899'];
+ yield ['2345'];
}
- /**
- * @dataProvider nonRecentYearProvider
- * @param $password
- */
- public function testNonRecentYears($password)
+ #[DataProvider('nonRecentYearProvider')]
+ public function testNonRecentYears(string $password): void
{
$matches = YearMatch::match($password);
- $this->assertEmpty($matches, "does not match non-recent year");
+ $this->assertEmpty($matches, 'does not match non-recent year');
}
- public function testYearSurroundedByWords()
+ public function testYearSurroundedByWords(): void
{
$prefixes = ['car', 'dog'];
$suffixes = ['car', 'dog'];
$pattern = '1900';
- foreach ($this->generatePasswords($pattern, $prefixes, $suffixes) as list($password, $i, $j)) {
+ foreach ($this->generatePasswords($pattern, $prefixes, $suffixes) as [$password, $i, $j]) {
$this->checkMatches(
- "identifies years surrounded by words",
+ 'identifies years surrounded by words',
YearMatch::match($password),
'regex',
[$pattern],
@@ -85,11 +76,11 @@ public function testYearSurroundedByWords()
$this->assertSame('1900', $matches[0]->token, 'Token incorrect');
}
- public function testYearWithinOtherNumbers()
+ public function testYearWithinOtherNumbers(): void
{
$password = '419004';
$this->checkMatches(
- "matches year within other numbers",
+ 'matches year within other numbers',
YearMatch::match($password),
'regex',
['1900'],
@@ -98,43 +89,45 @@ public function testYearWithinOtherNumbers()
);
}
- public function testGuessesPast()
+ public function testGuessesPast(): void
{
$token = '1972';
$match = new YearMatch($token, 0, 3, $token);
$this->assertSame(
- (float)(DateMatch::getReferenceYear() - (int)$token),
+ (float) (DateMatch::getReferenceYear() - (int) $token),
$match->getGuesses(),
- "guesses of |year - REFERENCE_YEAR| for past year matches"
+ 'guesses of |year - REFERENCE_YEAR| for past year matches'
);
}
- public function testGuessesFuture()
+ public function testGuessesFuture(): void
{
$token = '2050';
$match = new YearMatch($token, 0, 3, $token);
$this->assertSame(
- (float)((int)$token - DateMatch::getReferenceYear()),
+ (float) ((int) $token - DateMatch::getReferenceYear()),
$match->getGuesses(),
- "guesses of |year - REFERENCE_YEAR| for future year matches"
+ 'guesses of |year - REFERENCE_YEAR| for future year matches'
);
}
- public function testGuessesUnderMinimumYearSpace()
+ public function testGuessesUnderMinimumYearSpace(): void
{
$token = '2005';
$match = new YearMatch($token, 0, 3, $token);
- $this->assertSame(
- 20.0, // DateMatch::MIN_YEAR_SPACE
+ $this->assertEqualsWithDelta(
+ 20.0,
+ // DateMatch::MIN_YEAR_SPACE
$match->getGuesses(),
- "guesses of MIN_YEAR_SPACE for a year close to REFERENCE_YEAR"
+ PHP_FLOAT_EPSILON,
+ 'guesses of MIN_YEAR_SPACE for a year close to REFERENCE_YEAR'
);
}
- public function testFeedback()
+ public function testFeedback(): void
{
$token = '2010';
$match = new YearMatch($token, 0, strlen($token) - 1, $token);
@@ -143,17 +136,17 @@ public function testFeedback()
$this->assertSame(
'Recent years are easy to guess',
$feedback['warning'],
- "year match gives correct warning"
+ 'year match gives correct warning'
);
$this->assertContains(
'Avoid recent years',
$feedback['suggestions'],
- "year match gives correct suggestion #1"
+ 'year match gives correct suggestion #1'
);
$this->assertContains(
'Avoid years that are associated with you',
$feedback['suggestions'],
- "year match gives correct suggestion #2"
+ 'year match gives correct suggestion #2'
);
}
}
diff --git a/test/Math/BinomialTest.php b/test/Math/BinomialTest.php
index 61fc87a..99da659 100644
--- a/test/Math/BinomialTest.php
+++ b/test/Math/BinomialTest.php
@@ -4,60 +4,57 @@
namespace ZxcvbnPhp\Test\Math;
+use Iterator;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use ZxcvbnPhp\Math\Binomial;
use ZxcvbnPhp\Math\BinomialProvider;
class BinomialTest extends TestCase
{
- public function binomialDataProvider()
+ public static function binomialDataProvider(): Iterator
{
- return [
- [ 0, 0, 1.0 ],
- [ 1, 0, 1.0 ],
- [ 5, 0, 1.0 ],
- [ 0, 1, 0.0 ],
- [ 0, 5, 0.0 ],
- [ 2, 1, 2.0 ],
- [ 4, 2, 6.0 ],
- [ 33, 7, 4272048.0 ],
- [ 206, 202, 72867865.0 ],
- [ 3, 5, 0.0 ],
- [ 29847, 2, 445406781.0 ],
- [ 49, 12, 92263734836.0 ],
- ];
+ yield [ 0, 0, 1.0 ];
+ yield [ 1, 0, 1.0 ];
+ yield [ 5, 0, 1.0 ];
+ yield [ 0, 1, 0.0 ];
+ yield [ 0, 5, 0.0 ];
+ yield [ 2, 1, 2.0 ];
+ yield [ 4, 2, 6.0 ];
+ yield [ 33, 7, 4272048.0 ];
+ yield [ 206, 202, 72867865.0 ];
+ yield [ 3, 5, 0.0 ];
+ yield [ 29847, 2, 445406781.0 ];
+ yield [ 49, 12, 92263734836.0 ];
}
- public function testHasProvider()
+ public function testHasProvider(): void
{
$this->assertNotEmpty(Binomial::getUsableProviderClasses());
}
- public function testChosenProviderMatchesExpected()
+ public function testChosenProviderMatchesExpected(): void
{
$providerClasses = Binomial::getUsableProviderClasses();
+ $provider = reset($providerClasses);
+ $this->assertNotFalse($provider);
- $this->assertInstanceOf(reset($providerClasses), Binomial::getProvider());
+ $this->assertInstanceOf($provider, Binomial::getProvider());
}
- /**
- * @dataProvider binomialDataProvider
- * @param int $n
- * @param int $k
- * @param float $expected
- */
- public function testBinomialCoefficient(int $n, int $k, float $expected)
+ #[DataProvider('binomialDataProvider')]
+ public function testBinomialCoefficient(int $n, int $k, float $expected): void
{
foreach (Binomial::getUsableProviderClasses() as $providerClass) {
$provider = new $providerClass();
$this->assertInstanceOf(BinomialProvider::class, $provider);
$value = $provider->binom($n, $k);
- $this->assertSame($expected, $value, "$providerClass returns expected result for ($n, $k)");
+ $this->assertSame($expected, $value, "{$providerClass} returns expected result for ({$n}, {$k})");
if ($k <= $n) { // Behavior is undefined for $k > n; don't test that
$flippedValue = $provider->binom($n, $n - $k);
- $this->assertSame($value, $flippedValue, "$providerClass is symmetrical");
+ $this->assertSame($value, $flippedValue, "{$providerClass} is symmetrical");
}
}
}
diff --git a/test/ScorerTest.php b/test/ScorerTest.php
index 594e711..96928ae 100644
--- a/test/ScorerTest.php
+++ b/test/ScorerTest.php
@@ -4,95 +4,93 @@
namespace ZxcvbnPhp\Test;
+use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use ZxcvbnPhp\Scorer;
use ZxcvbnPhp\Test\Matchers\MockMatch;
-/**
- * @covers \ZxcvbnPhp\Scorer
- */
+#[CoversClass(Scorer::class)]
class ScorerTest extends TestCase
{
public const PASSWORD = '0123456789';
- /** @var Scorer */
- private $scorer;
+ private Scorer $scorer;
public function setUp(): void
{
$this->scorer = new Scorer();
}
- public function testStrictAssertions()
+ public function testStrictAssertions(): void
{
$this->assertNotSame(1, 1.0);
}
- public function testBlankPassword()
+ public function testBlankPassword(): void
{
$result = $this->scorer->getMostGuessableMatchSequence('', []);
- $this->assertSame(1.0, $result['guesses']);
+ $this->assertEqualsWithDelta(1.0, $result['guesses'], PHP_FLOAT_EPSILON);
$this->assertEmpty($result['sequence']);
}
- public function testEmptyMatchSequence()
+ public function testEmptyMatchSequence(): void
{
$result = $this->scorer->getMostGuessableMatchSequence(self::PASSWORD, []);
- $this->assertSame(1, count($result['sequence']), "result.sequence.length == 1");
- $this->assertSame(10000000001.0, $result['guesses'], "result.guesses == 10000000001");
+ $this->assertCount(1, $result['sequence'], 'result.sequence.length == 1');
+ $this->assertEqualsWithDelta(10000000001.0, $result['guesses'], PHP_FLOAT_EPSILON, 'result.guesses == 10000000001');
$match = $result['sequence'][0];
$this->assertSame('bruteforce', $match->pattern, "match.pattern == 'bruteforce'");
- $this->assertSame(self::PASSWORD, $match->token, "match.token == " . self::PASSWORD);
- $this->assertSame([0, 9], [$match->begin, $match->end], "[i, j] == [0, 9]");
+ $this->assertSame(self::PASSWORD, $match->token, 'match.token == ' . self::PASSWORD);
+ $this->assertSame([0, 9], [$match->begin, $match->end], '[i, j] == [0, 9]');
}
- public function testMatchAndBruteforceWithPrefix()
+ public function testMatchAndBruteforceWithPrefix(): void
{
$match = new MockMatch(0, 5, 1);
$result = $this->scorer->getMostGuessableMatchSequence(self::PASSWORD, [$match], true);
- $this->assertSame(2, count($result['sequence']), "result.sequence.length == 2");
- $this->assertSame($match, $result['sequence'][0], "first match is the provided match object");
+ $this->assertCount(2, $result['sequence'], 'result.sequence.length == 2');
+ $this->assertSame($match, $result['sequence'][0], 'first match is the provided match object');
$match1 = $result['sequence'][1];
- $this->assertSame('bruteforce', $match1->pattern, "second match is bruteforce");
- $this->assertSame([6, 9], [$match1->begin, $match1->end], "second match covers full suffix after first match");
+ $this->assertSame('bruteforce', $match1->pattern, 'second match is bruteforce');
+ $this->assertSame([6, 9], [$match1->begin, $match1->end], 'second match covers full suffix after first match');
}
- public function testMatchAndBruteforceWithSuffix()
+ public function testMatchAndBruteforceWithSuffix(): void
{
$match = new MockMatch(3, 9, 1);
$result = $this->scorer->getMostGuessableMatchSequence(self::PASSWORD, [$match], true);
- $this->assertSame(2, count($result['sequence']), "result.sequence.length == 2");
- $this->assertSame($match, $result['sequence'][1], "second match is the provided match object");
+ $this->assertCount(2, $result['sequence'], 'result.sequence.length == 2');
+ $this->assertSame($match, $result['sequence'][1], 'second match is the provided match object');
$match0 = $result['sequence'][0];
- $this->assertSame('bruteforce', $match0->pattern, "first match is bruteforce");
- $this->assertSame([0, 2], [$match0->begin, $match0->end], "first match covers full prefix before second match");
+ $this->assertSame('bruteforce', $match0->pattern, 'first match is bruteforce');
+ $this->assertSame([0, 2], [$match0->begin, $match0->end], 'first match covers full prefix before second match');
}
- public function testMatchAndBruteforceWithInfix()
+ public function testMatchAndBruteforceWithInfix(): void
{
$match = new MockMatch(1, 8, 1);
$result = $this->scorer->getMostGuessableMatchSequence(self::PASSWORD, [$match], true);
- $this->assertSame(3, count($result['sequence']), "result.sequence.length == 3");
+ $this->assertCount(3, $result['sequence'], 'result.sequence.length == 3');
$match0 = $result['sequence'][0];
$match2 = $result['sequence'][2];
- $this->assertSame($match, $result['sequence'][1], "middle match is the provided match object");
- $this->assertSame('bruteforce', $match0->pattern, "first match is bruteforce");
- $this->assertSame('bruteforce', $match2->pattern, "third match is bruteforce");
- $this->assertSame([0, 0], [$match0->begin, $match0->end], "first match covers full prefix before second match");
- $this->assertSame([9, 9], [$match2->begin, $match2->end], "third match covers full suffix after second match");
+ $this->assertSame($match, $result['sequence'][1], 'middle match is the provided match object');
+ $this->assertSame('bruteforce', $match0->pattern, 'first match is bruteforce');
+ $this->assertSame('bruteforce', $match2->pattern, 'third match is bruteforce');
+ $this->assertSame([0, 0], [$match0->begin, $match0->end], 'first match covers full prefix before second match');
+ $this->assertSame([9, 9], [$match2->begin, $match2->end], 'third match covers full suffix after second match');
}
- public function testBasicGuesses()
+ public function testBasicGuesses(): void
{
$matches = [
new MockMatch(0, 9, 1),
@@ -100,11 +98,11 @@ public function testBasicGuesses()
];
$result = $this->scorer->getMostGuessableMatchSequence(self::PASSWORD, $matches, true);
- $this->assertSame(1, count($result['sequence']), "result.sequence.length == 1");
- $this->assertSame($matches[0], $result['sequence'][0], "result.sequence[0] == m0");
+ $this->assertCount(1, $result['sequence'], 'result.sequence.length == 1');
+ $this->assertSame($matches[0], $result['sequence'][0], 'result.sequence[0] == m0');
}
- public function testChoosesLowerGuessesMatchesForSameSpan()
+ public function testChoosesLowerGuessesMatchesForSameSpan(): void
{
$matches = [
new MockMatch(0, 9, 1),
@@ -112,11 +110,11 @@ public function testChoosesLowerGuessesMatchesForSameSpan()
];
$result = $this->scorer->getMostGuessableMatchSequence(self::PASSWORD, $matches, true);
- $this->assertSame(1, count($result['sequence']), "result.sequence.length == 1");
- $this->assertSame($matches[0], $result['sequence'][0], "result.sequence[0] == m0");
+ $this->assertCount(1, $result['sequence'], 'result.sequence.length == 1');
+ $this->assertSame($matches[0], $result['sequence'][0], 'result.sequence[0] == m0');
}
- public function testChoosesLowerGuessesMatchesForSameSpanReversedOrder()
+ public function testChoosesLowerGuessesMatchesForSameSpanReversedOrder(): void
{
$matches = [
new MockMatch(0, 9, 2),
@@ -124,11 +122,11 @@ public function testChoosesLowerGuessesMatchesForSameSpanReversedOrder()
];
$result = $this->scorer->getMostGuessableMatchSequence(self::PASSWORD, $matches, true);
- $this->assertSame(1, count($result['sequence']), "result.sequence.length == 1");
- $this->assertSame($matches[1], $result['sequence'][0], "result.sequence[0] == m1");
+ $this->assertCount(1, $result['sequence'], 'result.sequence.length == 1');
+ $this->assertSame($matches[1], $result['sequence'][0], 'result.sequence[0] == m1');
}
- public function testChoosesSupersetMatchWhenApplicable()
+ public function testChoosesSupersetMatchWhenApplicable(): void
{
$matches = [
new MockMatch(0, 9, 3),
@@ -137,11 +135,11 @@ public function testChoosesSupersetMatchWhenApplicable()
];
$result = $this->scorer->getMostGuessableMatchSequence(self::PASSWORD, $matches, true);
- $this->assertSame(3.0, $result['guesses'], "total guesses == 3");
- $this->assertSame([$matches[0]], $result['sequence'], "sequence is [m0]");
+ $this->assertEqualsWithDelta(3.0, $result['guesses'], PHP_FLOAT_EPSILON, 'total guesses == 3');
+ $this->assertSame([$matches[0]], $result['sequence'], 'sequence is [m0]');
}
- public function testChoosesSubsetMatchesWhenApplicable()
+ public function testChoosesSubsetMatchesWhenApplicable(): void
{
$matches = [
new MockMatch(0, 9, 5),
@@ -150,7 +148,7 @@ public function testChoosesSubsetMatchesWhenApplicable()
];
$result = $this->scorer->getMostGuessableMatchSequence(self::PASSWORD, $matches, true);
- $this->assertSame(4.0, $result['guesses'], "total guesses == 4");
- $this->assertSame([$matches[1], $matches[2]], $result['sequence'], "sequence is [m1, m2]");
+ $this->assertEqualsWithDelta(4.0, $result['guesses'], PHP_FLOAT_EPSILON, 'total guesses == 4');
+ $this->assertSame([$matches[1], $matches[2]], $result['sequence'], 'sequence is [m1, m2]');
}
}
diff --git a/test/TimeEstimatorTest.php b/test/TimeEstimatorTest.php
index 1efca32..4123c7f 100644
--- a/test/TimeEstimatorTest.php
+++ b/test/TimeEstimatorTest.php
@@ -4,94 +4,89 @@
namespace ZxcvbnPhp\Test;
+use Iterator;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use ZxcvbnPhp\TimeEstimator;
class TimeEstimatorTest extends TestCase
{
- /** @var TimeEstimator */
- private $timeEstimator;
+ private TimeEstimator $timeEstimator;
public function setUp(): void
{
$this->timeEstimator = new TimeEstimator();
}
- public function testTime100PerHour()
+ public function testTime100PerHour(): void
{
$actual = $this->timeEstimator->estimateAttackTimes(100)['crack_times_display']['online_throttling_100_per_hour'];
- $this->assertSame('1 hour', $actual, "100 guesses / 100 per hour = 1 hour");
+ $this->assertSame('1 hour', $actual, '100 guesses / 100 per hour = 1 hour');
}
- public function testTime10PerSecond()
+ public function testTime10PerSecond(): void
{
$actual = $this->timeEstimator->estimateAttackTimes(10)['crack_times_display']['online_no_throttling_10_per_second'];
- $this->assertSame('1 second', $actual, "10 guesses / 10 per second = 1 second");
+ $this->assertSame('1 second', $actual, '10 guesses / 10 per second = 1 second');
}
- public function testTime1e4PerSecond()
+ public function testTime1e4PerSecond(): void
{
$actual = $this->timeEstimator->estimateAttackTimes(1e5)['crack_times_display']['offline_slow_hashing_1e4_per_second'];
- $this->assertSame('10 seconds', $actual, "1e5 guesses / 1e4 per second = 10 seconds");
+ $this->assertSame('10 seconds', $actual, '1e5 guesses / 1e4 per second = 10 seconds');
}
- public function testTime1e10PerSecond()
+ public function testTime1e10PerSecond(): void
{
$actual = $this->timeEstimator->estimateAttackTimes(2e11)['crack_times_display']['offline_fast_hashing_1e10_per_second'];
- $this->assertSame('20 seconds', $actual, "2e11 guesses / 1e10 per second = 20 seconds");
+ $this->assertSame('20 seconds', $actual, '2e11 guesses / 1e10 per second = 20 seconds');
}
- public function testTimeLessThanASecond()
+ public function testTimeLessThanASecond(): void
{
$actual = $this->timeEstimator->estimateAttackTimes(1)['crack_times_display']['offline_fast_hashing_1e10_per_second'];
- $this->assertSame('less than a second', $actual, "less than a second");
+ $this->assertSame('less than a second', $actual, 'less than a second');
}
- public function testTimeCenturies()
+ public function testTimeCenturies(): void
{
$actual = $this->timeEstimator->estimateAttackTimes(1e10)['crack_times_display']['online_throttling_100_per_hour'];
- $this->assertSame('centuries', $actual, "centuries");
+ $this->assertSame('centuries', $actual, 'centuries');
}
- public function testTimeRounding()
+ public function testTimeRounding(): void
{
$actual = $this->timeEstimator->estimateAttackTimes(1500)['crack_times_display']['online_no_throttling_10_per_second'];
- $this->assertSame('3 minutes', $actual, "1500 guesses / 10 per second = 3 minutes and not 2.5 minutes");
+ $this->assertSame('3 minutes', $actual, '1500 guesses / 10 per second = 3 minutes and not 2.5 minutes');
}
- public function testPlurals()
+ public function testPlurals(): void
{
$actual = $this->timeEstimator->estimateAttackTimes(12)['crack_times_display']['online_no_throttling_10_per_second'];
- $this->assertSame('1 second', $actual, "no plural if unit value is 1");
+ $this->assertSame('1 second', $actual, 'no plural if unit value is 1');
$actual = $this->timeEstimator->estimateAttackTimes(22)['crack_times_display']['online_no_throttling_10_per_second'];
- $this->assertSame('2 seconds', $actual, "plural if unit value is more than 1");
+ $this->assertSame('2 seconds', $actual, 'plural if unit value is more than 1');
}
- public function unitProvider()
+ public static function unitProvider(): Iterator
{
- return [
- [1e2, '10 seconds'],
- [1e3, '2 minutes'],
- [1e5, '3 hours'],
- [1e7, '12 days'],
- [1e8, '4 months'],
- [1e9, '3 years'],
- ];
+ yield [1e2, '10 seconds'];
+ yield [1e3, '2 minutes'];
+ yield [1e5, '3 hours'];
+ yield [1e7, '12 days'];
+ yield [1e8, '4 months'];
+ yield [1e9, '3 years'];
}
- /**
- * @dataProvider unitProvider
- * @param int $guesses
- * @param string $displayText
- */
- public function testTimeUnits($guesses, $displayText)
+ #[DataProvider('unitProvider')]
+ public function testTimeUnits(float $guesses, string $displayText): void
{
$actual = $this->timeEstimator->estimateAttackTimes($guesses)['crack_times_display']['online_no_throttling_10_per_second'];
- $this->assertSame($displayText, $actual, "centuries");
+ $this->assertSame($displayText, $actual, 'centuries');
}
- public function testDifferentSpeeds()
+ public function testDifferentSpeeds(): void
{
$results = $this->timeEstimator->estimateAttackTimes(1e10)['crack_times_seconds'];
@@ -101,43 +96,37 @@ public function testDifferentSpeeds()
$this->assertSame(1e10 / (100 / 3600), $results['online_throttling_100_per_hour']);
}
- public function testSpeedLessThanOne()
+ public function testSpeedLessThanOne(): void
{
$actual = $this->timeEstimator->estimateAttackTimes(100)['crack_times_seconds']['offline_slow_hashing_1e4_per_second'];
- $this->assertSame(0.01, $actual, "decimal speed when less than one second");
+ $this->assertEqualsWithDelta(0.01, $actual, PHP_FLOAT_EPSILON, 'decimal speed when less than one second');
}
- public function scoreProvider()
+ public static function scoreProvider(): Iterator
{
- return [
- [1e2, 0],
- [1e4, 1],
- [1e7, 2],
- [1e9, 3],
- [1e11, 4],
- ];
+ yield [1e2, 0];
+ yield [1e4, 1];
+ yield [1e7, 2];
+ yield [1e9, 3];
+ yield [1e11, 4];
}
- /**
- * @dataProvider scoreProvider
- * @param int $guesses
- * @param int $expectedScore
- */
- public function testScores($guesses, $expectedScore)
+ #[DataProvider('scoreProvider')]
+ public function testScores(float $guesses, int $expectedScore): void
{
$actual = $this->timeEstimator->estimateAttackTimes($guesses)['score'];
- $this->assertSame($expectedScore, $actual, "correct score");
+ $this->assertSame($expectedScore, $actual, 'correct score');
}
- public function testScoreDelta()
+ public function testScoreDelta(): void
{
$score = $this->timeEstimator->estimateAttackTimes(1000)['score'];
- $this->assertSame(0, $score, "guesses at threshold gets lower score");
+ $this->assertSame(0, $score, 'guesses at threshold gets lower score');
$score = $this->timeEstimator->estimateAttackTimes(1003)['score'];
- $this->assertSame(0, $score, "guesses just above threshold gets lower score");
+ $this->assertSame(0, $score, 'guesses just above threshold gets lower score');
$score = $this->timeEstimator->estimateAttackTimes(1010)['score'];
- $this->assertSame(1, $score, "guesses above delta gets higher score");
+ $this->assertSame(1, $score, 'guesses above delta gets higher score');
}
}
diff --git a/test/ZxcvbnTest.php b/test/ZxcvbnTest.php
index 4aab198..4955afc 100644
--- a/test/ZxcvbnTest.php
+++ b/test/ZxcvbnTest.php
@@ -4,6 +4,8 @@
namespace ZxcvbnPhp\Test;
+use Iterator;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use ZxcvbnPhp\Matchers\Bruteforce;
use ZxcvbnPhp\Matchers\DictionaryMatch;
@@ -12,49 +14,46 @@
class ZxcvbnTest extends TestCase
{
- /** @var Zxcvbn */
- private $zxcvbn;
+ private Zxcvbn $zxcvbn;
public function setUp(): void
{
$this->zxcvbn = new Zxcvbn();
}
- public function testMinimumGuessesForMultipleMatches()
+ public function testMinimumGuessesForMultipleMatches(): void
{
- /** @var MatchInterface[] $matches */
+ /** @var array $matches */
$matches = $this->zxcvbn->passwordStrength('rockyou')['sequence'];
// zxcvbn will return two matches: 'rock' (rank 359) and 'you' (rank 1).
// If tested alone, the word 'you' would return only 1 guess, but because it's part of a larger password,
// it should return the minimum number of guesses, which is 50 for a multi-character token.
- $this->assertSame(50.0, $matches[1]->getGuesses());
+ $this->assertEqualsWithDelta(50.0, $matches[1]->getGuesses(), PHP_FLOAT_EPSILON);
}
- public function typeDataProvider()
+ public static function typeDataProvider(): Iterator
{
- return [
- ['password', 'string'],
- ['guesses', 'numeric'],
- ['guesses_log10', 'numeric'],
- ['sequence', 'array'],
- ['crack_times_seconds', 'array'],
- ['crack_times_display', 'array'],
- ['feedback', 'array'],
- ['calc_time', 'numeric'],
- ];
+ yield ['password', 'string'];
+ yield ['guesses', 'numeric'];
+ yield ['guesses_log10', 'numeric'];
+ yield ['sequence', 'array'];
+ yield ['crack_times_seconds', 'array'];
+ yield ['crack_times_display', 'array'];
+ yield ['feedback', 'array'];
+ yield ['calc_time', 'numeric'];
}
/**
- * @dataProvider typeDataProvider
* @throws \Exception
*/
- public function testZxcvbnReturnTypes($key, $type)
+ #[DataProvider('typeDataProvider')]
+ public function testZxcvbnReturnTypes(string $key, string $type): void
{
$zxcvbn = new Zxcvbn();
$result = $zxcvbn->passwordStrength('utmostfortitude2018');
- $this->assertArrayHasKey($key, $result, "zxcvbn result has key " . $key);
+ $this->assertArrayHasKey($key, $result, 'zxcvbn result has key ' . $key);
if ($type === 'string') {
$correct = is_string($result[$key]);
@@ -66,85 +65,77 @@ public function testZxcvbnReturnTypes($key, $type)
throw new \Exception('Invalid test case');
}
- $this->assertTrue($correct, "zxcvbn result value " . $key . " is type " . $type);
+ $this->assertTrue($correct, 'zxcvbn result value ' . $key . ' is type ' . $type);
}
- public function sanityCheckDataProvider()
+ public static function sanityCheckDataProvider(): Iterator
{
- return [
- ['password', 0, ['dictionary',], 'less than a second', 3],
- ['65432', 0, ['sequence',], 'less than a second', 101],
- ['sdfgsdfg', 1, ['repeat',], 'less than a second', 2595],
- ['fortitude', 1, ['dictionary',], '1 second', 11308],
- ['dfjkym', 1, ['bruteforce',], '2 minutes', 1000001],
- ['fortitude22', 2, ['dictionary', 'repeat',], '2 minutes', 1140700],
- ['absoluteadnap', 2, ['dictionary', 'dictionary',], '25 minutes', 15187504],
- ['knifeandspoon', 3, ['dictionary', 'dictionary', 'dictionary'], '1 day', 1108057600],
- ['h1dden_26191', 3, ['dictionary', 'bruteforce', 'date'], '3 days', 2993690800],
- ['4rfv1236yhn!', 4, ['spatial', 'sequence', 'bruteforce'], '1 month', 38980000000],
- ['BVidSNqe3oXVyE1996', 4, ['bruteforce', 'regex',], 'centuries', 10000000000010000],
- ];
+ yield ['password', 0, ['dictionary'], 'less than a second', 3];
+ yield ['65432', 0, ['sequence'], 'less than a second', 101];
+ yield ['sdfgsdfg', 1, ['repeat'], 'less than a second', 2595];
+ yield ['fortitude', 1, ['dictionary'], '1 second', 11308];
+ yield ['dfjkym', 1, ['bruteforce'], '2 minutes', 1000001];
+ yield ['fortitude22', 2, ['dictionary', 'repeat'], '2 minutes', 1140700];
+ yield ['absoluteadnap', 2, ['dictionary', 'dictionary'], '25 minutes', 15187504];
+ yield ['knifeandspoon', 3, ['dictionary', 'dictionary', 'dictionary'], '1 day', 1108057600];
+ yield ['h1dden_26191', 3, ['dictionary', 'bruteforce', 'date'], '4 days', 3081378400];
+ yield ['4rfv1236yhn!', 4, ['spatial', 'sequence', 'bruteforce'], '1 month', 38980000000];
+ yield ['BVidSNqe3oXVyE1996', 4, ['bruteforce', 'regex'], 'centuries', 10000000000010000];
}
/**
* Some basic sanity checks. All of the underlying functionality is tested in more details in their specific
* classes, but this is just to check that it's all tied together correctly at the end.
- * @dataProvider sanityCheckDataProvider
- * @param string $password
- * @param int $score
- * @param string[] $patterns
- * @param string $slowHashingDisplay
- * @param float $guesses
+ *
+ * @param array $patterns
*/
+ #[DataProvider('sanityCheckDataProvider')]
public function testZxcvbnSanityCheck(string $password, int $score, array $patterns, string $slowHashingDisplay, float $guesses): void
{
$result = $this->zxcvbn->passwordStrength($password);
- $this->assertSame($password, $result['password'], "zxcvbn result has correct password");
- $this->assertSame($score, $result['score'], "zxcvbn result has correct score");
+ $this->assertSame($password, $result['password'], 'zxcvbn result has correct password');
+ $this->assertSame($score, $result['score'], 'zxcvbn result has correct score');
$this->assertSame(
$slowHashingDisplay,
$result['crack_times_display']['offline_slow_hashing_1e4_per_second'],
- "zxcvbn result has correct display time for offline slow hashing"
+ 'zxcvbn result has correct display time for offline slow hashing'
);
- $this->assertEqualsWithDelta($guesses, $result['guesses'], 1.0, "zxcvbn result has correct guesses");
+ $this->assertEqualsWithDelta($guesses, $result['guesses'], 1.0, 'zxcvbn result has correct guesses');
- $actualPatterns = array_map(function ($match) {
- return $match->pattern;
- }, $result['sequence']);
- $this->assertSame($patterns, $actualPatterns, "zxcvbn result has correct patterns");
+ $actualPatterns = array_map(static fn ($match) => $match->pattern, $result['sequence']);
+ $this->assertSame($patterns, $actualPatterns, 'zxcvbn result has correct patterns');
}
/**
* There's a similar test in DictionaryTest for this as well, but this specific test is for ensuring that the
* user input gets passed from the Zxcvbn class all the way through to the DictionaryMatch function.
*/
- public function testUserDefinedWords()
+ public function testUserDefinedWords(): void
{
$result = $this->zxcvbn->passwordStrength('_wQbgL491', ['PJnD', 'WQBG', 'ZhwZ']);
- $this->assertInstanceOf(DictionaryMatch::class, $result['sequence'][1], "user input match is correct class");
- $this->assertSame('wQbg', $result['sequence'][1]->token, "user input match has correct token");
+ $this->assertInstanceOf(DictionaryMatch::class, $result['sequence'][1], 'user input match is correct class');
+ $this->assertSame('wQbg', $result['sequence'][1]->token, 'user input match has correct token');
}
- public function testMultibyteUserDefinedWords()
+ public function testMultibyteUserDefinedWords(): void
{
$result = $this->zxcvbn->passwordStrength('المفاتيح', ['العربية', 'المفاتيح', 'لوحة']);
- $this->assertInstanceOf(DictionaryMatch::class, $result['sequence'][0], "user input match is correct class");
- $this->assertSame('المفاتيح', $result['sequence'][0]->token, "user input match has correct token");
+ $this->assertInstanceOf(DictionaryMatch::class, $result['sequence'][0], 'user input match is correct class');
+ $this->assertSame('المفاتيح', $result['sequence'][0]->token, 'user input match has correct token');
}
- public function testAddMatcherWillThrowException()
+ public function testAddMatcherWillThrowException(): void
{
$this->expectException(\InvalidArgumentException::class);
+ // @phpstan-ignore-next-line
$this->zxcvbn->addMatcher('invalid className');
-
- $this->expectNotToPerformAssertions();
}
- public function testAddMatcherWillReturnSelf()
+ public function testAddMatcherWillReturnSelf(): void
{
$result = $this->zxcvbn->addMatcher(Bruteforce::class);
diff --git a/test/config/bootstrap.php b/test/config/bootstrap.php
index f408832..1664d4a 100644
--- a/test/config/bootstrap.php
+++ b/test/config/bootstrap.php
@@ -1,7 +1,9 @@