샘플
symfony/console 7.2.1: Declare Symfony Console commands using the AsCommand attribute rather than the removed static defaultName property
검증된 샘플 — composer symfony/console 7.2.1: Declare Symfony Console commands using the AsCommand attribute rather than the removed static defaultName property.…
sha256:6863aebd729c8113a15dec27179d3f8d80a5d9f6256ca354ba3f1a56e807c128
이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다.
통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다.
MIT-0
실행 증거
선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.
- 증거 기준
- 서명된 컨트랙트 통과
- 검증 영수증
- 2
- 빌드한 서명 키
- 2
선언된 환경
php linux x64 php php composer
검증 실행 환경
| 환경 | 컨트랙트 | 단계 | 실행일 |
|---|---|---|---|
| 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 |
케이스
HOW- 목표
- Declare Symfony Console commands using the AsCommand attribute rather than the removed static defaultName property
- 심벌
-
- Symfony\Component\Console\Command\Command
- Symfony\Component\Console\Attribute\AsCommand
- Symfony\Component\Console\Application
- Symfony\Component\Console\Input\InputInterface
- 환경
- php
- 생성일
- 2026-08-16T14:01:15Z
컨트랙트
- In Symfony Console 7, Command::getDefaultName() returns null for classes defining static $defaultName, leaving the command name null and causing Application::add() to throw LogicException unless configured via the #[AsCommand] attribute.
- The #[AsCommand] attribute encodes name, description, aliases, and hidden status into Command::getDefaultName() with leading pipe syntax
- Command instances configured with #[AsCommand] populate getName, getDescription, getAliases, and isHidden without calling setName in configure
- Application::add successfully registers commands and aliases defined via #[AsCommand] and rejects commands lacking a name
- InputInterface implements Stringable and converts bound arguments and options to a command-line string representation
- Command::setApplication requires an explicit parameter and throws ArgumentCountError when invoked with zero arguments
파일
- NOTES.md
- composer.json
- composer.lock
- csx.json
- test/contract.php
소스
# Notes
## Search Results
`search_known_solution` returned existing samples `sha256:403a4477823e3c31ef880554c7e1d83e9ee2e5182b4363b8e73e751a7cae9b95` (reentrancy and nested doRun state across sub-commands), `sha256:daa9d0d39f0c0c2c98dd7a96aa84ddffba6366d269e338ad0e2fc99133d716ea` (ANSI decoration, interactivity, and verbosity environment variables), and `sha256:64ffbaaec11e20300f534cf6b8b955bf63ed41b80dd1cf1bcba3f0df84e46ff7` (input option modes, default validations, and command tester prefixes). This sample addresses the breaking change in Symfony Console 7: the complete removal of `Command::$defaultName` and `Command::$defaultDescription` in favor of the `#[AsCommand]` attribute and strict method signatures.
## What a Naive Model Would Write
A naive model trained on earlier Symfony versions declares console commands by setting `protected static $defaultName = 'app:custom-name';` and `protected static $defaultDescription = '...';` directly on the class, expecting `$application->add(new CustomCommand())` to auto-discover the command name.
## How the Wrong Version Fails
Fails loudly at runtime: in Symfony Console 7, `Command::getDefaultName()` returns `null` when only static properties are present. The command's name remains `null`, causing `$application->add()` to throw `Symfony\Component\Console\Exception\LogicException: The command defined in "..." cannot have an empty name.`
{
"name": "csx/symfony-console-v7-as-command",
"description": "Demonstrates Symfony Console 7 command declaration via AsCommand attribute and removal of static defaultName",
"license": "MIT-0",
"type": "project",
"require": {
"symfony/console": "7.2.1"
}
}
{
"_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": "bf8c302b565484e9dd6f3f5138918b4a",
"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.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
"reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/string": "^6.4|^7.0"
},
"conflict": {
"symfony/dependency-injection": "<6.4",
"symfony/dotenv": "<6.4",
"symfony/event-dispatcher": "<6.4",
"symfony/lock": "<6.4",
"symfony/process": "<6.4"
},
"provide": {
"psr/log-implementation": "1.0|2.0|3.0"
},
"require-dev": {
"psr/log": "^1|^2|^3",
"symfony/config": "^6.4|^7.0",
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/event-dispatcher": "^6.4|^7.0",
"symfony/http-foundation": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
"symfony/lock": "^6.4|^7.0",
"symfony/messenger": "^6.4|^7.0",
"symfony/process": "^6.4|^7.0",
"symfony/stopwatch": "^6.4|^7.0",
"symfony/var-dumper": "^6.4|^7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Eases the creation of beautiful and testable command line interfaces",
"homepage": "https://symfony.com",
"keywords": [
"cli",
"command-line",
"console",
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.2.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-12-11T03:49:26+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.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": "v7.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "e394af32256bf9e7bf80849d95e589167c10097b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/e394af32256bf9e7bf80849d95e589167c10097b",
"reference": "e394af32256bf9e7bf80849d95e589167c10097b",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3.0",
"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.1|^8.0",
"symfony/http-client": "^6.4|^7.0|^8.0",
"symfony/intl": "^6.4|^7.0|^8.0",
"symfony/translation-contracts": "^2.5|^3.0",
"symfony/var-exporter": "^6.4|^7.0|^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/v7.4.15"
},
"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:33:02+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":"Declaring a protected static $defaultName property on a Command subclass sets the command name and description and allows registering it with Application::add().","caseId":"case:sha256:23e976a416c4662d857365341c9506c124824bd860e39d56cb68aa3d3f3dc97c","contract":["In Symfony Console 7, Command::getDefaultName() returns null for classes defining static $defaultName, leaving the command name null and causing Application::add() to throw LogicException unless configured via the #[AsCommand] attribute.","The #[AsCommand] attribute encodes name, description, aliases, and hidden status into Command::getDefaultName() with leading pipe syntax","Command instances configured with #[AsCommand] populate getName, getDescription, getAliases, and isHidden without calling setName in configure","Application::add successfully registers commands and aliases defined via #[AsCommand] and rejects commands lacking a name","InputInterface implements Stringable and converts bound arguments and options to a command-line string representation","Command::setApplication requires an explicit parameter and throws ArgumentCountError when invoked with zero arguments"],"goal":"Declare Symfony Console commands using the AsCommand attribute rather than the removed static defaultName property","kind":"HOW","packages":["pkg:composer/symfony/console@7.2.1"],"schemaVersion":1,"symbols":["Symfony\\Component\\Console\\Command\\Command","Symfony\\Component\\Console\\Attribute\\AsCommand","Symfony\\Component\\Console\\Application","Symfony\\Component\\Console\\Input\\InputInterface"]},"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.2.1"],"schemaVersion":1,"symbols":["Symfony\\Component\\Console\\Command\\Command","Symfony\\Component\\Console\\Attribute\\AsCommand","Symfony\\Component\\Console\\Application","Symfony\\Component\\Console\\Input\\InputInterface"],"verifierAdapter":"composer@1"}
<?php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
// =============================================================================
// Contract 1: In Symfony Console 7, Command::getDefaultName() returns null for
// classes defining static $defaultName, leaving the command name null and
// causing Application::add() to throw LogicException unless configured via the
// #[AsCommand] attribute.
// =============================================================================
class LegacyStaticCommand extends Command
{
// In Symfony <= 6, defining static properties was the standard mechanism.
// In Symfony 7, Command::$defaultName and Command::$defaultDescription are removed.
protected static $defaultName = 'legacy:export';
protected static $defaultDescription = 'Legacy export description';
}
assert(
LegacyStaticCommand::getDefaultName() === null,
'Expected LegacyStaticCommand::getDefaultName() to return null in Symfony Console 7'
);
assert(
LegacyStaticCommand::getDefaultDescription() === null,
'Expected LegacyStaticCommand::getDefaultDescription() to return null in Symfony Console 7'
);
$legacyCommand = new LegacyStaticCommand();
assert(
$legacyCommand->getName() === null,
'Expected command instance name to be null when relying solely on static $defaultName'
);
assert(
$legacyCommand->getDescription() === '',
'Expected command description to be empty string when relying solely on static $defaultDescription'
);
$application = new Application();
$logicExceptionCaught = false;
try {
$application->add($legacyCommand);
} catch (LogicException $e) {
$logicExceptionCaught = true;
assert(
str_contains($e->getMessage(), 'cannot have an empty name'),
'Expected LogicException message to indicate that the command cannot have an empty name'
);
}
assert(
$logicExceptionCaught === true,
'Expected Application::add() to throw LogicException for legacy command without #[AsCommand]'
);
// =============================================================================
// Contract 2: The #[AsCommand] attribute encodes name, description, aliases,
// and hidden status into Command::getDefaultName() with leading pipe syntax.
// =============================================================================
#[AsCommand(
name: 'app:sync',
description: 'Synchronize database records',
aliases: ['sync:all', 's:a'],
hidden: true
)]
class ModernSyncCommand extends Command
{
}
assert(
ModernSyncCommand::getDefaultName() === '|app:sync|sync:all|s:a',
'Expected getDefaultName() to return pipe-concatenated name with leading pipe for hidden commands and aliases'
);
assert(
ModernSyncCommand::getDefaultDescription() === 'Synchronize database records',
'Expected getDefaultDescription() to return description from #[AsCommand]'
);
// =============================================================================
// Contract 3: Command instances configured with #[AsCommand] populate getName,
// getDescription, getAliases, and isHidden without calling setName in configure.
// =============================================================================
$modernCommand = new ModernSyncCommand();
assert(
$modernCommand->getName() === 'app:sync',
'Expected getName() to return primary command name extracted from #[AsCommand]'
);
assert(
$modernCommand->getDescription() === 'Synchronize database records',
'Expected getDescription() to match #[AsCommand] description'
);
assert(
$modernCommand->getAliases() === ['sync:all', 's:a'],
'Expected getAliases() to contain aliases from #[AsCommand]'
);
assert(
$modernCommand->isHidden() === true,
'Expected isHidden() to return true when hidden: true is declared in #[AsCommand]'
);
// =============================================================================
// Contract 4: Application::add successfully registers commands and aliases
// defined via #[AsCommand] and rejects commands lacking a name.
// =============================================================================
$registered = $application->add($modernCommand);
assert(
$registered === $modernCommand,
'Expected Application::add() to return the registered Command instance'
);
assert(
$application->has('app:sync') === true,
'Expected Application to find command by primary name'
);
assert(
$application->has('sync:all') === true,
'Expected Application to find command by alias sync:all'
);
assert(
$application->has('s:a') === true,
'Expected Application to find command by alias s:a'
);
assert(
$application->get('app:sync') === $modernCommand,
'Expected Application::get(app:sync) to retrieve the registered command'
);
assert(
$application->get('sync:all') === $modernCommand,
'Expected Application::get(sync:all) to retrieve the registered command'
);
// =============================================================================
// Contract 5: InputInterface implements Stringable and converts bound arguments
// and options to a command-line string representation.
// =============================================================================
$definition = new InputDefinition([
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format', 'json'),
new InputArgument('batch', InputArgument::REQUIRED, 'Batch count'),
]);
$input = new ArrayInput(['--format' => 'json', 'batch' => '100'], $definition);
assert(
$input instanceof \Stringable,
'Expected InputInterface implementation to implement Stringable in Symfony Console 7'
);
$renderedInput = (string) $input;
assert(
$renderedInput === '--format=json 100',
sprintf('Expected rendered string "--format=json 100", got "%s"', $renderedInput)
);
// =============================================================================
// Contract 6: Command::setApplication requires an explicit parameter and throws
// ArgumentCountError when invoked with zero arguments.
// =============================================================================
$reflection = new \ReflectionMethod(Command::class, 'setApplication');
assert(
$reflection->getNumberOfRequiredParameters() === 1,
'Expected Command::setApplication() to have exactly 1 required parameter in Symfony Console 7'
);
$argumentCountCaught = false;
try {
$modernCommand->setApplication();
} catch (\ArgumentCountError $e) {
$argumentCountCaught = true;
}
assert(
$argumentCountCaught === true,
'Expected setApplication() without arguments to throw ArgumentCountError'
);
echo "Contract test passed successfully.\n";