Beispiel
symfony/console 8.1.4: Test a symfony/console command with CommandTester, with no process and no terminal
Verifiziertes Beispiel für composer symfony/console 8.1.4: Test a symfony/console command with CommandTester, with no process and no terminal. Der Vertrag…
sha256:a49ac462fc85823519ce8151c25439ec2ac9e9556684d5a165edf4ae9fd8e48e
Dieses Netzwerk bietet eine Sache: ein Sample, das baut. Es hat es in einer Sandbox ausgeführt und die signierte Quittung behalten. Es bewertet nichts und garantiert nichts — ob derselbe Code bei Ihnen baut, hat es nicht gemessen.
Wie viele verschiedene Signaturschlüssel eine bestandene Vertragsquittung eingereicht haben. Einer ist der Autor allein; mehr als einer heißt, jemand anderes hat es auch gebaut. Ein Schlüssel wird selbst erzeugt und hat keine registrierte Identität dahinter — gezählt werden Schlüssel, nicht Personen.
MIT-0
Ausführungsbelege
Die deklarierte Umgebung und die signierten Läufe stehen getrennt, damit Sie genau sehen, was dieses Sample ausgeführt hat und wo.
- Beleggrundlage
- Signierter Vertrag bestanden
- Verifizierungsbelege
- 2
- Signaturschlüssel, die es gebaut haben
- 2
Deklarierte Umgebung
php 8 linux x64 php 8 php composer
Umgebungen der Verifizierungsläufe
| Umgebung | Contract | Stufen | Lauf |
|---|---|---|---|
| php 8 · linux alpine/x64 · docker ed25519:a2ec939a4c60e243 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · composer@1 |
2026-08-14 |
| php 8 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · composer@1 |
2026-08-18 |
Fall
HOW- Ziel
- Test a symfony/console command with CommandTester, with no process and no terminal
- Pakete
- Symbole
-
- Symfony.Component.Console.Tester.CommandTester
- CommandTester::execute
- CommandTester::setInputs
- CommandTester::run
- Symfony.Component.Console.Tester.ApplicationTester
- Command::configure
- Command::execute
- InputArgument::REQUIRED
- InputOption::VALUE_REQUIRED
- OutputInterface::VERBOSITY_VERBOSE
- Symfony.Component.Console.Question.Question
- Umgebung
- php 8
- Erstellt
- 2026-08-14T14:11:03Z
Contract
- assert configure() declares the definition the tester array is bound against: bare keys are arguments and prefixed keys are options, an omitted optional argument falls back to the default declared there, and the command is named by the #[AsCommand] attribute now that Command::getDefaultName is gone
- assert values arrive with the type the array held because there is no argv to stringify them, so an int option stays an int and only a defaulted value carries the type configure() declared
- assert a missing required argument is a console RuntimeException raised by validate() inside Command::run before execute() is entered, that the tester therefore has no status code to read, and that interact() runs earlier in that order than the validation, so a command filling the argument in interact() survives the same empty array
- assert the command name does not belong in the array for a standalone command, that a key without dashes is looked up as an argument name whatever it was meant to be, and that an undeclared option is an InvalidOptionException
- assert an Application is what declares the command argument, that CommandTester supplies the command name once the command is attached to one, and that Application::add was removed in favour of addCommand
- assert getStatusCode is whatever execute() returned, with Command::SUCCESS 0, Command::FAILURE 1 and Command::INVALID 2
- assert getDisplay returns the rendered output and that the decorated flag is what turns an <info> tag into the green ANSI pair
- assert setInputs answers an interactive question, and that omitting it is EOF rather than a hang: a Question with a default answers itself and flips the input to non-interactive, while a Question without one raises MissingInputException
- assert --no-interaction is inert under CommandTester because only Application::configureIO interprets it, so the execute() interactive option or an ApplicationTester run is what actually turns the question off
- assert a writeln at VERBOSITY_VERBOSE prints nothing at default verbosity and that -v in the tester array does not change that, while the verbosity execute() option and an ApplicationTester run both do
- assert CommandTester::run returns an ExecutionResult that carries the status code and keeps output and error output apart, and that the input it kept is ArrayInput's rendering of the array rather than a command line, because the VALUE_NONE option renders with a value that argv rejects, while the legacy execute() path merges the two streams into one display and refuses getErrorOutput without capture_stderr_separately
Dateien
- composer.json
- composer.lock
- csx.json
- phpunit.xml
- src/AskNameCommand.php
- src/ConsoleApp.php
- src/GreetCommand.php
- test/CommandTesterTest.php
Quelltext
{
"name": "csx/symfony-console-tester",
"license": "MIT-0",
"require": {
"symfony/console": "8.1.4"
},
"require-dev": {
"phpunit/phpunit": "13.3.1"
},
"autoload": {
"psr-4": {
"Csx\\": "src/"
}
},
"config": {
"platform": {
"php": "8.5.0"
}
}
}
{
"_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": "532d8e1a0e4b3450d120f618910878c9",
"packages": [
{
"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": "symfony/console",
"version": "v8.1.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "68efa2ebfd9a362951eb5a8b09fd177c66ddec24"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/68efa2ebfd9a362951eb5a8b09fd177c66ddec24",
"reference": "68efa2ebfd9a362951eb5a8b09fd177c66ddec24",
"shasum": ""
},
"require": {
"php": ">=8.4.1",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "^1.0",
"symfony/polyfill-php85": "^1.32",
"symfony/service-contracts": "^2.5|^3",
"symfony/string": "^7.4.6|^8.0.6"
},
"conflict": {
"symfony/dependency-injection": "<8.1",
"symfony/event-dispatcher": "<8.1"
},
"provide": {
"psr/log-implementation": "1.0|2.0|3.0"
},
"require-dev": {
"psr/log": "^1|^2|^3",
"symfony/config": "^7.4|^8.0",
"symfony/dependency-injection": "^8.1",
"symfony/event-dispatcher": "^8.1",
"symfony/filesystem": "^7.4|^8.0",
"symfony/http-foundation": "^7.4|^8.0",
"symfony/http-kernel": "^7.4|^8.0",
"symfony/lock": "^7.4|^8.0",
"symfony/messenger": "^7.4|^8.0",
"symfony/mime": "^7.4|^8.0",
"symfony/process": "^7.4|^8.0",
"symfony/stopwatch": "^7.4|^8.0",
"symfony/uid": "^7.4|^8.0",
"symfony/validator": "^7.4|^8.0",
"symfony/var-dumper": "^7.4|^8.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/v8.1.4"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-07-31T12:43:13+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d",
"reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/contracts",
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "3.7-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.7.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-06-05T06:23:12+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "141046a8f9477948ff284fa65be2095baafb94f2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
"reference": "141046a8f9477948ff284fa65be2095baafb94f2",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-04-10T16:19:22+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.41.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d",
"reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Grapheme\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's grapheme_* functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"grapheme",
"intl",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-07-28T08:25:59+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.38.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b",
"reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b",
"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.38.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-05-25T13:48:31+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.38.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
"reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-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.38.2"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-05-27T06:59:30+00:00"
},
{
"name": "symfony/polyfill-php85",
"version": "v1.41.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php85.git",
"reference": "255fab485aaa1006ed411040c42aecd7b5302d7a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a",
"reference": "255fab485aaa1006ed411040c42aecd7b5302d7a",
"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\\Php85\\": ""
},
"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.5+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-07-01T12:47:55+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v3.7.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0",
"reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0",
"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.7-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.7.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-06-16T09:55:08+00:00"
},
{
"name": "symfony/string",
"version": "v8.1.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc",
"reference": "286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc",
"shasum": ""
},
"require": {
"php": ">=8.4.1",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-intl-grapheme": "^1.33",
"symfony/polyfill-intl-normalizer": "^1.0",
"symfony/polyfill-mbstring": "^1.0"
},
"conflict": {
"symfony/translation-contracts": "<2.5"
},
"require-dev": {
"symfony/emoji": "^7.4|^8.0",
"symfony/http-client": "^7.4|^8.0",
"symfony/intl": "^7.4|^8.0",
"symfony/translation-contracts": "^2.5|^3.0",
"symfony/var-exporter": "^7.4|^8.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/v8.1.2"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-07-28T07:35:25+00:00"
}
],
"packages-dev": [
{
"name": "myclabs/deep-copy",
"version": "1.14.0",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae",
"reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae",
"shasum": ""
},
"require": {
"php": "^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.14.0"
},
"funding": [
{
"url": "https://github.com/mnapoli",
"type": "github"
}
],
"time": "2026-08-11T10:17:44+00:00"
},
{
"name": "nikic/php-parser",
"version": "v5.8.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f",
"reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f",
"shasum": ""
},
"require": {
"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.x-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.8.0"
},
"time": "2026-07-04T14:30:18+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": "phpunit/php-code-coverage",
"version": "14.3.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "060a5c93e81afb01e97dbe8930a5d0c9bd46e54e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/060a5c93e81afb01e97dbe8930a5d0c9bd46e54e",
"reference": "060a5c93e81afb01e97dbe8930a5d0c9bd46e54e",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^5.8.0",
"php": ">=8.4",
"phpunit/php-text-template": "^6.0",
"sebastian/complexity": "^6.0",
"sebastian/environment": "^9.3.2",
"sebastian/git-state": "^1.0",
"sebastian/lines-of-code": "^5.0.2",
"sebastian/version": "^7.0",
"theseer/tokenizer": "^2.0.1"
},
"require-dev": {
"phpunit/phpunit": "^13.3"
},
"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": "14.3.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/14.3.0"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage",
"type": "tidelift"
}
],
"time": "2026-08-07T07:24:15+00:00"
},
{
"name": "phpunit/php-file-iterator",
"version": "7.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "3ccaa29123548190af12fee7af078dcd7f3ddfab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3ccaa29123548190af12fee7af078dcd7f3ddfab",
"reference": "3ccaa29123548190af12fee7af078dcd7f3ddfab",
"shasum": ""
},
"require": {
"php": ">=8.4"
},
"require-dev": {
"phpunit/phpunit": "^13.3.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": "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/7.0.1"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator",
"type": "tidelift"
}
],
"time": "2026-08-10T06:43:12+00:00"
},
{
"name": "phpunit/php-invoker",
"version": "7.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-invoker.git",
"reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88",
"reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88",
"shasum": ""
},
"require": {
"php": ">=8.4"
},
"require-dev": {
"ext-pcntl": "*",
"phpunit/phpunit": "^13.0"
},
"suggest": {
"ext-pcntl": "*"
},
"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": "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/7.0.0"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/php-invoker",
"type": "tidelift"
}
],
"time": "2026-02-06T04:34:47+00:00"
},
{
"name": "phpunit/php-text-template",
"version": "6.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-text-template.git",
"reference": "a47af19f93f76aa3368303d752aa5272ca3299f4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/a47af19f93f76aa3368303d752aa5272ca3299f4",
"reference": "a47af19f93f76aa3368303d752aa5272ca3299f4",
"shasum": ""
},
"require": {
"php": ">=8.4"
},
"require-dev": {
"phpunit/phpunit": "^13.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",
"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/6.0.0"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template",
"type": "tidelift"
}
],
"time": "2026-02-06T04:36:37+00:00"
},
{
"name": "phpunit/php-timer",
"version": "9.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
"reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a0e12065831f6ab0d83120dc61513eb8d9a966f6",
"reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6",
"shasum": ""
},
"require": {
"php": ">=8.4"
},
"require-dev": {
"phpunit/phpunit": "^13.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "9.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/9.0.0"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/php-timer",
"type": "tidelift"
}
],
"time": "2026-02-06T04:37:53+00:00"
},
{
"name": "phpunit/phpunit",
"version": "13.3.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "fc024931d6ad047404e9d86536735923fe63a06b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc024931d6ad047404e9d86536735923fe63a06b",
"reference": "fc024931d6ad047404e9d86536735923fe63a06b",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-filter": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.14.0",
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.4.1",
"phpunit/php-code-coverage": "^14.3",
"phpunit/php-file-iterator": "^7.0.1",
"phpunit/php-invoker": "^7.0.0",
"phpunit/php-text-template": "^6.0.0",
"phpunit/php-timer": "^9.0.0",
"sebastian/cli-parser": "^5.0.1",
"sebastian/comparator": "^8.4",
"sebastian/diff": "^9.0",
"sebastian/environment": "^9.3.2",
"sebastian/exporter": "^8.2.1",
"sebastian/file-filter": "^1.0",
"sebastian/git-state": "^1.0",
"sebastian/global-state": "^9.0.1",
"sebastian/object-enumerator": "^8.1.0",
"sebastian/recursion-context": "^8.0.1",
"sebastian/type": "^7.0.2",
"sebastian/version": "^7.0.0",
"staabm/side-effects-detector": "^1.0.5"
},
"bin": [
"phpunit"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "13.3-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/13.3.1"
},
"funding": [
{
"url": "https://phpunit.de/sponsoring.html",
"type": "other"
}
],
"time": "2026-08-13T13:14:23+00:00"
},
{
"name": "sebastian/cli-parser",
"version": "5.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/cli-parser.git",
"reference": "eeb759ad3146b7096fb59c3195d39e071cd409e3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/eeb759ad3146b7096fb59c3195d39e071cd409e3",
"reference": "eeb759ad3146b7096fb59c3195d39e071cd409e3",
"shasum": ""
},
"require": {
"php": ">=8.4"
},
"require-dev": {
"phpunit/phpunit": "^13.2.6"
},
"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 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/5.0.1"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser",
"type": "tidelift"
}
],
"time": "2026-08-01T04:27:14+00:00"
},
{
"name": "sebastian/comparator",
"version": "8.4.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "3b070e608146cba00fd6fd1f0ffba89e5a8897fb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/3b070e608146cba00fd6fd1f0ffba89e5a8897fb",
"reference": "3b070e608146cba00fd6fd1f0ffba89e5a8897fb",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-mbstring": "*",
"php": ">=8.4",
"sebastian/diff": "^9.0",
"sebastian/exporter": "^8.2"
},
"require-dev": {
"phpunit/phpunit": "^13.3"
},
"suggest": {
"ext-bcmath": "For comparing BcMath\\Number objects"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "8.4-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/8.4.0"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
"type": "tidelift"
}
],
"time": "2026-08-07T07:23:13+00:00"
},
{
"name": "sebastian/complexity",
"version": "6.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git",
"reference": "c5651c795c98093480df79350cb050813fc7a2f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c5651c795c98093480df79350cb050813fc7a2f3",
"reference": "c5651c795c98093480df79350cb050813fc7a2f3",
"shasum": ""
},
"require": {
"nikic/php-parser": "^5.0",
"php": ">=8.4"
},
"require-dev": {
"phpunit/phpunit": "^13.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",
"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/6.0.0"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/complexity",
"type": "tidelift"
}
],
"time": "2026-02-06T04:41:32+00:00"
},
{
"name": "sebastian/diff",
"version": "9.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "a3fb6a298a265ff487a91bbea46e03cd01dbb226"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/a3fb6a298a265ff487a91bbea46e03cd01dbb226",
"reference": "a3fb6a298a265ff487a91bbea46e03cd01dbb226",
"shasum": ""
},
"require": {
"php": ">=8.4"
},
"require-dev": {
"phpunit/phpunit": "^13.2",
"symfony/process": "^7.4.13"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "9.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/9.0.0"
},
"fundin
{"case":{"caseId":"case:sha256:56c5c139ead3b0a968c078c59145e578a27a2b37636d5aab2b6d1f276bb8a340","constraints":{"runtime":"php"},"contract":["assert configure() declares the definition the tester array is bound against: bare keys are arguments and prefixed keys are options, an omitted optional argument falls back to the default declared there, and the command is named by the #[AsCommand] attribute now that Command::getDefaultName is gone","assert values arrive with the type the array held because there is no argv to stringify them, so an int option stays an int and only a defaulted value carries the type configure() declared","assert a missing required argument is a console RuntimeException raised by validate() inside Command::run before execute() is entered, that the tester therefore has no status code to read, and that interact() runs earlier in that order than the validation, so a command filling the argument in interact() survives the same empty array","assert the command name does not belong in the array for a standalone command, that a key without dashes is looked up as an argument name whatever it was meant to be, and that an undeclared option is an InvalidOptionException","assert an Application is what declares the command argument, that CommandTester supplies the command name once the command is attached to one, and that Application::add was removed in favour of addCommand","assert getStatusCode is whatever execute() returned, with Command::SUCCESS 0, Command::FAILURE 1 and Command::INVALID 2","assert getDisplay returns the rendered output and that the decorated flag is what turns an \u003cinfo\u003e tag into the green ANSI pair","assert setInputs answers an interactive question, and that omitting it is EOF rather than a hang: a Question with a default answers itself and flips the input to non-interactive, while a Question without one raises MissingInputException","assert --no-interaction is inert under CommandTester because only Application::configureIO interprets it, so the execute() interactive option or an ApplicationTester run is what actually turns the question off","assert a writeln at VERBOSITY_VERBOSE prints nothing at default verbosity and that -v in the tester array does not change that, while the verbosity execute() option and an ApplicationTester run both do","assert CommandTester::run returns an ExecutionResult that carries the status code and keeps output and error output apart, and that the input it kept is ArrayInput's rendering of the array rather than a command line, because the VALUE_NONE option renders with a value that argv rejects, while the legacy execute() path merges the two streams into one display and refuses getErrorOutput without capture_stderr_separately"],"goal":"Test a symfony/console command with CommandTester, with no process and no terminal","kind":"HOW","packages":["pkg:composer/symfony/console@8.1.4"],"schemaVersion":1,"symbols":["Symfony.Component.Console.Tester.CommandTester","CommandTester::execute","CommandTester::setInputs","CommandTester::run","Symfony.Component.Console.Tester.ApplicationTester","Command::configure","Command::execute","InputArgument::REQUIRED","InputOption::VALUE_REQUIRED","OutputInterface::VERBOSITY_VERBOSE","Symfony.Component.Console.Question.Question"]},"contractCommand":["php","vendor/bin/phpunit","--colors=never"],"environment":{"arch":"x64","ecosystem":"composer","executionContext":"php","language":"php","os":"linux","packageManager":"composer","runtime":"php","runtimeVersion":"8","schemaVersion":1},"license":"MIT-0","packages":["pkg:composer/symfony/console@8.1.4"],"schemaVersion":1,"symbols":["Symfony.Component.Console.Tester.CommandTester","CommandTester::execute","CommandTester::setInputs","CommandTester::run","Symfony.Component.Console.Tester.ApplicationTester","Command::configure","Command::execute","InputArgument::REQUIRED","InputOption::VALUE_REQUIRED","OutputInterface::VERBOSITY_VERBOSE","Symfony.Component.Console.Question.Question"],"verifierAdapter":"composer@1"}
<?xml version="1.0"?>
<phpunit bootstrap="vendor/autoload.php" colors="false" recordTestRunHistory="false">
<testsuites><testsuite name="csx"><directory>test</directory></testsuite></testsuites>
</phpunit>
<?php
namespace Csx;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
/**
* A command that asks a question, so the interactive half of CommandTester can
* be pinned down.
*
* The received wisdom is that forgetting setInputs() hangs the test on STDIN.
* It does not: CommandTester always attaches an in-memory stream to the input,
* built from whatever setInputs() was given, so an unanswered question reads
* EOF immediately. What happens next depends entirely on the Question's
* default. With no default, QuestionHelper::ask() rethrows
* MissingInputException and the test fails loudly. With a default, ask()
* catches that exception, flips the input to non-interactive and returns the
* default — the test goes green while the question was never really answered,
* which is the failure mode worth being able to recognise.
*
* Asking on purpose for the default is a different code path with the same
* result: an input that is already non-interactive never touches the stream.
* Under CommandTester that state comes from the execute() options or the
* constructor, not from --no-interaction, which needs an Application to be
* interpreted.
*
* The helper is instantiated directly rather than fetched with
* $this->getHelper('question'), which needs a HelperSet the command only has
* once an Application has given it one.
*/
#[AsCommand(name: 'app:ask', description: 'Asks for a name')]
final class AskNameCommand extends Command
{
/**
* @param string|null $default the answer used when the question is not
* answered; null makes an unanswered question
* an error instead of a silent pass
*/
public function __construct(private readonly ?string $default = 'anonymous')
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$answer = (new QuestionHelper())
->ask($input, $output, new Question('Your name? ', $this->default));
$output->writeln('greeting '.($answer ?? 'nobody'));
// Read after the question, because ask() rewrites it: an interactive
// input that hit EOF comes back non-interactive.
$output->writeln('interactive: '.var_export($input->isInteractive(), true));
return self::SUCCESS;
}
}
<?php
namespace Csx;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
/**
* Builds the Application the tests use, with the two settings that a test
* harness needs and a real console entry point does not.
*
* setAutoExit(false) stops Application::run() from calling exit() with the
* status code, which would take PHPUnit down with it. setCatchExceptions(false)
* stops it from rendering an exception as a pretty error block and returning a
* non-zero status, so a broken invocation reaches the test as a throwable
* instead of as formatted text.
*
* add() was removed in symfony/console 8.0; addCommand() is the replacement,
* and it is what every pre-7.4 snippet gets wrong.
*/
final class ConsoleApp
{
public static function build(Command ...$commands): Application
{
$application = new Application('csx', '1.0.0');
$application->setAutoExit(false);
$application->setCatchExceptions(false);
foreach ($commands as $command) {
$application->addCommand($command);
}
return $application;
}
}
<?php
namespace Csx;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Testing a symfony/console command with CommandTester, and the ways a green
* test can disagree with the real command line.
*
* CommandTester starts no process and opens no terminal. It builds an
* ArrayInput, binds it against the definition configure() declared, and calls
* Command::run() in-process. Everything below follows from that.
*
* The array is a definition-level array, not a command line. Arguments are
* bare keys, options are written with the "--" of their long name or the "-"
* of their shortcut, and the command name is not in it: a standalone command
* has no "command" argument, so ['command' => 'app:greet'] is an
* InvalidArgumentException, and a key without dashes is read as an argument
* name, so ['shout' => true] fails the same way. The name only becomes legal
* once the command belongs to an Application, because the application
* definition is what declares that argument — and at that point CommandTester
* supplies it for you.
*
* Nothing is stringified. A real argv hands you strings; ArrayInput stores
* whatever the array held, so ['--times' => 2] gives you the int back and a
* test can pass on an int that the shell would have delivered as "2". Only a
* default keeps its declared type in both worlds.
*
* Command::run() has a fixed order: bind, initialize, interact (interactive
* runs only), fill in the command argument, validate, execute. So a missing
* required argument is a RuntimeException raised before execute() is entered
* at all, and interact() runs before validate(), which is what makes the usual
* "prompt for the argument the user forgot" recipe legal.
*
* The flags an application would interpret are inert here. --no-interaction,
* -v, -q and --ansi are turned into behaviour by Application::configureIO(),
* which only runs inside Application::run(). Command::run() binds them against
* the merged definition and then ignores them, so passing '-v' to
* CommandTester::execute() leaves the output at VERBOSITY_NORMAL and the
* verbose line below still disappears. Verbosity, interactivity and decoration
* live on the tester's second argument, or on an ApplicationTester.
*
* Errors belong on the error output, and the two tester APIs disagree about
* where that goes. The legacy execute() path builds a plain StreamOutput, so
* the instanceof check below is false and everything lands in one buffer;
* getErrorOutput() there is a LogicException unless capture_stderr_separately
* is set. The 8.1 run() path builds a TestOutput, which is a
* ConsoleOutputInterface, and its ExecutionResult keeps the two streams apart.
*/
#[AsCommand(name: 'app:greet', description: 'Greets someone by name')]
final class GreetCommand extends Command
{
/** Proof of whether execute() was reached, for the validation-order test. */
public int $executeCalls = 0;
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'Who to greet')
->addArgument('salutation', InputArgument::OPTIONAL, 'Word to greet with', 'Hello')
->addOption('shout', null, InputOption::VALUE_NONE, 'Upper-case the greeting')
// The default is an int on purpose: a value that arrives through
// the definition keeps the type declared here, while a value that
// arrives through the input keeps the type the caller wrote.
->addOption('times', 't', InputOption::VALUE_REQUIRED, 'How many greetings', 1)
->addOption('fail', null, InputOption::VALUE_NONE, 'Return Command::FAILURE');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
++$this->executeCalls;
// The line people go looking for when they say their logging vanished.
// writeln() drops the message whenever the *output* is below
// VERBOSITY_VERBOSE, and no input flag raises it under CommandTester.
$output->writeln(
'times resolved to '.var_export($input->getOption('times'), true),
OutputInterface::VERBOSITY_VERBOSE
);
$greeting = $input->getArgument('salutation').', '.$input->getArgument('name');
if ($input->getOption('shout')) {
$greeting = strtoupper($greeting).'!';
}
for ($i = 0; $i < (int) $input->getOption('times'); ++$i) {
$output->writeln('<info>'.$greeting.'</info>');
}
if ($input->getOption('fail')) {
$this->errorOutput($output)->writeln('greeting failed');
return self::FAILURE;
}
return self::SUCCESS;
}
/**
* The documented way to write to stderr from a command. Whether it is a
* separate stream depends on which output the tester handed over.
*/
private function errorOutput(OutputInterface $output): OutputInterface
{
return $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
}
}
<?php
use Csx\AskNameCommand;
use Csx\ConsoleApp;
use Csx\GreetCommand;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\InvalidOptionException;
use Symfony\Component\Console\Exception\MissingInputException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Tester\ApplicationTester;
use Symfony\Component\Console\Tester\CommandTester;
final class CommandTesterTest extends TestCase
{
public function testConfigureDeclaresTheInputThatTheTesterArrayIsBoundAgainst(): void
{
$command = new GreetCommand();
$tester = new CommandTester($command);
$status = $tester->execute(['name' => 'World', '--times' => 2, '--shout' => true]);
$this->assertSame(Command::SUCCESS, $status);
$this->assertSame("HELLO, WORLD!\nHELLO, WORLD!\n", $tester->getDisplay(true));
// The optional argument's default came from configure(), not the array.
$this->assertSame('Hello', $tester->getInput()->getArgument('salutation'));
$this->assertTrue($tester->getInput()->getOption('shout'));
// The name is on the #[AsCommand] attribute; the static $defaultName
// property and its getters were removed in symfony/console 8.0.
$this->assertSame('app:greet', $command->getName());
$this->assertFalse(method_exists(Command::class, 'getDefaultName'));
$this->assertFalse(method_exists(Command::class, 'getDefaultDescription'));
}
public function testValuesKeepTheTypeTheArrayHeldBecauseThereIsNoArgv(): void
{
// No process means no argv, and ArrayInput stores what it was given
// rather than the strings a shell would have produced. So this passes
// on an int that the real command line could never deliver.
$given = new CommandTester(new GreetCommand());
$given->execute(['name' => 'World', '--times' => 2]);
$this->assertSame(2, $given->getInput()->getOption('times'));
$viaShortcut = new CommandTester(new GreetCommand());
$viaShortcut->execute(['name' => 'World', '-t' => '2']);
$this->assertSame('2', $viaShortcut->getInput()->getOption('times'));
// Only a defaulted value has the type configure() declared, in tests
// and on the command line alike.
$defaulted = new CommandTester(new GreetCommand());
$defaulted->execute(['name' => 'World']);
$this->assertSame(1, $defaulted->getInput()->getOption('times'));
}
public function testAMissingRequiredArgumentThrowsBeforeExecuteIsEntered(): void
{
$command = new GreetCommand();
$tester = new CommandTester($command);
try {
$tester->execute([]);
$this->fail('a missing required argument should have thrown');
} catch (RuntimeException $exception) {
// Command::run() binds, initializes, interacts, and only then
// validates — all before execute(). This is $input->validate()
// talking, not the command.
$this->assertSame('Not enough arguments (missing: "name").', $exception->getMessage());
$this->assertInstanceOf(ExceptionInterface::class, $exception);
}
$this->assertSame(0, $command->executeCalls);
// And nothing ran, so there is no status code to read: the tester's
// state is only written after Command::run() returns. Caught into a
// variable rather than with fail() in the try block, because PHPUnit's
// AssertionFailedError is itself a \RuntimeException and a catch that
// wide would swallow it.
$uninitialized = null;
try {
$tester->getStatusCode();
} catch (\RuntimeException $exception) {
$uninitialized = $exception;
}
$this->assertNotNull($uninitialized, 'reading the status code should have thrown');
$this->assertStringContainsString('Status code not initialized', $uninitialized->getMessage());
$this->assertNotInstanceOf(ExceptionInterface::class, $uninitialized);
// interact() sits earlier in that order than validate(), which is what
// makes the "prompt for the argument the user forgot" recipe legal: the
// same empty array that threw above gets through here.
$prompting = new class extends Command {
protected function configure(): void
{
$this->setName('app:prompt')->addArgument('name', InputArgument::REQUIRED);
}
protected function interact(InputInterface $input, OutputInterface $output): void
{
if (null === $input->getArgument('name')) {
$input->setArgument('name', 'asked for');
}
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('name='.$input->getArgument('name'));
return self::SUCCESS;
}
};
$prompted = new CommandTester($prompting);
$prompted->execute([]);
$this->assertSame("name=asked for\n", $prompted->getDisplay(true));
}
public function testTheCommandNameIsNotPartOfTheArrayForAStandaloneCommand(): void
{
// The trap. A standalone command's definition holds only what
// configure() declared, so the command name is an argument that does
// not exist — the CLI-shaped call is the one that fails.
try {
(new CommandTester(new GreetCommand()))->execute(['command' => 'app:greet', 'name' => 'World']);
$this->fail('the command name should not be accepted');
} catch (InvalidArgumentException $exception) {
$this->assertSame('The "command" argument does not exist.', $exception->getMessage());
}
// Same mechanism, and the reason the error reads oddly: ArrayInput
// decides by the leading dashes, so a key without them is looked up as
// an argument name whatever it was meant to be.
try {
(new CommandTester(new GreetCommand()))->execute(['name' => 'World', 'shout' => true]);
$this->fail('an option written without dashes should not be accepted');
} catch (InvalidArgumentException $exception) {
$this->assertSame('The "shout" argument does not exist.', $exception->getMessage());
}
try {
(new CommandTester(new GreetCommand()))->execute(['name' => 'World', '--nope' => true]);
$this->fail('an undeclared option should not be accepted');
} catch (InvalidOptionException $exception) {
$this->assertSame('The "--nope" option does not exist.', $exception->getMessage());
}
}
public function testAnApplicationDeclaresTheCommandArgumentAndTheTesterSuppliesIt(): void
{
$command = new GreetCommand();
ConsoleApp::build($command);
// Attached to an application, Command::run() merges the application
// definition in, so the "command" argument exists — and CommandTester
// fills it with the command's own name when the array omits it.
$tester = new CommandTester($command);
$tester->execute(['name' => 'World']);
$this->assertSame('app:greet', $tester->getInput()->getArgument('command'));
// Which is why the same array that failed above is fine here. The
// difference is the application, not the tester.
$explicit = new CommandTester($command);
$explicit->execute(['command' => 'app:greet', 'name' => 'World']);
$this->assertSame(Command::SUCCESS, $explicit->getStatusCode());
// Registering it is addCommand(); add() was removed in 8.0.
$this->assertFalse(method_exists(Application::class, 'add'));
$this->assertTrue(method_exists(Application::class, 'addCommand'));
}
public function testTheStatusCodeIsWhateverExecuteReturned(): void
{
$tester = new CommandTester(new GreetCommand());
$status = $tester->execute(['name' => 'World', '--fail' => true]);
$this->assertSame(1, $status);
$this->assertSame($status, $tester->getStatusCode());
$tester->assertCommandFailed();
$ok = new CommandTester(new GreetCommand());
$ok->execute(['name' => 'World']);
$this->assertSame(0, $ok->getStatusCode());
$ok->assertCommandIsSuccessful();
// The constants are the exit codes, so asserting on the constant and
// asserting on the number are the same assertion.
$this->assertSame(0, Command::SUCCESS);
$this->assertSame(1, Command::FAILURE);
$this->assertSame(2, Command::INVALID);
}
public function testDecoratedIsWhatDecidesWhetherTheDisplayHasAnsiCodes(): void
{
// getDisplay() is the rendered output, tags and all: the formatter has
// already run, so what a test sees is what a terminal would print.
$plain = new CommandTester(new GreetCommand());
$plain->execute(['name' => 'World']);
$this->assertSame("Hello, World\n", $plain->getDisplay(true));
// Decoration is off by default in the tester, which is why assertions
// on plain substrings normally work. Turn it on and <info> becomes the
// green pair, so the same assertion stops matching.
$decorated = new CommandTester(new GreetCommand());
$decorated->execute(['name' => 'World'], ['decorated' => true]);
$this->assertSame("\033[32mHello, World\033[39m\n", $decorated->getDisplay(true));
}
public function testSetInputsAnswersTheQuestionAndAnUnansweredOneIsEofNotAHang(): void
{
$answered = new CommandTester(new AskNameCommand());
$answered->setInputs(['Ada']);
$answered->execute([]);
$this->assertStringContainsString('Your name? ', $answered->getDisplay());
$this->assertStringContainsString('greeting Ada', $answered->getDisplay());
$this->assertStringContainsString('interactive: true', $answered->getDisplay());
// Measured, against the folklore that forgetting setInputs() blocks on
// STDIN: CommandTester always attaches an in-memory stream, so the
// question is asked, reads EOF, and the Question's default answers it.
// The test goes green having proved nothing about the prompt.
$unanswered = new CommandTester(new AskNameCommand());
$unanswered->execute([]);
$this->assertStringContainsString('Your name? ', $unanswered->getDisplay());
$this->assertStringContainsString('greeting anonymous', $unanswered->getDisplay());
// The rescue is visible: ask() flips the input to non-interactive
// before it returns the default.
$this->assertStringContainsString('interactive: false', $unanswered->getDisplay());
// With no default there is nothing to fall back to, so the same
// omission fails loudly instead.
$strict = new CommandTester(new AskNameCommand(null));
try {
$strict->execute([]);
$this->fail('an unanswerable question should have thrown');
} catch (MissingInputException $exception) {
$this->assertSame('Aborted.', $exception->getMessage());
$this->assertInstanceOf(RuntimeException::class, $exception);
}
}
public function testNoInteractionIsOnlyInterpretedInsideAnApplicationRun(): void
{
$command = new AskNameCommand();
ConsoleApp::build($command);
// Measured, and the opposite of what the flag's name promises:
// --no-interaction is turned into behaviour by
// Application::configureIO(), which Command::run() never calls. The
// option binds, the value is there, and the question is still asked.
$ignored = new CommandTester($command);
$ignored->execute(['--no-interaction' => true]);
$this->assertTrue($ignored->getInput()->getOption('no-interaction'));
$this->assertStringContainsString('Your name? ', $ignored->getDisplay());
// The execute() option is what a CommandTester listens to.
$off = new CommandTester($command);
$off->execute([], ['interactive' => false]);
$this->assertStringNotContainsString('Your name? ', $off->getDisplay());
$this->assertStringContainsString('greeting anonymous', $off->getDisplay());
// Run the whole application and the flag means what it says, because
// now configureIO() has seen it.
$appTester = new ApplicationTester(ConsoleApp::build(new AskNameCommand()));
$appTester->run(['command' => 'app:ask', '--no-interaction' => true]);
$this->assertStringNotContainsString('Your name? ', $appTester->getDisplay());
$this->assertStringContainsString('greeting anonymous', $appTester->getDisplay());
}
public function testAVerboseWritelnNeedsOutputVerbosityNotAnInputFlag(): void
{
// Where "my log line disappeared" comes from: writeln() with a
// verbosity argument writes nothing at all at VERBOSITY_NORMAL.
$normal = new CommandTester(new GreetCommand());
$normal->execute(['name' => 'World']);
$this->assertStringContainsString('Hello, World', $normal->getDisplay());
$this->assertStringNotContainsString('times resolved to', $normal->getDisplay());
$this->assertSame(OutputInterface::VERBOSITY_NORMAL, $normal->getOutput()->getVerbosity());
$verbose = new CommandTester(new GreetCommand());
$verbose->execute(['name' => 'World'], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]);
$this->assertStringContainsString('times resolved to 1', $verbose->getDisplay());
// -v in the array is the same dead end as --no-interaction: bound
// against the merged application definition, then ignored.
$command = new GreetCommand();
ConsoleApp::build($command);
$dashV = new CommandTester($command);
$dashV->execute(['name' => 'World', '-v' => true]);
$this->assertTrue($dashV->getInput()->getOption('verbose'));
$this->assertSame(OutputInterface::VERBOSITY_NORMAL, $dashV->getOutput()->getVerbosity());
$this->assertStringContainsString('Hello, World', $dashV->getDisplay());
$this->assertStringNotContainsString('times resolved to', $dashV->getDisplay());
$appTester = new ApplicationTester(ConsoleApp::build(new GreetCommand()));
$appTester->run(['command' => 'app:greet', 'name' => 'World', '-v' => true]);
$this->assertStringContainsString('times resolved to 1', $appTester->getDisplay());
}
public function testRunReturnsAnExecutionResultThatKeepsTheStreamsApart(): void
{
// The 8.1 result-based API. execute() is now the legacy stateful one:
// it hands the command a single StreamOutput, so a command writing to
// getErrorOutput() has nowhere separate to write.
$legacy = new CommandTester(new GreetCommand());
$legacy->execute(['name' => 'World', '--fail' => true]);
$this->assertSame("Hello, World\ngreeting failed\n", $legacy->getDisplay(true));
try {
$legacy->getErrorOutput();
$this->fail('the legacy path has no separate error output by default');
} catch (LogicException $exception) {
$this->assertStringContainsString('capture_stderr_separately', $exception->getMessage());
}
// run() hands over a TestOutput, which is a ConsoleOutputInterface, so
// the two streams exist and the result exposes both plus the combined
// display. No option to remember, and no state read back off the
// tester.
$result = (new CommandTester(new GreetCommand()))->run(['name' => 'World', '--fail' => true]);
$this->assertSame(Command::FAILURE, $result->statusCode);
$this->assertSame("Hello, World\n", $result->getOutput());
$this->assertSame("greeting failed\n", $result->getErrorOutput());
$this->assertSame("Hello, World\ngreeting failed\n", $result->getDisplay());
// The result also keeps the input, as the string dump() prints after
// "CLI: ". It reads like a command line and is not one: ArrayInput
// renders a VALUE_NONE option with a value, and argv rejects exactly
// that, which is the definition-level/command-line gap in one string.
$this->assertSame('World --fail=1', $result->input);
try {
new ArgvInput(['bin/console', 'World', '--fail=1'], (new GreetCommand())->getDefinition());
$this->fail('argv should not accept a value for a VALUE_NONE option');
} catch (RuntimeException $exception) {
$this->assertSame('The "--fail" option does not accept a value.', $exception->getMessage());
}
}
}