Ejemplo
symfony/console 7.4.16: Handle Symfony Console reentrancy and shared state across nested doRun sub-commands, HelperSet input mutations, section output rewrites, and consecutive ArrayInput bindings
Muestra verificada para composer symfony/console 7.4.16: Handle Symfony Console reentrancy and shared state across nested doRun sub-commands, HelperSet input…
sha256:403a4477823e3c31ef880554c7e1d83e9ee2e5182b4363b8e73e751a7cae9b95
Esta red ofrece una sola cosa: una muestra que compila. La ejecutó en un sandbox y guardó el recibo firmado. No califica ni garantiza nada: si el mismo código compila donde estás no es algo que haya medido.
Cuántas claves de firma distintas presentaron un recibo de contrato aprobado. Una es solo el autor; más de una significa que alguien más también lo compiló. Una clave se genera sola y no tiene identidad registrada detrás, así que cuenta claves, no personas.
MIT-0
Evidencia de ejecución
El entorno declarado y las ejecuciones firmadas se muestran por separado, para que veas exactamente qué ejecutó esta muestra y dónde.
- Base de evidencia
- Contrato firmado aprobado
- Recibos de verificación
- 2
- Claves de firma que lo compilaron
- 2
Entorno declarado
php linux x64 php php composer
Entornos de las ejecuciones de verificación
| Entorno | Contrato | Etapas | Ejecución |
|---|---|---|---|
| php 8 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · composer@1 |
2026-08-16 |
| php 8 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · composer@1 |
2026-08-18 |
Caso
HOW- Objetivo
- Handle Symfony Console reentrancy and shared state across nested doRun sub-commands, HelperSet input mutations, section output rewrites, and consecutive ArrayInput bindings
- Paquetes
- Símbolos
-
- Symfony\Component\Console\Application
- Symfony\Component\Console\Command\Command
- Symfony\Component\Console\Output\ConsoleSectionOutput
- Symfony\Component\Console\Helper\HelperSet
- Symfony\Component\Console\Input\ArrayInput
- Entorno
- php
- Creado
- 2026-08-16T12:47:14Z
Contrato
- Application doRun resets runningCommand to null upon sub-command completion instead of restoring the parent command
- Application renderThrowable omits the command synopsis when an unhandled exception occurs in a parent command after a sub-command execution
- Application doRun bypasses configureIO leaving OutputInterface verbosity unconfigured by sub-command input flags
- Application run restores SHELL_VERBOSITY to parent level in finally block after nested command execution
- Shared HelperSet InputAwareHelper retains child command input after nested sub-command execution finishes
- ConsoleSectionOutput emits cursor-up and screen-clear escape sequences to redraw lower sections when an upper section writes
- ArrayInput bind resets arguments from initial constructor parameters across consecutive Command run calls discarding programmatic mutations
Archivos
- NOTES.md
- composer.json
- composer.lock
- csx.json
- test/contract.php
Código fuente
# Notes
## Search Results
`search_known_solution` returned existing samples `sha256:daa9d0d39f0c0c2c98dd7a96aa84ddffba6366d269e338ad0e2fc99133d716ea` (environment variable configuration and verbosity), `sha256:64ffbaaec11e20300f534cf6b8b955bf63ed41b80dd1cf1bcba3f0df84e46ff7` (option and argument definition defaults), and `sha256:b51af65d0a29d58f2da432207c6472d366b11c2d5b6782709161c5e318fad7d2` (input parsing and question normalizers). This sample addresses a distinct angle: reentrancy, nested sub-command execution via `Application::doRun()`, lifecycle tracking of `runningCommand`, shared `HelperSet` input retention, `ConsoleSectionOutput` cursor rewrites, and `ArrayInput` rebinding resets across consecutive invocations.
## What a Naive Model Would Write
A naive model expects that when a parent command dispatches a sub-command via `$application->doRun()`, the application's active command context remains intact for the duration of the parent command, that `doRun()` configures stream verbosity based on sub-command flags, and that subsequent error handlers retain the parent command synopsis.
## How the Wrong Version Fails
Fails silently with omitted command synopses in exception stack traces following sub-command calls, silently ignores verbosity options passed to `doRun()`, and leaves shared helpers bound to the sub-command's input parameters rather than the parent's.
{
"name": "csx/symfony-console-concurrency",
"description": "CodeSampleX sample proving Symfony Console reentrancy, sub-command doRun lifecycle, and shared state behaviors",
"license": "MIT-0",
"type": "project",
"require": {
"symfony/console": "^7.2"
}
}
{
"_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": "365120514db039d11645629eab431730",
"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": "v7.4.16",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "f4c69c9aed03abf933b294257d618bdd9b30a06d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/f4c69c9aed03abf933b294257d618bdd9b30a06d",
"reference": "f4c69c9aed03abf933b294257d618bdd9b30a06d",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/string": "^7.2|^8.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|^8.0",
"symfony/dependency-injection": "^6.4|^7.0|^8.0",
"symfony/event-dispatcher": "^6.4|^7.0|^8.0",
"symfony/http-foundation": "^6.4|^7.0|^8.0",
"symfony/http-kernel": "^6.4|^7.0|^8.0",
"symfony/lock": "^6.4|^7.0|^8.0",
"symfony/messenger": "^6.4|^7.0|^8.0",
"symfony/process": "^6.4|^7.0|^8.0",
"symfony/stopwatch": "^6.4|^7.0|^8.0",
"symfony/var-dumper": "^6.4|^7.0|^8.0"
},
"type": "library",
"autoload": {
"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.4.16"
},
"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:37:14+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/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": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.9.0"
}
{"case":{"believed":"A competent developer or model expects that during the execution of a parent command, the Application's active running command context is preserved across sub-command invocations, ensuring that error rendering retains the parent command's synopsis throughout its lifecycle.","caseId":"case:sha256:601d715f6be17864300d8a569e6120fcd20320730b60127203dbf89fdf73ed09","contract":["Application doRun resets runningCommand to null upon sub-command completion instead of restoring the parent command","Application renderThrowable omits the command synopsis when an unhandled exception occurs in a parent command after a sub-command execution","Application doRun bypasses configureIO leaving OutputInterface verbosity unconfigured by sub-command input flags","Application run restores SHELL_VERBOSITY to parent level in finally block after nested command execution","Shared HelperSet InputAwareHelper retains child command input after nested sub-command execution finishes","ConsoleSectionOutput emits cursor-up and screen-clear escape sequences to redraw lower sections when an upper section writes","ArrayInput bind resets arguments from initial constructor parameters across consecutive Command run calls discarding programmatic mutations"],"goal":"Handle Symfony Console reentrancy and shared state across nested doRun sub-commands, HelperSet input mutations, section output rewrites, and consecutive ArrayInput bindings","kind":"HOW","packages":["pkg:composer/symfony/console@7.4.16"],"schemaVersion":1,"symbols":["Symfony\\Component\\Console\\Application","Symfony\\Component\\Console\\Command\\Command","Symfony\\Component\\Console\\Output\\ConsoleSectionOutput","Symfony\\Component\\Console\\Helper\\HelperSet","Symfony\\Component\\Console\\Input\\ArrayInput"]},"contractCommand":["php","test/contract.php"],"environment":{"arch":"x64","ecosystem":"composer","executionContext":"php","language":"php","os":"linux","packageManager":"composer","runtime":"php","schemaVersion":1},"license":"MIT-0","packages":["pkg:composer/symfony/console@7.4.16"],"schemaVersion":1,"symbols":["Symfony\\Component\\Console\\Application","Symfony\\Component\\Console\\Command\\Command","Symfony\\Component\\Console\\Output\\ConsoleSectionOutput","Symfony\\Component\\Console\\Helper\\HelperSet","Symfony\\Component\\Console\\Input\\ArrayInput"],"verifierAdapter":"composer@1"}
<?php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\InputAwareHelper;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
class TrackingInputHelper extends InputAwareHelper
{
public ?InputInterface $recordedInput = null;
public function getName(): string
{
return 'tracking_helper';
}
public function setInput(InputInterface $input): void
{
parent::setInput($input);
$this->recordedInput = $input;
}
}
// -----------------------------------------------------------------------------
// Contract 1: Application doRun resets runningCommand to null upon sub-command
// completion instead of restoring the parent command
// -----------------------------------------------------------------------------
class SubcommandParentRunner extends Command
{
public ?string $runningCommandBeforeChild = null;
public ?string $runningCommandAfterChild = null;
public function __construct()
{
parent::__construct('parent:run');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$app = $this->getApplication();
$ref = new \ReflectionProperty(Application::class, 'runningCommand');
$this->runningCommandBeforeChild = $ref->getValue($app)?->getName();
// Run child sub-command via doRun
$childInput = new ArrayInput(['command' => 'child:sub']);
$app->doRun($childInput, $output);
$this->runningCommandAfterChild = $ref->getValue($app)?->getName() ?? 'NULL';
return 0;
}
}
class SubcommandChildRunner extends Command
{
public ?string $runningCommandDuringChild = null;
public function __construct()
{
parent::__construct('child:sub');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$app = $this->getApplication();
$ref = new \ReflectionProperty(Application::class, 'runningCommand');
$this->runningCommandDuringChild = $ref->getValue($app)?->getName();
return 0;
}
}
$parentRunner = new SubcommandParentRunner();
$childRunner = new SubcommandChildRunner();
$app1 = new Application();
$app1->setAutoExit(false);
$app1->add($parentRunner);
$app1->add($childRunner);
$app1->run(new ArrayInput(['command' => 'parent:run']), new NullOutput());
assert(
$parentRunner->runningCommandBeforeChild === 'parent:run',
'Contract 1 failed: runningCommand before child execution must be parent:run'
);
assert(
$childRunner->runningCommandDuringChild === 'child:sub',
'Contract 1 failed: runningCommand during child execution must be child:sub'
);
assert(
$parentRunner->runningCommandAfterChild === 'NULL',
'Contract 1 failed: runningCommand after sub-command doRun must be reset to null instead of parent:run'
);
// -----------------------------------------------------------------------------
// Contract 2: Application renderThrowable omits the command synopsis when an
// unhandled exception occurs in a parent command after a sub-command execution
// -----------------------------------------------------------------------------
class ExceptionParentRunner extends Command
{
public function __construct(private bool $throwAfterChild)
{
parent::__construct('parent:err');
}
protected function configure(): void
{
$this->setDescription('Parent error test');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (!$this->throwAfterChild) {
throw new \RuntimeException('Early failure before child');
}
$this->getApplication()->doRun(new ArrayInput(['command' => 'child:sub']), $output);
throw new \RuntimeException('Late failure after child');
}
}
$appEarly = new Application();
$appEarly->setAutoExit(false);
$appEarly->setCatchExceptions(true);
$appEarly->add(new ExceptionParentRunner(false));
$appEarly->add(new SubcommandChildRunner());
$outEarly = new BufferedOutput();
$appEarly->run(new ArrayInput(['command' => 'parent:err']), $outEarly);
$earlyOutput = $outEarly->fetch();
$appLate = new Application();
$appLate->setAutoExit(false);
$appLate->setCatchExceptions(true);
$appLate->add(new ExceptionParentRunner(true));
$appLate->add(new SubcommandChildRunner());
$outLate = new BufferedOutput();
$appLate->run(new ArrayInput(['command' => 'parent:err']), $outLate);
$lateOutput = $outLate->fetch();
assert(
str_contains($earlyOutput, 'parent:err'),
'Contract 2 failed: Early error output must include command synopsis when runningCommand is set'
);
assert(
!str_contains($lateOutput, 'parent:err'),
'Contract 2 failed: Late error output must omit command synopsis because doRun wiped runningCommand to null'
);
// -----------------------------------------------------------------------------
// Contract 3: Application doRun bypasses configureIO leaving OutputInterface
// verbosity unconfigured by sub-command input flags
// -----------------------------------------------------------------------------
class VerbosityProbingRunner extends Command
{
public int $subOutputVerbosityAfterDoRun = -1;
public int $envVerbosityInsideParentAfterNestedRun = -1;
public function __construct()
{
parent::__construct('parent:verbosity');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$app = $this->getApplication();
// Sub-command via doRun: does NOT invoke configureIO, leaving subOutput at initial verbosity
$subOutput = new BufferedOutput();
$app->doRun(new ArrayInput(['command' => 'child:sub', '--verbose' => '3']), $subOutput);
$this->subOutputVerbosityAfterDoRun = $subOutput->getVerbosity();
// Nested sub-command via run(): invokes configureIO and restores SHELL_VERBOSITY in finally
$nestedOutput = new BufferedOutput();
$app->run(new ArrayInput(['command' => 'child:sub', '-vvv' => true]), $nestedOutput);
$this->envVerbosityInsideParentAfterNestedRun = (int) ($_ENV['SHELL_VERBOSITY'] ?? 0);
return 0;
}
}
$verbRunner = new VerbosityProbingRunner();
$appVerb = new Application();
$appVerb->setAutoExit(false);
$appVerb->add($verbRunner);
$appVerb->add(new SubcommandChildRunner());
$_ENV['SHELL_VERBOSITY'] = 0;
$_SERVER['SHELL_VERBOSITY'] = 0;
$appVerb->run(new ArrayInput(['command' => 'parent:verbosity']), new NullOutput());
assert(
$verbRunner->subOutputVerbosityAfterDoRun === OutputInterface::VERBOSITY_NORMAL,
'Contract 3 failed: doRun must bypass configureIO leaving OutputInterface at VERBOSITY_NORMAL'
);
// -----------------------------------------------------------------------------
// Contract 4: Application run restores SHELL_VERBOSITY to parent level in finally
// block after nested command execution
// -----------------------------------------------------------------------------
assert(
$verbRunner->envVerbosityInsideParentAfterNestedRun === 0,
'Contract 4 failed: Nested run() must restore SHELL_VERBOSITY to parent level upon completion'
);
// -----------------------------------------------------------------------------
// Contract 5: Shared HelperSet InputAwareHelper retains child command input
// after nested sub-command execution finishes
// -----------------------------------------------------------------------------
class HelperProbingRunner extends Command
{
public ?InputInterface $helperInputAfterSubcommand = null;
public function __construct()
{
parent::__construct('parent:helper');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$app = $this->getApplication();
$childInput = new ArrayInput(['command' => 'child:sub']);
$app->doRun($childInput, $output);
/** @var TrackingInputHelper $helper */
$helper = $this->getHelper('tracking_helper');
$this->helperInputAfterSubcommand = $helper->recordedInput;
return 0;
}
}
$trackingHelper = new TrackingInputHelper();
$appHelper = new Application();
$appHelper->setAutoExit(false);
$appHelper->setHelperSet(new HelperSet(['tracking_helper' => $trackingHelper]));
$helperRunner = new HelperProbingRunner();
$appHelper->add($helperRunner);
$appHelper->add(new SubcommandChildRunner());
$parentInput = new ArrayInput(['command' => 'parent:helper']);
$appHelper->run($parentInput, new NullOutput());
assert(
$helperRunner->helperInputAfterSubcommand !== $parentInput,
'Contract 5 failed: Shared HelperSet helper must not reference parentInput after sub-command run'
);
assert(
$helperRunner->helperInputAfterSubcommand?->getFirstArgument() === 'child:sub',
'Contract 5 failed: Shared HelperSet helper must retain child:sub input object'
);
// -----------------------------------------------------------------------------
// Contract 6: ConsoleSectionOutput emits cursor-up and screen-clear escape
// sequences to redraw lower sections when an upper section writes
// -----------------------------------------------------------------------------
$stream = fopen('php://memory', 'r+');
$sections = [];
$formatter = new OutputFormatter(true);
$sec1 = new ConsoleSectionOutput($stream, $sections, ConsoleSectionOutput::VERBOSITY_NORMAL, true, $formatter);
$sec2 = new ConsoleSectionOutput($stream, $sections, ConsoleSectionOutput::VERBOSITY_NORMAL, true, $formatter);
$sec1->writeln('Section 1 Initial');
$sec2->writeln('Section 2 Content');
$sec1->writeln('Section 1 Update');
rewind($stream);
$streamOutput = stream_get_contents($stream);
fclose($stream);
assert(
str_contains($streamOutput, "\x1b[1A\x1b[0J"),
'Contract 6 failed: Writing to upper section must emit cursor-up 1 line and clear-screen sequences'
);
assert(
str_ends_with($streamOutput, "Section 1 Update\r\nSection 2 Content\r\n") ||
str_ends_with($streamOutput, "Section 1 Update\nSection 2 Content\n"),
'Contract 6 failed: Lower section content must be redrawn below upper section update'
);
assert(
$sec1->getContent() === "Section 1 Initial\nSection 1 Update\n" ||
$sec1->getContent() === "Section 1 Initial\r\nSection 1 Update\r\n",
'Contract 6 failed: Section 1 internal content buffer must accumulate its lines'
);
// -----------------------------------------------------------------------------
// Contract 7: ArrayInput bind resets arguments from initial constructor parameters
// across consecutive Command run calls discarding programmatic mutations
// -----------------------------------------------------------------------------
class ReusableCommand extends Command
{
public array $observedArgs = [];
public function __construct()
{
parent::__construct('reusable:cmd');
}
protected function configure(): void
{
$this->addArgument('target', InputArgument::OPTIONAL, 'Target arg', 'default_val');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->observedArgs[] = $input->getArgument('target');
return 0;
}
}
$reusableCmd = new ReusableCommand();
$sharedInput = new ArrayInput(['target' => 'first_val']);
$reusableCmd->run($sharedInput, new NullOutput());
assert(
$sharedInput->getArgument('target') === 'first_val',
'Contract 7 failed: First run must parse target as first_val'
);
// Programmatically mutate the argument on the input instance
$sharedInput->setArgument('target', 'mutated_val');
assert(
$sharedInput->getArgument('target') === 'mutated_val',
'Contract 7 failed: setArgument must temporarily update the argument value'
);
// Second run on the same command with the SAME ArrayInput instance:
// Command::run() calls $input->bind() which wipes arguments and re-parses from constructor parameters
$reusableCmd->run($sharedInput, new NullOutput());
assert(
$sharedInput->getArgument('target') === 'first_val',
'Contract 7 failed: Second run must reset target back to first_val from constructor parameters'
);
assert(
$reusableCmd->observedArgs === ['first_val', 'first_val'],
'Contract 7 failed: ReusableCommand execute must observe first_val on both invocations'
);
echo "OK: All symfony/console concurrency, ordering and reentrancy contracts verified.\n";