Scope Hierarchy Deep Dive
Complete Hierarchy
┌─────────────â”
│ Default │ app/etc/config.php
└──────┬──────┘
│
┌──────▼──────â”
│ Website │ shared payment/shipping
└──────┬──────┘
│
┌──────▼──────â”
│ Store │ shared product catalog
└──────┬──────┘
│
┌──────▼──────â”
│ Store View │ language/display
└─────────────┘
Hierarchy Navigator
namespace Vendor\Config\Hierarchy;
class HierarchyNavigator
{
private StoreManagerInterface $storeManager;
public function getAncestors(int $storeId): array
{
$store = $this->storeManager->getStore($storeId);
$website = $store->getWebsite();
return [
'store_view' => $storeId,
'store' => $store->getGroupId(),
'website' => $website->getId(),
'default' => null,
];
}
public function getDescendants(string $scope, int $scopeId): array
{
return match ($scope) {
'default' => $this->getAllWebsites(),
'websites' => $this->getWebsiteStores($scopeId),
'stores' => $this->getStoreViews($scopeId),
default => [],
};
}
}
Config Value Resolution
Resolution Algorithm
namespace Vendor\Config\Resolver;
class ConfigValueResolver
{
private ConfigInterface $config;
private HierarchyNavigatorInterface $navigator;
public function resolve(
string $path,
int $storeId
): ResolvedValue {
$ancestors = $this->navigator->getAncestors($storeId);
// Check each scope level
foreach ($ancestors as $scope => $scopeId) {
$scopeName = match ($scope) {
'store_view' => ScopeInterface::SCOPE_STORES,
'store' => ScopeInterface::SCOPE_STORES,
'website' => ScopeInterface::SCOPE_WEBSITES,
'default' => ScopeInterface::SCOPE_DEFAULT,
};
$value = $this->config->getValue(
$path,
$scopeName,
$scopeId
);
if ($value !== null) {
return new ResolvedValue([
'value' => $value,
'scope' => $scope,
'scope_id' => $scopeId,
]);
}
}
return new ResolvedValue([
'value' => null,
'scope' => 'default',
'scope_id' => null,
]);
}
}
Resolution Cache
namespace Vendor\Config\Resolver\Cache;
class CachedConfigResolver implements ConfigValueResolverInterface
{
private ConfigInterface $config;
private CacheInterface $cache;
public function resolve(string $path, int $storeId): ResolvedValue
{
$cacheKey = sprintf('config_%s_%d', md5($path), $storeId);
$cached = $this->cache->load($cacheKey);
if ($cached) {
return unserialize($cached);
}
$result = $this->doResolve($path, $storeId);
$this->cache->save(serialize($result), $cacheKey, [], 3600);
return $result;
}
}
Override Behavior
Override Detection
namespace Vendor\Config\Override;
class OverrideDetector
{
private ConfigInterface $config;
public function detectOverrides(int $storeId): array
{
$overrides = [];
$allPaths = $this->config->getAllPaths();
foreach ($allPaths as $path) {
$storeValue = $this->config->getValue(
$path,
ScopeInterface::SCOPE_STORES,
$storeId
);
if ($storeValue !== null) {
$websiteValue = $this->config->getValue(
$path,
ScopeInterface::SCOPE_WEBSITES,
$this->getWebsiteId($storeId)
);
$defaultValue = $this->config->getValue(
$path,
ScopeInterface::SCOPE_DEFAULT
);
$overrides[$path] = [
'value' => $storeValue,
'inherited_from' => $websiteValue ?? $defaultValue,
];
}
}
return $overrides;
}
}
Override Reset
namespace Vendor\Config\Override\Reset;
class OverrideResetter
{
public function resetToInherited(
string $path,
int $storeId
): void {
// Remove store-level value
$this->config->setValue(
$path,
null,
ScopeInterface::SCOPE_STORES,
$storeId
);
$this->config->save();
}
public function resetAllOverrides(int $storeId): int
{
$overrides = $this->detector->detectOverrides($storeId);
$count = 0;
foreach (array_keys($overrides) as $path) {
$this->resetToInherited($path, $storeId);
$count++;
}
return $count;
}
}
Cascading Configuration
Cascading Config Manager
namespace Vendor\Config\Cascade;
class CascadingConfigManager
{
private ConfigInterface $config;
private HierarchyNavigatorInterface $navigator;
public function cascade(
string $path,
mixed $value,
string $scope,
int $scopeId
): CascadeResult {
$descendants = $this->navigator->getDescendants($scope, $scopeId);
$affected = 0;
foreach ($descendants as $descendant) {
$currentValue = $this->config->getValue(
$path,
$descendant['scope'],
$descendant['scope_id']
);
// Only cascade if no override exists
if ($currentValue === null) {
$this->config->setValue(
$path,
$value,
$descendant['scope'],
$descendant['scope_id']
);
$affected++;
}
}
$this->config->save();
return new CascadeResult([
'affected' => $affected,
'total' => count($descendants),
]);
}
}
Scope Comparison
namespace Vendor\Config\Comparison;
class ScopeComparator
{
public function compare(
string $path,
int $storeId
): ScopeComparisonResult {
$values = [];
$scopes = ['default', 'websites', 'stores'];
foreach ($scopes as $scope) {
$scopeId = match ($scope) {
'default' => null,
'websites' => $this->getWebsiteId($storeId),
'stores' => $storeId,
};
$values[$scope] = $this->config->getValue(
$path,
$scope,
$scopeId
);
}
return new ScopeComparisonResult([
'values' => $values,
'is_inherited' => $values['stores'] === null,
'effective_scope' => $values['stores'] !== null ? 'stores' : (
$values['websites'] !== null ? 'websites' : 'default'
),
]);
}
}
Quiz
1. What happens when a store view value is not set?
2. How do you detect scope overrides?
3. What does cascading configuration do?
Flashcards
Question
What is config inheritance?
Click to reveal answer
Answer
Values cascade from default → website → store → store view
Question
How does value resolution work?
Click to reveal answer
Answer
Check most specific scope first, fall back to broader
Question
What is a scope override?
Click to reveal answer
Answer
A scope-specific value that differs from inherited value
Question
What is cascading config?
Click to reveal answer
Answer
Propagating values to descendant scopes
Revision Notes
Key Takeaways
- 1. Config inheritance cascades: store view → website → default
- 2. Value resolution checks most specific scope first
- 3. Overrides exist when scope-specific value differs from inherited
- 4. Cascading propagates values to descendants without overrides
- 5. Override detection enables admin UI for scope management
Interview Tips
- • Explain the config resolution algorithm
- • Discuss override detection and reset patterns
- • Describe cascading configuration use cases
- • Talk about performance implications of scope resolution
Cheat Sheet
Resolution:
Check store → website → default
First non-null value wins
Override:
Store value != inherited value
Detected by comparing scopes
Cascading:
Set value in all descendants
Skip scopes with overrides
Reset:
Remove scope-specific value
Falls back to inherited