Skip to content
intermediate Phase 91 · Multi-store

Configuration Inheritance

Configuration inheritance - scope hierarchy, config value resolution, override behavior

45m
0 problems
Topic Progress 0%

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?

Question 1 options

2. How do you detect scope overrides?

Question 2 options

3. What does cascading configuration do?

Question 3 options

Flashcards

Question

What is config inheritance?

Answer

Values cascade from default → website → store → store view

Question

How does value resolution work?

Answer

Check most specific scope first, fall back to broader

Question

What is a scope override?

Answer

A scope-specific value that differs from inherited value

Question

What is cascading config?

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