diff --git a/app/code/Magento/Config/Console/Command/ConfigSet/EmulatedProcessorFacade.php b/app/code/Magento/Config/Console/Command/ConfigSet/EmulatedProcessorFacade.php deleted file mode 100644 index cb5ba6f09a5e1..0000000000000 --- a/app/code/Magento/Config/Console/Command/ConfigSet/EmulatedProcessorFacade.php +++ /dev/null @@ -1,95 +0,0 @@ -scope = $scope; - $this->state = $state; - $this->processorFacadeFactory = $processorFacadeFactory; - } - - /** - * Processes config:set command. - * - * @param string $path The configuration path in format group/section/field_name - * @param string $value The configuration value - * @param string $scope The configuration scope (default, website, or store) - * @param string $scopeCode The scope code - * @param boolean $lock The lock flag - * @return string Processor response message - * @throws RuntimeException If exception was catch - */ - public function process($path, $value, $scope, $scopeCode, $lock) - { - $currentScope = $this->scope->getCurrentScope(); - - try { - // Emulating adminhtml scope to be able to read configs. - return $this->state->emulateAreaCode(Area::AREA_ADMINHTML, function () use ( - $path, - $value, - $scope, - $scopeCode, - $lock - ) { - $this->scope->setCurrentScope(Area::AREA_ADMINHTML); - - return $this->processorFacadeFactory->create()->process( - $path, - $value, - $scope, - $scopeCode, - $lock - ); - }); - } catch (LocalizedException $exception) { - throw new RuntimeException(__('%1', $exception->getMessage()), $exception); - } finally { - $this->scope->setCurrentScope($currentScope); - } - } -} diff --git a/app/code/Magento/Config/Console/Command/ConfigSetCommand.php b/app/code/Magento/Config/Console/Command/ConfigSetCommand.php index 3629cde113d2e..4b5d821def6cf 100644 --- a/app/code/Magento/Config/Console/Command/ConfigSetCommand.php +++ b/app/code/Magento/Config/Console/Command/ConfigSetCommand.php @@ -6,10 +6,9 @@ namespace Magento\Config\Console\Command; use Magento\Config\App\Config\Type\System; -use Magento\Config\Console\Command\ConfigSet\EmulatedProcessorFacade; +use Magento\Config\Console\Command\ConfigSet\ProcessorFacadeFactory; use Magento\Deploy\Model\DeploymentConfig\ChangeDetector; use Magento\Deploy\Model\DeploymentConfig\Hash; -use Magento\Deploy\Model\DeploymentConfig\Validator; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Console\Cli; use Symfony\Component\Console\Command\Command; @@ -34,11 +33,11 @@ class ConfigSetCommand extends Command /**#@-*/ /** - * The emulated processor facade. + * Emulator adminhtml area for CLI command. * - * @var EmulatedProcessorFacade + * @var EmulatedAdminhtmlAreaProcessor */ - private $emulatedProcessorFacade; + private $emulatedAreaProcessor; /** * The config change detector. @@ -55,18 +54,28 @@ class ConfigSetCommand extends Command private $hash; /** - * @param EmulatedProcessorFacade $emulatedProcessorFacade The emulated processor facade + * The factory for processor facade. + * + * @var ProcessorFacadeFactory + */ + private $processorFacadeFactory; + + /** + * @param EmulatedAdminhtmlAreaProcessor $emulatedAreaProcessor Emulator adminhtml area for CLI command * @param ChangeDetector $changeDetector The config change detector * @param Hash $hash The hash manager + * @param ProcessorFacadeFactory $processorFacadeFactory The factory for processor facade */ public function __construct( - EmulatedProcessorFacade $emulatedProcessorFacade, + EmulatedAdminhtmlAreaProcessor $emulatedAreaProcessor, ChangeDetector $changeDetector, - Hash $hash + Hash $hash, + ProcessorFacadeFactory $processorFacadeFactory ) { - $this->emulatedProcessorFacade = $emulatedProcessorFacade; + $this->emulatedAreaProcessor = $emulatedAreaProcessor; $this->changeDetector = $changeDetector; $this->hash = $hash; + $this->processorFacadeFactory = $processorFacadeFactory; parent::__construct(); } @@ -128,13 +137,15 @@ protected function execute(InputInterface $input, OutputInterface $output) } try { - $message = $this->emulatedProcessorFacade->process( - $input->getArgument(static::ARG_PATH), - $input->getArgument(static::ARG_VALUE), - $input->getOption(static::OPTION_SCOPE), - $input->getOption(static::OPTION_SCOPE_CODE), - $input->getOption(static::OPTION_LOCK) - ); + $message = $this->emulatedAreaProcessor->process(function () use ($input) { + return $this->processorFacadeFactory->create()->process( + $input->getArgument(static::ARG_PATH), + $input->getArgument(static::ARG_VALUE), + $input->getOption(static::OPTION_SCOPE), + $input->getOption(static::OPTION_SCOPE_CODE), + $input->getOption(static::OPTION_LOCK) + ); + }); $this->hash->regenerate(System::CONFIG_TYPE); diff --git a/app/code/Magento/Config/Console/Command/EmulatedAdminhtmlAreaProcessor.php b/app/code/Magento/Config/Console/Command/EmulatedAdminhtmlAreaProcessor.php new file mode 100644 index 0000000000000..7d46fcc3412c1 --- /dev/null +++ b/app/code/Magento/Config/Console/Command/EmulatedAdminhtmlAreaProcessor.php @@ -0,0 +1,67 @@ +scope = $scope; + $this->state = $state; + } + + /** + * Emulates callback inside adminhtml area code and adminhtml scope. + * + * Returns the return value of the callback. + * + * @param callable $callback The callable to be called + * @param array $params The parameters to be passed to the callback, as an indexed array + * @return bool|int|float|string|array|null - as the result of this method is the result of callback, + * you can use callback only with specified in this method return types + * @throws \Exception The exception is thrown if the parameter $callback throws an exception + */ + public function process(callable $callback, array $params = []) + { + $currentScope = $this->scope->getCurrentScope(); + try { + return $this->state->emulateAreaCode(Area::AREA_ADMINHTML, function () use ($callback, $params) { + $this->scope->setCurrentScope(Area::AREA_ADMINHTML); + return call_user_func_array($callback, $params); + }); + } catch (\Exception $exception) { + throw $exception; + } finally { + $this->scope->setCurrentScope($currentScope); + } + } +} diff --git a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/EmulatedProcessorFacadeTest.php b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/EmulatedProcessorFacadeTest.php deleted file mode 100644 index 07c119cda8742..0000000000000 --- a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/EmulatedProcessorFacadeTest.php +++ /dev/null @@ -1,74 +0,0 @@ -scopeMock = $this->getMockBuilder(ScopeInterface::class) - ->getMockForAbstractClass(); - $this->stateMock = $this->getMockBuilder(State::class) - ->disableOriginalConstructor() - ->getMock(); - $this->processorFacadeFactory = $this->getMockBuilder(ProcessorFacadeFactory::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->model = new EmulatedProcessorFacade( - $this->scopeMock, - $this->stateMock, - $this->processorFacadeFactory - ); - } - - public function testProcess() - { - $this->scopeMock->expects($this->once()) - ->method('getCurrentScope') - ->willReturn('currentScope'); - $this->scopeMock->expects($this->once()) - ->method('setCurrentScope') - ->with('currentScope'); - $this->stateMock->expects($this->once()) - ->method('emulateAreaCode'); - - $this->model->process( - 'test/test/test', - 'value', - ScopeConfigInterface::SCOPE_TYPE_DEFAULT, - null, - false - ); - } -} diff --git a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSetCommandTest.php b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSetCommandTest.php index c0cbb210e457a..518bf7ff69fd0 100644 --- a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSetCommandTest.php +++ b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSetCommandTest.php @@ -6,11 +6,11 @@ namespace Magento\Config\Test\Unit\Console\Command; use Magento\Config\App\Config\Type\System; -use Magento\Config\Console\Command\ConfigSet\EmulatedProcessorFacade; +use Magento\Config\Console\Command\ConfigSet\ProcessorFacadeFactory; use Magento\Config\Console\Command\ConfigSetCommand; +use Magento\Config\Console\Command\EmulatedAdminhtmlAreaProcessor; use Magento\Deploy\Model\DeploymentConfig\ChangeDetector; use Magento\Deploy\Model\DeploymentConfig\Hash; -use Magento\Deploy\Model\DeploymentConfig\Validator; use Magento\Framework\Console\Cli; use Magento\Framework\Exception\ValidatorException; use PHPUnit_Framework_MockObject_MockObject as Mock; @@ -29,9 +29,9 @@ class ConfigSetCommandTest extends \PHPUnit_Framework_TestCase private $command; /** - * @var EmulatedProcessorFacade|Mock + * @var EmulatedAdminhtmlAreaProcessor|Mock */ - private $emulatedProcessorFacadeMock; + private $emulatedAreProcessorMock; /** * @var ChangeDetector|Mock @@ -43,12 +43,17 @@ class ConfigSetCommandTest extends \PHPUnit_Framework_TestCase */ private $hashMock; + /** + * @var ProcessorFacadeFactory|Mock + */ + private $processorFacadeFactoryMock; + /** * @inheritdoc */ protected function setUp() { - $this->emulatedProcessorFacadeMock = $this->getMockBuilder(EmulatedProcessorFacade::class) + $this->emulatedAreProcessorMock = $this->getMockBuilder(EmulatedAdminhtmlAreaProcessor::class) ->disableOriginalConstructor() ->getMock(); $this->changeDetectorMock = $this->getMockBuilder(ChangeDetector::class) @@ -57,11 +62,15 @@ protected function setUp() $this->hashMock = $this->getMockBuilder(Hash::class) ->disableOriginalConstructor() ->getMock(); + $this->processorFacadeFactoryMock = $this->getMockBuilder(ProcessorFacadeFactory::class) + ->disableOriginalConstructor() + ->getMock(); $this->command = new ConfigSetCommand( - $this->emulatedProcessorFacadeMock, + $this->emulatedAreProcessorMock, $this->changeDetectorMock, - $this->hashMock + $this->hashMock, + $this->processorFacadeFactoryMock ); } @@ -70,7 +79,7 @@ public function testExecute() $this->changeDetectorMock->expects($this->once()) ->method('hasChanges') ->willReturn(false); - $this->emulatedProcessorFacadeMock->expects($this->once()) + $this->emulatedAreProcessorMock->expects($this->once()) ->method('process') ->willReturn('Some message'); $this->hashMock->expects($this->once()) @@ -95,7 +104,7 @@ public function testExecuteNeedsRegeneration() $this->changeDetectorMock->expects($this->once()) ->method('hasChanges') ->willReturn(true); - $this->emulatedProcessorFacadeMock->expects($this->never()) + $this->emulatedAreProcessorMock->expects($this->never()) ->method('process'); $tester = new CommandTester($this->command); @@ -116,7 +125,7 @@ public function testExecuteWithException() $this->changeDetectorMock->expects($this->once()) ->method('hasChanges') ->willReturn(false); - $this->emulatedProcessorFacadeMock->expects($this->once()) + $this->emulatedAreProcessorMock->expects($this->once()) ->method('process') ->willThrowException(new ValidatorException(__('The "test/test/test" path does not exists'))); diff --git a/app/code/Magento/Config/Test/Unit/Console/Command/EmulatedAdminhtmlAreaProcessorTest.php b/app/code/Magento/Config/Test/Unit/Console/Command/EmulatedAdminhtmlAreaProcessorTest.php new file mode 100644 index 0000000000000..29de0dc0b3c2b --- /dev/null +++ b/app/code/Magento/Config/Test/Unit/Console/Command/EmulatedAdminhtmlAreaProcessorTest.php @@ -0,0 +1,95 @@ +scopeMock = $this->getMockBuilder(ScopeInterface::class) + ->getMockForAbstractClass(); + $this->stateMock = $this->getMockBuilder(State::class) + ->disableOriginalConstructor() + ->setMethods(['emulateAreaCode']) + ->getMock(); + + $this->emulatedAdminhtmlProcessorArea = new EmulatedAdminhtmlAreaProcessor( + $this->scopeMock, + $this->stateMock + ); + } + + public function testProcess() + { + $currentScope = 'currentScope'; + $callback = function () { + }; + $this->scopeMock->expects($this->once()) + ->method('getCurrentScope') + ->willReturn($currentScope); + + $this->scopeMock->expects($this->once()) + ->method('setCurrentScope') + ->with($currentScope); + + $this->stateMock->expects($this->once()) + ->method('emulateAreaCode') + ->with(Area::AREA_ADMINHTML, $callback) + ->willReturn('result'); + + $this->assertEquals('result', $this->emulatedAdminhtmlProcessorArea->process($callback)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Some Message + */ + public function testProcessWithException() + { + $currentScope = 'currentScope'; + $this->scopeMock->expects($this->once()) + ->method('getCurrentScope') + ->willReturn($currentScope); + + $this->scopeMock->expects($this->once()) + ->method('setCurrentScope') + ->with($currentScope); + + $this->stateMock->expects($this->once()) + ->method('emulateAreaCode') + ->willThrowException(new \Exception('Some Message')); + + $this->emulatedAdminhtmlProcessorArea->process(function () { + }); + } +} diff --git a/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommand.php b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommand.php index 30b2117b48864..47d227253777b 100644 --- a/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommand.php +++ b/app/code/Magento/Deploy/Console/Command/App/SensitiveConfigSetCommand.php @@ -6,10 +6,10 @@ namespace Magento\Deploy\Console\Command\App; use Magento\Config\App\Config\Type\System; +use Magento\Config\Console\Command\EmulatedAdminhtmlAreaProcessor; use Magento\Deploy\Console\Command\App\SensitiveConfigSet\SensitiveConfigSetFacade; use Magento\Deploy\Model\DeploymentConfig\ChangeDetector; use Magento\Deploy\Model\DeploymentConfig\Hash; -use Magento\Deploy\Model\DeploymentConfig\Validator; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Console\Cli; use Symfony\Component\Console\Command\Command; @@ -69,19 +69,29 @@ class SensitiveConfigSetCommand extends Command */ private $facade; + /** + * Emulator adminhtml area for CLI command. + * + * @var EmulatedAdminhtmlAreaProcessor + */ + private $emulatedAreaProcessor; + /** * @param SensitiveConfigSetFacade $facade The processor facade * @param ChangeDetector $changeDetector The config change detector * @param Hash $hash The hash manager + * @param EmulatedAdminhtmlAreaProcessor $emulatedAreaProcessor Emulator adminhtml area for CLI command */ public function __construct( SensitiveConfigSetFacade $facade, ChangeDetector $changeDetector, - Hash $hash + Hash $hash, + EmulatedAdminhtmlAreaProcessor $emulatedAreaProcessor ) { $this->facade = $facade; $this->changeDetector = $changeDetector; $this->hash = $hash; + $this->emulatedAreaProcessor = $emulatedAreaProcessor; parent::__construct(); } @@ -143,7 +153,9 @@ protected function execute(InputInterface $input, OutputInterface $output) } try { - $this->facade->process($input, $output); + $this->emulatedAreaProcessor->process(function () use ($input, $output) { + $this->facade->process($input, $output); + }); $this->hash->regenerate(System::CONFIG_TYPE); return Cli::RETURN_SUCCESS; diff --git a/app/code/Magento/Deploy/Model/ConfigWriter.php b/app/code/Magento/Deploy/Model/ConfigWriter.php index 3b51bd42e14d7..5bd2b0e071dcc 100644 --- a/app/code/Magento/Deploy/Model/ConfigWriter.php +++ b/app/code/Magento/Deploy/Model/ConfigWriter.php @@ -7,9 +7,12 @@ use Magento\Config\App\Config\Type\System; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Config\Value; use Magento\Framework\App\DeploymentConfig\Writer; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Config\File\ConfigFilePool; use Magento\Framework\Stdlib\ArrayManager; +use Magento\Config\Model\PreparedValueFactory; /** * Class ConfigWriter. Save configuration values into config file. @@ -26,16 +29,26 @@ class ConfigWriter */ private $arrayManager; + /** + * Creates a prepared instance of Value. + * + * @var PreparedValueFactory + */ + private $preparedValueFactory; + /** * @param Writer $writer * @param ArrayManager $arrayManager + * @param PreparedValueFactory|null $valueFactory Creates a prepared instance of Value */ public function __construct( Writer $writer, - ArrayManager $arrayManager + ArrayManager $arrayManager, + PreparedValueFactory $valueFactory = null ) { $this->writer = $writer; $this->arrayManager = $arrayManager; + $this->preparedValueFactory = $valueFactory ?: ObjectManager::getInstance()->get(PreparedValueFactory::class); } /** @@ -52,6 +65,15 @@ public function save(array $values, $scope = ScopeConfigInterface::SCOPE_TYPE_DE $pathPrefix = $this->getPathPrefix($scope, $scopeCode); foreach ($values as $configPath => $configValue) { $fullConfigPath = $pathPrefix . $configPath; + $backendModel = $this->preparedValueFactory->create($configPath, $configValue, $scope, $scopeCode); + + if ($backendModel instanceof Value) { + $backendModel->validateBeforeSave(); + $backendModel->beforeSave(); + $configValue = $backendModel->getValue(); + $backendModel->afterSave(); + } + $config = $this->setConfig($config, $fullConfigPath, $configValue); } diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSetCommandTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSetCommandTest.php index a227d130ef38a..6254e10523f28 100644 --- a/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSetCommandTest.php +++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/App/SensitiveConfigSetCommandTest.php @@ -6,11 +6,11 @@ namespace Magento\Deploy\Test\Unit\Console\Command\App; use Magento\Config\App\Config\Type\System; +use Magento\Config\Console\Command\EmulatedAdminhtmlAreaProcessor; use Magento\Deploy\Console\Command\App\SensitiveConfigSet\SensitiveConfigSetFacade; use Magento\Deploy\Console\Command\App\SensitiveConfigSetCommand; use Magento\Deploy\Model\DeploymentConfig\ChangeDetector; use Magento\Deploy\Model\DeploymentConfig\Hash; -use Magento\Deploy\Model\DeploymentConfig\Validator; use Magento\Framework\Console\Cli; use PHPUnit_Framework_MockObject_MockObject as MockObject; use Symfony\Component\Console\Tester\CommandTester; @@ -35,6 +35,11 @@ class SensitiveConfigSetCommandTest extends \PHPUnit_Framework_TestCase */ private $hashMock; + /** + * @var EmulatedAdminhtmlAreaProcessor|MockObject + */ + private $emulatedAreaProcessorMock; + /** * @var SensitiveConfigSetCommand */ @@ -54,11 +59,15 @@ public function setUp() $this->hashMock = $this->getMockBuilder(Hash::class) ->disableOriginalConstructor() ->getMock(); + $this->emulatedAreaProcessorMock = $this->getMockBuilder(EmulatedAdminhtmlAreaProcessor::class) + ->disableOriginalConstructor() + ->getMock(); $this->model = new SensitiveConfigSetCommand( $this->facadeMock, $this->changeDetectorMock, - $this->hashMock + $this->hashMock, + $this->emulatedAreaProcessorMock ); } @@ -67,7 +76,7 @@ public function testExecute() $this->changeDetectorMock->expects($this->once()) ->method('hasChanges') ->willReturn(false); - $this->facadeMock->expects($this->once()) + $this->emulatedAreaProcessorMock->expects($this->once()) ->method('process'); $this->hashMock->expects($this->once()) ->method('regenerate') @@ -87,7 +96,7 @@ public function testExecuteNeedsRegeneration() $this->changeDetectorMock->expects($this->once()) ->method('hasChanges') ->willReturn(true); - $this->facadeMock->expects($this->never()) + $this->emulatedAreaProcessorMock->expects($this->never()) ->method('process'); $this->hashMock->expects($this->never()) ->method('regenerate'); @@ -110,7 +119,7 @@ public function testExecuteWithException() $this->changeDetectorMock->expects($this->once()) ->method('hasChanges') ->willReturn(false); - $this->facadeMock->expects($this->once()) + $this->emulatedAreaProcessorMock->expects($this->once()) ->method('process') ->willThrowException(new \Exception('Some exception')); diff --git a/app/code/Magento/Deploy/Test/Unit/Model/ConfigWriterTest.php b/app/code/Magento/Deploy/Test/Unit/Model/ConfigWriterTest.php index d41ade42c2c1b..d8d10f071bf2b 100644 --- a/app/code/Magento/Deploy/Test/Unit/Model/ConfigWriterTest.php +++ b/app/code/Magento/Deploy/Test/Unit/Model/ConfigWriterTest.php @@ -5,7 +5,10 @@ */ namespace Magento\Deploy\Test\Unit\Model; +use Magento\Config\Model\PreparedValueFactory; use Magento\Deploy\Model\ConfigWriter; +use Magento\Framework\App\Config\ValueInterface; +use Magento\Framework\App\Config\Value; use Magento\Framework\App\DeploymentConfig\Writer; use Magento\Framework\Config\File\ConfigFilePool; use Magento\Framework\Stdlib\ArrayManager; @@ -23,6 +26,21 @@ class ConfigWriterTest extends \PHPUnit_Framework_TestCase */ private $arrayManagerMock; + /** + * @var PreparedValueFactory|MockObject + */ + private $preparedValueFactoryMock; + + /** + * @var ValueInterface|MockObject + */ + private $valueInterfaceMock; + + /** + * @var Value|MockObject + */ + private $valueMock; + /** * @var ConfigWriter */ @@ -39,10 +57,21 @@ public function setUp() $this->writerMock = $this->getMockBuilder(Writer::class) ->disableOriginalConstructor() ->getMock(); + $this->preparedValueFactoryMock = $this->getMockBuilder(PreparedValueFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->valueInterfaceMock = $this->getMockBuilder(ValueInterface::class) + ->getMockForAbstractClass(); + $this->valueMock = $this->getMockBuilder(Value::class) + ->disableOriginalConstructor() + ->setMethods(['validateBeforeSave', 'beforeSave', 'getValue', 'afterSave']) + ->getMock(); $this->model = new ConfigWriter( $this->writerMock, - $this->arrayManagerMock + $this->arrayManagerMock, + $this->preparedValueFactoryMock ); } @@ -55,6 +84,29 @@ public function testSave() ]; $config = ['system' => []]; + $this->preparedValueFactoryMock->expects($this->exactly(3)) + ->method('create') + ->withConsecutive( + ['some1/config1/path1', 'someValue1', 'scope', 'scope_code'], + ['some2/config2/path2', 'someValue2', 'scope', 'scope_code'], + ['some3/config3/path3', 'someValue3', 'scope', 'scope_code'] + ) + ->willReturnOnConsecutiveCalls( + $this->valueInterfaceMock, + $this->valueMock, + $this->valueMock + ); + + $this->valueMock->expects($this->exactly(2)) + ->method('validateBeforeSave'); + $this->valueMock->expects($this->exactly(2)) + ->method('beforeSave'); + $this->valueMock->expects($this->exactly(2)) + ->method('getValue') + ->willReturnOnConsecutiveCalls('someValue2', 'someValue3'); + $this->valueMock->expects($this->exactly(2)) + ->method('afterSave'); + $this->arrayManagerMock->expects($this->exactly(3)) ->method('set') ->withConsecutive( @@ -79,6 +131,29 @@ public function testSaveDefaultScope() ]; $config = ['system' => []]; + $this->preparedValueFactoryMock->expects($this->exactly(3)) + ->method('create') + ->withConsecutive( + ['some1/config1/path1', 'someValue1', 'default'], + ['some2/config2/path2', 'someValue2', 'default'], + ['some3/config3/path3', 'someValue3', 'default'] + ) + ->willReturnOnConsecutiveCalls( + $this->valueInterfaceMock, + $this->valueMock, + $this->valueMock + ); + + $this->valueMock->expects($this->exactly(2)) + ->method('validateBeforeSave'); + $this->valueMock->expects($this->exactly(2)) + ->method('beforeSave'); + $this->valueMock->expects($this->exactly(2)) + ->method('getValue') + ->willReturnOnConsecutiveCalls('someValue2', 'someValue3'); + $this->valueMock->expects($this->exactly(2)) + ->method('afterSave'); + $this->arrayManagerMock->expects($this->exactly(3)) ->method('set') ->withConsecutive(