Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create console command to view global config #1105

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
121 changes: 121 additions & 0 deletions src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

declare(strict_types=1);

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;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
* Views a configuration value in the global scope.
*/
class ConfigViewCommand extends Command
{
protected static $defaultName = 'oro:config:view';

private ConfigManager $configManager;
private SystemConfigurationFormProvider $formProvider;

public function __construct(
ConfigManager $configManager,
SystemConfigurationFormProvider $formProvider,
) {
$this->configManager = $configManager;
$this->formProvider = $formProvider;

parent::__construct();
}

/** @noinspection PhpMissingParentCallCommonInspection */
protected function configure(): void
{
$this
->addArgument('name', InputArgument::REQUIRED, 'Config parameter name')
->setDescription('Views a configuration value in the global scope.')
->setHelp(
<<<'HELP'
The <info>%command.name%</info> command views a configuration value in the global scope.

<info>php %command.full_name% <name></info>

For example, to view the back-office and storefront URLs of an OroCommerce instance respectively:

<info>php %command.full_name% oro_ui.application_url</info>
<info>php %command.full_name% oro_website.url</info>
<info>php %command.full_name% oro_website.secure_url</info>

HELP
)
;
}

/**
* 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
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$symfonyStyle = new SymfonyStyle($input, $output);
$configManager = $this->configManager;
$fieldName = $input->getArgument('name');

$configTree = $this->formProvider->getTree();
$configField = $this->findFieldNode($configTree, $fieldName);
if ($configField !== null
&& $configField->getType() === OroEncodedPlaceholderPasswordType::class
) {
$symfonyStyle->error("Encrypted value");
return Command::INVALID;
}

$value = $configManager->get($fieldName);
if (is_null($value) && $configField === null) {
$symfonyStyle->error("Unknown config field");
return Command::FAILURE;
}
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)) {
$symfonyStyle->error("Value cannot be displayed");
return Command::FAILURE;
}

$output->writeln($value);
return Command::SUCCESS;
}
}
7 changes: 7 additions & 0 deletions src/Oro/Bundle/ConfigBundle/Resources/config/commands.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,10 @@ services:
- '@oro_config.global'
tags:
- { name: console.command }

Oro\Bundle\ConfigBundle\Command\ConfigViewCommand:
arguments:
- '@oro_config.global'
- '@oro_config.provider.system_configuration.form_provider'
tags:
- { name: console.command }
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace Oro\Bundle\ConfigBundle\Tests\Functional\Command;

use Oro\Bundle\TestFrameworkBundle\Test\WebTestCase;

class ConfigViewCommandTest extends WebTestCase
{
public function testBoolTrue(): void
{
$output = self::runCommand('oro:config:view', ['oro_frontend.web_api']);
self::assertEquals('true', $output);
}

public function testBoolFalse(): void
{
$output = self::runCommand('oro:config:view', ['oro_report.display_sql_query']);
self::assertEquals('false', $output);
}

public function testNull(): void
{
$output = self::runCommand('oro:config:view', ['oro_shopping_list.default_guest_shopping_list_owner']);
self::assertEquals('null', $output);
}

public function testInt(): void
{
$output = self::runCommand('oro:config:view', ['oro_product.new_arrivals_products_segment_id']);
self::assertEquals('2', $output);
}

public function testFloat(): void
{
$output = self::runCommand('oro:config:view', ['oro_seo.sitemap_priority_product']);
self::assertEquals('0.5', $output);
}

public function testString(): void
{
$output = self::runCommand('oro:config:view', ['oro_pricing_pro.default_currency']);
self::assertEquals('USD', $output);
}

public function testArray(): void
{
$output = self::runCommand('oro:config:view', ['oro_shipping.length_units']);
self::assertEquals('[ "inch", "foot", "cm", "m" ]', $output);
}

public function testAssocArray(): void
{
$output = self::runCommand('oro:config:view', ['oro_locale.quarter_start']);
self::assertEquals('{ "month": "1", "day": "1" }', $output);
}

public function testEncryptedValue(): void
{
$output = self::runCommand('oro:config:view', ['oro_google_integration.client_secret']);
self::assertEquals('[ERROR] Encrypted value', $output);
}

public function testNonexistentField(): void
{
$output = self::runCommand('oro:config:view', ['oro_example.nonexistent_field']);
self::assertEquals('[ERROR] Unknown config field', $output);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

declare(strict_types=1);

namespace Oro\Bundle\ConfigBundle\Tests\Unit\Command;

use Oro\Bundle\ConfigBundle\Command\ConfigViewCommand;
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 Oro\Bundle\SalesBundle\Form\Type\OpportunityStatusConfigType;
use Oro\Component\Testing\Command\CommandTestingTrait;
use PHPUnit\Framework\TestCase;

class ConfigViewCommandTest extends TestCase
{
use CommandTestingTrait;

private ConfigViewCommand $command;

protected function setUp(): void
{
$configManager = $this->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_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'],

// 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,
]);

$nullField = $this->createConfiguredMock(FieldNodeDefinition::class, [
'getName' => 'oro_sales.opportunity_statuses',
'getType' => OpportunityStatusConfigType::class,
]);

$fieldGroup = $this->createConfiguredMock(GroupNodeDefinition::class, [
'getIterator' => new \ArrayIterator([
$encryptedField,
$nullField,
]),
]);

$formProvider = $this->createConfiguredMock(SystemConfigurationFormProvider::class, [
'getTree' => $fieldGroup,
]);

$this->command = new ConfigViewCommand(
$configManager,
$formProvider
);
}

private function validateConfigView(string $configFieldName, string $expectedValue): void
{
$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_sales.opportunity_statuses', 'null');
$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']),
"Unknown config field"
);
}
}