Beispiel
symfony/console v8.1.4: Normalize default answers and validate responses consistently across interactive and non-interactive QuestionHelper execution
Verifiziertes Beispiel für composer symfony/console v8.1.4: Normalize default answers and validate responses consistently across interactive and…
sha256:98593c84d6a1d030ce58808015e6b1d236cb9e48d43aac49bc03c125bab0c57b
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 linux x64 php php composer
Umgebungen der Verifizierungsläufe
| Umgebung | Contract | Stufen | Lauf |
|---|---|---|---|
| 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 |
Fall
HOW- Ziel
- Normalize default answers and validate responses consistently across interactive and non-interactive QuestionHelper execution
- Pakete
- Symbole
-
- Symfony\Component\Console\Helper\QuestionHelper
- Symfony\Component\Console\Helper\QuestionHelper::ask
- Symfony\Component\Console\Question\Question
- Symfony\Component\Console\Question\Question::setNormalizer
- Symfony\Component\Console\Question\Question::setValidator
- Umgebung
- php
- Erstellt
- 2026-08-16T14:41:09Z
Contract
- QuestionHelper::ask() in non-interactive mode completely bypasses Question::setNormalizer(), returning the unnormalized default value and passing the unnormalized type directly to the validator.
- In interactive mode accepting the default value executes Question::setNormalizer() before passing the result to Question::setValidator()
- Non-interactive QuestionHelper execution passes the raw unnormalized default to validators, throwing when validators enforce types produced by normalizers
- Questions without validators return the normalized string in interactive mode but return the raw unnormalized string in non-interactive mode
Dateien
- NOTES.md
- composer.json
- composer.lock
- csx.json
- test/contract.php
Quelltext
# Notes
## Network Search Result
A previous sample was found in the network for `symfony/console`: `sha256:89afc201c4bfa004148a13da413c0cb0fe77d5d9ca4e6b45a5bec301d7080e69` (Goal: Accurately calculate Table column widths and wrapping when combining TableCell colspan with per-column setColumnMaxWidth constraints). This sample covers a completely different feature interaction between `Question::setNormalizer()`, `Question::setValidator()`, and non-interactive execution in `QuestionHelper`.
## What a Model Would Have Written Instead
A model assumes that configuring `Question::setNormalizer()` normalizes the question's default value consistently whenever the default is used, both when accepting the default in interactive mode (pressing Enter) and when running non-interactively (`--no-interaction` or `$input->setInteractive(false)`).
## How the Wrong Version Fails
Code relying on normalizers fails loudly with a `RuntimeException` or `TypeError` in non-interactive CI environments when validators expect normalized types (such as integers or parsed arrays), or fails silently with a green build returning raw untrimmed/unnormalized string values to downstream business logic.
{
"require": {
"symfony/console": "8.1.4"
}
}
{
"_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": "f5b9dc8181c75e9e89cb7b202937cedd",
"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": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.9.0"
}
{"case":{"believed":"QuestionHelper applies configured Question normalizers to default values in non-interactive execution before validating and returning the response.","caseId":"case:sha256:c01cf599507af5f6610a41d1106ce46ac642ce8f7e97350b51ac0d80e4970cad","contract":["QuestionHelper::ask() in non-interactive mode completely bypasses Question::setNormalizer(), returning the unnormalized default value and passing the unnormalized type directly to the validator.","In interactive mode accepting the default value executes Question::setNormalizer() before passing the result to Question::setValidator()","Non-interactive QuestionHelper execution passes the raw unnormalized default to validators, throwing when validators enforce types produced by normalizers","Questions without validators return the normalized string in interactive mode but return the raw unnormalized string in non-interactive mode"],"goal":"Normalize default answers and validate responses consistently across interactive and non-interactive QuestionHelper execution","kind":"HOW","packages":["pkg:composer/symfony/console@v8.1.4"],"schemaVersion":1,"symbols":["Symfony\\Component\\Console\\Helper\\QuestionHelper","Symfony\\Component\\Console\\Helper\\QuestionHelper::ask","Symfony\\Component\\Console\\Question\\Question","Symfony\\Component\\Console\\Question\\Question::setNormalizer","Symfony\\Component\\Console\\Question\\Question::setValidator"]},"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@v8.1.4"],"schemaVersion":1,"symbols":["Symfony\\Component\\Console\\Helper\\QuestionHelper","Symfony\\Component\\Console\\Helper\\QuestionHelper::ask","Symfony\\Component\\Console\\Question\\Question","Symfony\\Component\\Console\\Question\\Question::setNormalizer","Symfony\\Component\\Console\\Question\\Question::setValidator"],"verifierAdapter":"composer@1"}
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
function assertTrue(bool $condition, string $message): void {
if (!$condition) {
throw new \RuntimeException("Assertion failed: $message");
}
}
function assertSame(mixed $expected, mixed $actual, string $message): void {
if ($expected !== $actual) {
throw new \RuntimeException(
"Assertion failed: $message\nExpected:\n" . var_export($expected, true) . "\nActual:\n" . var_export($actual, true)
);
}
}
echo "Running symfony/console contract assertions...\n";
$helper = new QuestionHelper();
$output = new BufferedOutput();
// Contract 1: QuestionHelper::ask() in non-interactive mode completely bypasses Question::setNormalizer(),
// returning the unnormalized default value and passing the unnormalized type directly to the validator.
{
$createPortQuestion = function(): Question {
$q = new Question('Server port [8080]: ', '8080');
$q->setNormalizer(fn($v) => (int) $v);
$q->setValidator(function ($v) {
if (!is_int($v)) {
throw new \RuntimeException('Validator expected integer from normalizer, got ' . gettype($v));
}
return $v;
});
return $q;
};
// Interactive mode accepting default (pressing Enter)
$stream = fopen('php://memory', 'r+', false);
fwrite($stream, "\n");
rewind($stream);
$interactiveInput = new ArrayInput([]);
$interactiveInput->setStream($stream);
$interactiveInput->setInteractive(true);
$interactiveResult = $helper->ask($interactiveInput, $output, $createPortQuestion());
assertSame(8080, $interactiveResult, 'Interactive default must be normalized to integer');
// Non-interactive mode
$nonInteractiveInput = new ArrayInput([]);
$nonInteractiveInput->setInteractive(false);
$caughtException = null;
try {
$helper->ask($nonInteractiveInput, $output, $createPortQuestion());
} catch (\RuntimeException $e) {
$caughtException = $e;
}
assertTrue(
$caughtException !== null,
'Non-interactive mode must throw because QuestionHelper bypasses normalizer and passes string to validator'
);
assertSame(
'Validator expected integer from normalizer, got string',
$caughtException->getMessage(),
'Exception message must confirm unnormalized string was passed to validator'
);
}
// Contract 2: In interactive mode accepting the default value executes Question::setNormalizer()
// before passing the result to Question::setValidator(), whereas without a validator non-interactive returns raw unnormalized default.
{
$q = new Question('Environment [prod]: ', ' prod ');
$q->setNormalizer(fn($v) => strtoupper(trim((string) $v)));
// Interactive mode with empty input (Enter)
$stream = fopen('php://memory', 'r+', false);
fwrite($stream, "\n");
rewind($stream);
$interactiveInput = new ArrayInput([]);
$interactiveInput->setStream($stream);
$interactiveInput->setInteractive(true);
$interactiveResult = $helper->ask($interactiveInput, $output, $q);
assertSame('PROD', $interactiveResult, 'Interactive fallback must trim and uppercase the default string');
// Non-interactive mode returns raw untrimmed lowercase default
$nonInteractiveInput = new ArrayInput([]);
$nonInteractiveInput->setInteractive(false);
$nonInteractiveResult = $helper->ask($nonInteractiveInput, $output, $q);
assertSame(' prod ', $nonInteractiveResult, 'Non-interactive mode must return raw unnormalized default string');
}
// Contract 3: Complex normalizer (e.g. comma-separated string to array) is bypassed in non-interactive mode.
{
$q = new Question('Allowed roles [admin,user]: ', 'admin,user');
$q->setNormalizer(fn($v) => is_array($v) ? $v : array_map('trim', explode(',', (string) $v)));
$q->setValidator(function ($v) {
if (!is_array($v)) {
throw new \InvalidArgumentException('Expected array of roles');
}
return $v;
});
// Interactive mode
$stream = fopen('php://memory', 'r+', false);
fwrite($stream, "\n");
rewind($stream);
$interactiveInput = new ArrayInput([]);
$interactiveInput->setStream($stream);
$interactiveInput->setInteractive(true);
$interactiveResult = $helper->ask($interactiveInput, $output, $q);
assertSame(['admin', 'user'], $interactiveResult, 'Interactive mode must return parsed array');
// Non-interactive mode
$nonInteractiveInput = new ArrayInput([]);
$nonInteractiveInput->setInteractive(false);
$caught = false;
try {
$helper->ask($nonInteractiveInput, $output, $q);
} catch (\InvalidArgumentException $e) {
$caught = true;
}
assertTrue($caught, 'Non-interactive mode must throw InvalidArgumentException due to unparsed string default');
}
// Contract 4: Explicit user input in interactive mode is correctly transformed by normalizer before validator.
{
$q = new Question('Confirm [y/n]: ', 'n');
$q->setNormalizer(fn($v) => strtolower(trim((string) $v)) === 'y');
$q->setValidator(function ($v) {
if (!is_bool($v)) {
throw new \RuntimeException('Validator expected boolean');
}
return $v;
});
$stream = fopen('php://memory', 'r+', false);
fwrite($stream, " Y \n");
rewind($stream);
$input = new ArrayInput([]);
$input->setStream($stream);
$input->setInteractive(true);
$result = $helper->ask($input, $output, $q);
assertSame(true, $result, 'Interactive user input must be normalized to boolean true');
}
echo "All symfony/console contract assertions passed successfully.\n";