Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

use Magento\Framework\App\EnvironmentInterface;
use Magento\Framework\App\Interception\Cache\CompiledConfig;
use Magento\Framework\ObjectManager\ConfigLoaderInterface;
use Magento\Framework\ObjectManager\FactoryInterface;
use Magento\Framework\App\Area;
use Magento\Framework\Interception\ObjectManager\ConfigInterface;
Expand Down Expand Up @@ -64,7 +65,8 @@ public function getDiConfig()
{
if (!$this->config) {
$this->config = new \Magento\Framework\Interception\ObjectManager\Config\Compiled(
$this->getConfigData()
$this->getConfigData(),
$this->getObjectManagerConfigLoader()
);
}

Expand All @@ -78,7 +80,8 @@ public function getDiConfig()
*/
protected function getConfigData()
{
return $this->getObjectManagerConfigLoader()->load(Area::AREA_GLOBAL);
return $this->getObjectManagerConfigLoader()->load(Area::AREA_GLOBAL)
+ [ConfigLoaderInterface::AREA_KEY => Area::AREA_GLOBAL];
}

/**
Expand All @@ -103,11 +106,7 @@ public function configureObjectManager(ConfigInterface $diConfig, &$sharedInstan
{
$objectManager = ObjectManager::getInstance();

$objectManager->configure(
$objectManager
->get(\Magento\Framework\ObjectManager\ConfigLoaderInterface::class)
->load(Area::AREA_GLOBAL)
);
$objectManager->configure($this->getConfigData());
$objectManager->get(\Magento\Framework\Config\ScopeInterface::class)
->setCurrentScope('global');
$diConfig->setInterceptionConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

use Magento\Framework\ObjectManager\ConfigInterface;
use Magento\Framework\ObjectManager\ConfigCacheInterface;
use Magento\Framework\ObjectManager\ConfigLoaderInterface;
use Magento\Framework\ObjectManager\LazyTypeAwareInterface;
use Magento\Framework\ObjectManager\RelationsInterface;

Expand Down Expand Up @@ -37,11 +38,26 @@ class Compiled implements ConfigInterface, LazyTypeAwareInterface
*/
private array $lazyTypes = [];

/**
* Area whose configuration is currently applied, null when the state matches no single area
*
* @var string|null
*/
private $appliedArea;

/**
* @var ConfigLoaderInterface|null
*/
private $configLoader;

/**
* @param array $data
* @param ConfigLoaderInterface|null $configLoader
*/
public function __construct($data)
public function __construct($data, ?ConfigLoaderInterface $configLoader = null)
{
$this->configLoader = $configLoader;
$this->appliedArea = $data[ConfigLoaderInterface::AREA_KEY] ?? null;
$this->arguments = isset($data['arguments']) && is_array($data['arguments'])
? $data['arguments'] : [];
$this->virtualTypes = isset($data['instanceTypes']) && is_array($data['instanceTypes'])
Expand Down Expand Up @@ -164,6 +180,8 @@ public function getPreference($type)
*/
public function extend(array $configuration)
{
$configuration = $this->resolveAgainstAppliedArea($configuration);

$this->arguments = isset($configuration['arguments']) && is_array($configuration['arguments'])
? array_replace($this->arguments, $configuration['arguments'])
: $this->arguments;
Expand All @@ -178,6 +196,42 @@ public function extend(array $configuration)
: $this->lazyTypes;
}

/**
* Returns a configuration that is equivalent to the complete one on top of the current state
*
* @param array $configuration
* @return array
*/
private function resolveAgainstAppliedArea(array $configuration)
{
$base = $configuration[ConfigLoaderInterface::EXTENDS_KEY] ?? null;
$resolved = $base !== null && $base !== $this->appliedArea && $this->configLoader !== null
? self::merge($this->configLoader->load($base), $configuration)
: $configuration;

$this->appliedArea = $configuration[ConfigLoaderInterface::AREA_KEY] ?? null;

return $resolved;
}

/**
* Applies a configuration on top of another one, per top-level key of each section
*
* @param array $base
* @param array $configuration
* @return array
*/
private static function merge(array $base, array $configuration)
{
foreach (['arguments', 'instanceTypes', 'preferences', 'lazyTypes'] as $section) {
if (isset($configuration[$section]) && is_array($configuration[$section])) {
$base[$section] = array_replace($base[$section] ?? [], $configuration[$section]);
}
}

return $base;
}

/**
* Retrieve all virtual types
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@
*/
interface ConfigLoaderInterface
{
/**
* When present, names the area the loaded configuration only holds the differences against
*/
public const EXTENDS_KEY = '_extends';

/**
* When present, names the area the loaded configuration belongs to
*/
public const AREA_KEY = '_area';

/**
* Load modules DI configuration
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace Magento\Framework\ObjectManager\Test\Unit\Config;

use Magento\Framework\ObjectManager\Config\Compiled;
use Magento\Framework\ObjectManager\ConfigLoaderInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\DataProvider;
Expand Down Expand Up @@ -347,4 +348,90 @@ public function testExtendMergesLazyTypes(): void
$this->assertFalse($compiled->isNonLazyType('Second\\Type'));
$this->assertTrue($compiled->isNonLazyType('Other\\Type'));
}

public function testExtendAppliesADeltaOnTopOfTheAreaItExtends(): void
{
$configLoader = $this->createMock(ConfigLoaderInterface::class);
$configLoader->expects($this->never())->method('load');

$compiled = new Compiled(
[
'preferences' => ['preference1' => 'globalValue'],
ConfigLoaderInterface::AREA_KEY => 'global',
],
$configLoader
);

$compiled->extend([
ConfigLoaderInterface::EXTENDS_KEY => 'global',
'preferences' => ['preference1' => 'frontendValue'],
]);

$this->assertSame('frontendValue', $compiled->getPreference('preference1'));
}

public function testExtendRebuildsFromTheBaseWhenAnotherAreaIsAlreadyApplied(): void
{
$globalConfig = ['preferences' => ['preference1' => 'globalValue']];
$configLoader = $this->createMock(ConfigLoaderInterface::class);
$configLoader->expects($this->once())
->method('load')
->with('global')
->willReturn($globalConfig);

$compiled = new Compiled(
$globalConfig + [ConfigLoaderInterface::AREA_KEY => 'global'],
$configLoader
);

$compiled->extend([
ConfigLoaderInterface::EXTENDS_KEY => 'global',
'preferences' => ['preference1' => 'adminhtmlValue'],
]);
$compiled->extend([
ConfigLoaderInterface::EXTENDS_KEY => 'global',
'preferences' => ['preference2' => 'frontendOnly'],
]);

$this->assertSame('globalValue', $compiled->getPreference('preference1'));
$this->assertSame('frontendOnly', $compiled->getPreference('preference2'));
}

public function testExtendRebuildsFromTheBaseAfterAnArbitraryConfiguration(): void
{
$globalConfig = ['preferences' => ['preference1' => 'globalValue']];
$configLoader = $this->createMock(ConfigLoaderInterface::class);
$configLoader->expects($this->once())
->method('load')
->with('global')
->willReturn($globalConfig);

$compiled = new Compiled(
$globalConfig + [ConfigLoaderInterface::AREA_KEY => 'global'],
$configLoader
);

$compiled->extend(['preferences' => ['preference1' => 'mockedValue']]);
$compiled->extend([
ConfigLoaderInterface::EXTENDS_KEY => 'global',
'preferences' => [],
]);

$this->assertSame('globalValue', $compiled->getPreference('preference1'));
}

public function testExtendAppliesADeltaWhenNoLoaderIsAvailable(): void
{
$compiled = new Compiled([
'preferences' => ['preference1' => 'globalValue'],
ConfigLoaderInterface::AREA_KEY => 'global',
]);

$compiled->extend([
ConfigLoaderInterface::EXTENDS_KEY => 'global',
'preferences' => ['preference1' => 'frontendValue'],
]);

$this->assertSame('frontendValue', $compiled->getPreference('preference1'));
}
}
43 changes: 43 additions & 0 deletions setup/src/Magento/Setup/Module/Di/App/Task/Operation/Area.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

use Magento\Setup\Module\Di\App\Task\OperationInterface;
use Magento\Framework\App;
use Magento\Framework\ObjectManager\ConfigLoaderInterface;
use Magento\Setup\Module\Di\Compiler\Config;
use Magento\Setup\Module\Di\Definition\Collection as DefinitionsCollection;

Expand All @@ -15,6 +16,11 @@
*/
class Area implements OperationInterface
{
/**
* Sections of the compiled configuration that are merged per top-level key
*/
private const MERGED_SECTIONS = ['arguments', 'preferences', 'instanceTypes', 'lazyTypes'];

/**
* @var App\AreaList
*/
Expand Down Expand Up @@ -91,6 +97,7 @@ public function doOperation()
$this->sortDefinitions($definitionsCollection);

$areaCodes = array_merge([App\Area::AREA_GLOBAL], $this->areaList->getCodes());
$globalConfig = [];
foreach ($areaCodes as $areaCode) {
$config = $this->configReader->generateCachePerScope($definitionsCollection, $areaCode);
$config = $this->modificationChain->modify($config);
Expand All @@ -100,10 +107,46 @@ public function doOperation()
ksort($config['preferences']);
ksort($config['instanceTypes']);

if ($areaCode === App\Area::AREA_GLOBAL) {
$globalConfig = $config;
} else {
$config = $this->extractDiff($config, $globalConfig);
}

$this->configWriter->write($areaCode, $config);
}
}

/**
* Reduces an area configuration to the entries that differ from the global one
*
* @param array $config
* @param array $globalConfig
* @return array
*/
private function extractDiff(array $config, array $globalConfig)
{
$diff = [ConfigLoaderInterface::EXTENDS_KEY => App\Area::AREA_GLOBAL];

foreach ($config as $section => $values) {
if (!is_array($values) || !in_array($section, self::MERGED_SECTIONS, true)) {
$diff[$section] = $values;
continue;
}

$globalValues = $globalConfig[$section] ?? [];
$sectionDiff = [];
foreach ($values as $key => $value) {
if (!array_key_exists($key, $globalValues) || $globalValues[$key] !== $value) {
$sectionDiff[$key] = $value;
}
}
$diff[$section] = $sectionDiff;
}

return $diff;
}

/**
* Returns definitions collection
*
Expand Down
Loading
Loading