From a4897813b7b3960ed3d66edf5294b0259ae73eff Mon Sep 17 00:00:00 2001 From: Benno Lang Date: Mon, 25 Sep 2023 15:23:44 +0930 Subject: [PATCH 01/12] Create console command to view global config --- .../Command/ConfigViewCommand.php | 64 +++++++++++++++++++ .../Resources/config/commands.yml | 6 ++ 2 files changed, 70 insertions(+) create mode 100644 src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php diff --git a/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php new file mode 100644 index 00000000000..382f2c61672 --- /dev/null +++ b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php @@ -0,0 +1,64 @@ +configManager = $configManager; + + parent::__construct(); + } + + /** @noinspection PhpMissingParentCallCommonInspection */ + protected function configure() + { + $this + ->addArgument('name', InputArgument::REQUIRED, 'Config parameter name') + ->setDescription('Views a configuration value in the global scope.') + ->setHelp( + <<<'HELP' +The %command.name% command views a configuration value in the global scope. + + php %command.full_name% + +For example, to view the back-office and storefront URLs of an OroCommerce instance respectively: + + php %command.full_name% oro_ui.application_url + php %command.full_name% oro_website.url + php %command.full_name% oro_website.secure_url + +HELP + ) + ; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @noinspection PhpMissingParentCallCommonInspection + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $configManager = $this->configManager; + $value = $configManager->get($input->getArgument('name')); + $output->writeln($value); + + return Command::SUCCESS; + } +} diff --git a/src/Oro/Bundle/ConfigBundle/Resources/config/commands.yml b/src/Oro/Bundle/ConfigBundle/Resources/config/commands.yml index 59c3e074d1c..694c32bc678 100644 --- a/src/Oro/Bundle/ConfigBundle/Resources/config/commands.yml +++ b/src/Oro/Bundle/ConfigBundle/Resources/config/commands.yml @@ -7,3 +7,9 @@ services: - '@oro_config.global' tags: - { name: console.command } + + Oro\Bundle\ConfigBundle\Command\ConfigViewCommand: + arguments: + - '@oro_config.global' + tags: + - { name: console.command } From f819eb04c500fc91d6c0b4525ac00c144989a544 Mon Sep 17 00:00:00 2001 From: Benno Lang Date: Mon, 9 Oct 2023 11:40:47 +1030 Subject: [PATCH 02/12] Display array|object|bool config values sensibly --- .../ConfigBundle/Command/ConfigViewCommand.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php index 382f2c61672..d66dbb7bd1f 100644 --- a/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php +++ b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php @@ -57,8 +57,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int { $configManager = $this->configManager; $value = $configManager->get($input->getArgument('name')); - $output->writeln($value); + if (is_null($value)) { + $output->writeln("Value could not be retrieved"); + return Command::FAILURE; + } + if (is_array($value) || is_object($value) || is_bool($value)) { + $value = json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } + if (!is_scalar($value)) { + $output->writeln("Value cannot be displayed"); + return Command::FAILURE; + } + $output->writeln($value); return Command::SUCCESS; } } From b66d81892ee96eedc013e80532fd96e167260aca Mon Sep 17 00:00:00 2001 From: Benno Lang Date: Tue, 12 Mar 2024 10:40:00 +1030 Subject: [PATCH 03/12] Prevent display of encrypted values by view global config command --- .../Command/ConfigViewCommand.php | 49 +++++++++++++++++-- .../Resources/config/commands.yml | 1 + 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php index d66dbb7bd1f..98378ee8fe6 100644 --- a/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php +++ b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php @@ -5,6 +5,10 @@ namespace Oro\Bundle\ConfigBundle\Command; use Oro\Bundle\ConfigBundle\Config\ConfigManager; +use Oro\Bundle\ConfigBundle\Config\Tree\FieldNodeDefinition; +use Oro\Bundle\ConfigBundle\Config\Tree\GroupNodeDefinition; +use Oro\Bundle\ConfigBundle\Provider\SystemConfigurationFormProvider; +use Oro\Bundle\FormBundle\Form\Type\OroEncodedPlaceholderPasswordType; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -18,10 +22,14 @@ class ConfigViewCommand extends Command protected static $defaultName = 'oro:config:view'; private ConfigManager $configManager; + private SystemConfigurationFormProvider $formProvider; - public function __construct(ConfigManager $configManager) - { + public function __construct( + ConfigManager $configManager, + SystemConfigurationFormProvider $formProvider, + ) { $this->configManager = $configManager; + $this->formProvider = $formProvider; parent::__construct(); } @@ -49,6 +57,30 @@ protected function configure() ; } + /** + * Find a field node by name from the config tree + * + * @param GroupNodeDefinition $node + * @param string $fieldName + * @return ?FieldNodeDefinition null if no matching node was found + */ + protected function findFieldNode(GroupNodeDefinition $node, string $fieldName): ?FieldNodeDefinition { + foreach ($node as $child) { + if ($child instanceof GroupNodeDefinition) { + $result = $this->findFieldNode($child, $fieldName); + if ($result !== null) { + return $result; + } + } elseif ($child instanceof FieldNodeDefinition) { + if ($child->getName() === $fieldName) { + return $child; + } + } + } + + return null; + } + /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @noinspection PhpMissingParentCallCommonInspection @@ -56,7 +88,18 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output): int { $configManager = $this->configManager; - $value = $configManager->get($input->getArgument('name')); + $fieldName = $input->getArgument('name'); + + $configTree = $this->formProvider->getTree(); + $configField = $this->findFieldNode($configTree, $fieldName); + if ($configField !== null + && $configField->getType() === OroEncodedPlaceholderPasswordType::class + ) { + $output->writeln("# encrypted value"); + return Command::INVALID; + } + + $value = $configManager->get($fieldName); if (is_null($value)) { $output->writeln("Value could not be retrieved"); return Command::FAILURE; diff --git a/src/Oro/Bundle/ConfigBundle/Resources/config/commands.yml b/src/Oro/Bundle/ConfigBundle/Resources/config/commands.yml index 694c32bc678..21c8edd91be 100644 --- a/src/Oro/Bundle/ConfigBundle/Resources/config/commands.yml +++ b/src/Oro/Bundle/ConfigBundle/Resources/config/commands.yml @@ -11,5 +11,6 @@ services: Oro\Bundle\ConfigBundle\Command\ConfigViewCommand: arguments: - '@oro_config.global' + - '@oro_config.provider.system_configuration.form_provider' tags: - { name: console.command } From 18764e14a72e1a3cf1f823f2d289aed99352c9bf Mon Sep 17 00:00:00 2001 From: Benno Lang Date: Tue, 12 Mar 2024 11:42:35 +1030 Subject: [PATCH 04/12] Minor code style fix --- src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php index 98378ee8fe6..30545486f07 100644 --- a/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php +++ b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php @@ -64,7 +64,8 @@ protected function configure() * @param string $fieldName * @return ?FieldNodeDefinition null if no matching node was found */ - protected function findFieldNode(GroupNodeDefinition $node, string $fieldName): ?FieldNodeDefinition { + protected function findFieldNode(GroupNodeDefinition $node, string $fieldName): ?FieldNodeDefinition + { foreach ($node as $child) { if ($child instanceof GroupNodeDefinition) { $result = $this->findFieldNode($child, $fieldName); From e107c975609e8c09931c3fee3f47fcf8f58e77ee Mon Sep 17 00:00:00 2001 From: Benno Lang Date: Tue, 12 Mar 2024 12:44:58 +1030 Subject: [PATCH 05/12] Output errors using Symfony styling --- src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php index 30545486f07..e685afbb6a7 100644 --- a/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php +++ b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php @@ -13,6 +13,7 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; /** * Views a configuration value in the global scope. @@ -88,6 +89,7 @@ protected function findFieldNode(GroupNodeDefinition $node, string $fieldName): */ protected function execute(InputInterface $input, OutputInterface $output): int { + $symfonyStyle = new SymfonyStyle($input, $output); $configManager = $this->configManager; $fieldName = $input->getArgument('name'); @@ -96,20 +98,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($configField !== null && $configField->getType() === OroEncodedPlaceholderPasswordType::class ) { - $output->writeln("# encrypted value"); + $symfonyStyle->error("Encrypted value"); return Command::INVALID; } $value = $configManager->get($fieldName); if (is_null($value)) { - $output->writeln("Value could not be retrieved"); + $symfonyStyle->error("Value could not be retrieved"); return Command::FAILURE; } if (is_array($value) || is_object($value) || is_bool($value)) { $value = json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); } if (!is_scalar($value)) { - $output->writeln("Value cannot be displayed"); + $symfonyStyle->error("Value cannot be displayed"); return Command::FAILURE; } From e60a21e29cb081f392edd859ab88036db7171bbe Mon Sep 17 00:00:00 2001 From: Benno Lang Date: Tue, 12 Mar 2024 13:16:23 +1030 Subject: [PATCH 06/12] Add unit test for view global config command --- .../Unit/Command/ConfigViewCommandTest.php | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php diff --git a/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php b/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php new file mode 100644 index 00000000000..d69211ed29d --- /dev/null +++ b/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php @@ -0,0 +1,112 @@ +createMock(ConfigManager::class, []); + $configManager->method('get')->will( + $this->returnCallback(function ($fieldName) { + $configValues = [ + // Plain values + 'oro_frontend.web_api' => true, + 'oro_locale.default_localization' => 1, + 'oro_website.secure_url' => 'https://example.com', + 'oro_locale.enabled_localizations' => [1, 2, 3], + 'oro_example.dummy_object' => (object)['test' => 'value'], + + // Encrypted value + 'oro_example.secret_value' => 'Shh, keep it secret', + + // Nonsense value + 'oro_example.nonsense_value' => fopen('php://stdin', 'r'), + ]; + return $configValues[$fieldName] ?? null; + }) + ); + + $encryptedField = $this->createConfiguredMock(FieldNodeDefinition::class, [ + 'getName' => 'oro_example.secret_value', + 'getType' => OroEncodedPlaceholderPasswordType::class, + ]); + + $fieldGroup = $this->createConfiguredMock(GroupNodeDefinition::class, [ + 'getIterator' => new \ArrayIterator([$encryptedField]), + ]); + + $formProvider = $this->createConfiguredMock(SystemConfigurationFormProvider::class, [ + 'getTree' => $fieldGroup, + ]); + + $this->command = new ConfigViewCommand( + $configManager, + $formProvider + ); + } + + private function validateConfigView(string $configFieldName, string $expectedValue) + { + $commandTester = $this->doExecuteCommand($this->command, ['name' => $configFieldName]); + $this->assertOutputContains($commandTester, $expectedValue); + } + + public function testViewScalarValues(): void + { + $this->validateConfigView('oro_frontend.web_api', 'true'); + $this->validateConfigView('oro_locale.default_localization', '1'); + $this->validateConfigView('oro_website.secure_url', 'https://example.com'); + } + + public function testViewArrayValue(): void + { + $this->validateConfigView('oro_locale.enabled_localizations', '[ 1, 2, 3 ]'); + } + + public function testViewObjectValue(): void + { + $this->validateConfigView('oro_example.dummy_object', '{ "test": "value" }'); + } + + public function testViewEncryptedValue(): void + { + $this->assertProducedError( + $this->doExecuteCommand($this->command, ['name' => 'oro_example.secret_value']), + "Encrypted value" + ); + } + + public function testViewInvalidValue(): void + { + $this->assertProducedError( + $this->doExecuteCommand($this->command, ['name' => 'oro_example.nonsense_value']), + "Value cannot be displayed" + ); + } + + public function testViewNonexistentValue(): void + { + $this->assertProducedError( + $this->doExecuteCommand($this->command, ['name' => 'oro_example.nonexistent_field']), + "Value could not be retrieved" + ); + } +} From d1a1f53676b39ce9c3fa97abfb1907eb1057c9ed Mon Sep 17 00:00:00 2001 From: Benno Lang Date: Mon, 6 May 2024 10:43:26 +0930 Subject: [PATCH 07/12] Handle null config values for known config fields --- src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php index e685afbb6a7..624c26a5e2c 100644 --- a/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php +++ b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php @@ -103,11 +103,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $value = $configManager->get($fieldName); - if (is_null($value)) { - $symfonyStyle->error("Value could not be retrieved"); + if (is_null($value) && $configField === null) { + $symfonyStyle->error("Unknown config field"); return Command::FAILURE; } - if (is_array($value) || is_object($value) || is_bool($value)) { + if (is_array($value) || is_object($value) || is_bool($value) || is_null($value)) { $value = json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); } if (!is_scalar($value)) { From 30d8d1bc95f1e480be45227be96d67046a73c545 Mon Sep 17 00:00:00 2001 From: Benno Lang Date: Mon, 6 May 2024 10:52:41 +0930 Subject: [PATCH 08/12] Update unit test to handle null config value --- .../Tests/Unit/Command/ConfigViewCommandTest.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php b/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php index d69211ed29d..66b93fa56dd 100644 --- a/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php +++ b/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php @@ -10,6 +10,7 @@ use Oro\Bundle\ConfigBundle\Config\Tree\GroupNodeDefinition; use Oro\Bundle\ConfigBundle\Provider\SystemConfigurationFormProvider; use Oro\Bundle\FormBundle\Form\Type\OroEncodedPlaceholderPasswordType; +use Oro\Bundle\SalesBundle\Form\Type\OpportunityStatusConfigType; use Oro\Component\Testing\Command\CommandTestingTrait; use PHPUnit\Framework\TestCase; @@ -30,6 +31,7 @@ protected function setUp(): void // Plain values 'oro_frontend.web_api' => true, 'oro_locale.default_localization' => 1, + 'oro_sales.opportunity_statuses' => null, 'oro_website.secure_url' => 'https://example.com', 'oro_locale.enabled_localizations' => [1, 2, 3], 'oro_example.dummy_object' => (object)['test' => 'value'], @@ -49,8 +51,16 @@ protected function setUp(): void 'getType' => OroEncodedPlaceholderPasswordType::class, ]); + $nullField = $this->createConfiguredMock(FieldNodeDefinition::class, [ + 'getName' => 'oro_sales.opportunity_statuses', + 'getType' => OpportunityStatusConfigType::class, + ]); + $fieldGroup = $this->createConfiguredMock(GroupNodeDefinition::class, [ - 'getIterator' => new \ArrayIterator([$encryptedField]), + 'getIterator' => new \ArrayIterator([ + $encryptedField, + $nullField, + ]), ]); $formProvider = $this->createConfiguredMock(SystemConfigurationFormProvider::class, [ @@ -73,6 +83,7 @@ public function testViewScalarValues(): void { $this->validateConfigView('oro_frontend.web_api', 'true'); $this->validateConfigView('oro_locale.default_localization', '1'); + $this->validateConfigView('oro_sales.opportunity_statuses', 'null'); $this->validateConfigView('oro_website.secure_url', 'https://example.com'); } @@ -106,7 +117,7 @@ public function testViewNonexistentValue(): void { $this->assertProducedError( $this->doExecuteCommand($this->command, ['name' => 'oro_example.nonexistent_field']), - "Value could not be retrieved" + "Unknown config field" ); } } From 0d1e68f2b222f0f02da58e715338e2368cfe3a2f Mon Sep 17 00:00:00 2001 From: Benno Lang Date: Mon, 6 May 2024 11:24:16 +0930 Subject: [PATCH 09/12] Add missing return types --- src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php | 2 +- .../ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php index 624c26a5e2c..504cfaf8326 100644 --- a/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php +++ b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php @@ -36,7 +36,7 @@ public function __construct( } /** @noinspection PhpMissingParentCallCommonInspection */ - protected function configure() + protected function configure(): void { $this ->addArgument('name', InputArgument::REQUIRED, 'Config parameter name') diff --git a/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php b/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php index 66b93fa56dd..6842c1336c8 100644 --- a/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php +++ b/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php @@ -73,7 +73,7 @@ protected function setUp(): void ); } - private function validateConfigView(string $configFieldName, string $expectedValue) + private function validateConfigView(string $configFieldName, string $expectedValue): void { $commandTester = $this->doExecuteCommand($this->command, ['name' => $configFieldName]); $this->assertOutputContains($commandTester, $expectedValue); From 4b4df7830f2e35e73ef4e439fdd8a215529e9752 Mon Sep 17 00:00:00 2001 From: Benno Lang Date: Mon, 6 May 2024 11:24:39 +0930 Subject: [PATCH 10/12] Remove unused attribute --- .../ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php b/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php index 6842c1336c8..4bdfb00e051 100644 --- a/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php +++ b/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php @@ -19,8 +19,6 @@ class ConfigViewCommandTest extends TestCase use CommandTestingTrait; private ConfigViewCommand $command; - private ConfigManager $configManager; - protected function setUp(): void { From e8b18d686f2f41cffc7f2d8e6f259e615d55dab2 Mon Sep 17 00:00:00 2001 From: Benno Lang Date: Mon, 6 May 2024 11:25:00 +0930 Subject: [PATCH 11/12] Remove superfluous param --- .../ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php b/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php index 4bdfb00e051..51e2a7a728c 100644 --- a/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php +++ b/src/Oro/Bundle/ConfigBundle/Tests/Unit/Command/ConfigViewCommandTest.php @@ -22,7 +22,7 @@ class ConfigViewCommandTest extends TestCase protected function setUp(): void { - $configManager = $this->createMock(ConfigManager::class, []); + $configManager = $this->createMock(ConfigManager::class); $configManager->method('get')->will( $this->returnCallback(function ($fieldName) { $configValues = [ From 64c0d5c35a8e45508ae9778eea503dec2b5383f0 Mon Sep 17 00:00:00 2001 From: Benno Lang Date: Mon, 6 May 2024 11:26:10 +0930 Subject: [PATCH 12/12] Add functional test for view global config command --- .../Command/ConfigViewCommandTest.php | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/Oro/Bundle/ConfigBundle/Tests/Functional/Command/ConfigViewCommandTest.php diff --git a/src/Oro/Bundle/ConfigBundle/Tests/Functional/Command/ConfigViewCommandTest.php b/src/Oro/Bundle/ConfigBundle/Tests/Functional/Command/ConfigViewCommandTest.php new file mode 100644 index 00000000000..6a5bbdb82e8 --- /dev/null +++ b/src/Oro/Bundle/ConfigBundle/Tests/Functional/Command/ConfigViewCommandTest.php @@ -0,0 +1,70 @@ +