laravel/framework · error · LogicException

Circular alias reference for

Error message

Circular alias reference for [{$abstract}].

What it means

LogicException from getAlias() when following the alias chain enters a cycle (e.g. A aliases to B which aliases back to A). The method walks $this->aliases tracking seen nodes and aborts rather than looping forever.

Solutions

  1. Break the cycle by removing one of the symmetric alias() calls.
  2. Audit all alias() registrations (including package providers) for the names in the error and eliminate the loop.
  3. Ensure aliases always point toward the concrete/abstract, not back to another alias.
  4. Use getAlias() defensively in tests to detect cycles early during provider boot.

Example fix

// before
$app->alias('cache', CacheManager::class);
$app->alias(CacheManager::class, 'cache'); // cycle
// after
$app->alias(CacheManager::class, 'cache'); // single direction: abstract -> short alias
Defensive patterns

Strategy: validation

Validate before calling

// Detect alias cycles before they trigger at runtime.
function aliasCycleExists(array $aliases, string $start): bool
{
    $seen = [];
    $cur = $start;
    while (isset($aliases[$cur])) {
        if (isset($seen[$cur])) return true;
        $seen[$cur] = true;
        $cur = $aliases[$cur];
    }
    return false;
}

// build $aliases from your providers, then assert no cycle
foreach (array_keys($aliases) as $k) {
    if (aliasCycleExists($aliases, $k)) throw new RuntimeException("Alias cycle at [$k]");
}

Type guard

function aliasesAreAcyclic(array $aliases): bool
{
    foreach (array_keys($aliases) as $start) {
        $seen = []; $cur = $start;
        while (isset($aliases[$cur])) {
            if (isset($seen[$cur])) return false;
            $seen[$cur] = true; $cur = $aliases[$cur];
        }
    }
    return true;
}

Try / catch

try {
    $container->alias($abstract, $alias);
    $container->getAlias($alias);
} catch (\LogicException $e) {
    if (str_contains($e->getMessage(), 'Circular alias')) {
        // remove the reverse alias() that closed the loop
    }
    throw $e;
}

Prevention

When it happens

Trigger: Calling alias(A, B) then alias(B, A); multiple providers each registering aliases that together form a loop; an alias pointing to a key that is itself aliased back through configuration.

Common situations: Two service providers aliasing the same names in opposite directions; refactoring that swaps abstract and alias and leaves the old alias in place; config-driven alias generation producing symmetric entries.

Related errors


AI-assisted analysis of laravel/framework@e0f6eb3518 (2026-08-11). Data as JSON: /api/errors/c28869605db06cbd. Report an issue: GitHub.

Appendix: source

Thrown at src/Illuminate/Container/Container.php:1681

    {
        return $this->bindings;
    }

    /**
     * Get the alias for an abstract if available.
     *
     * @param  string  $abstract
     * @return string
     *
     * @throws \LogicException
     */
    public function getAlias($abstract)
    {
        $seen = [];

        while (isset($this->aliases[$abstract])) {
            if (isset($seen[$abstract])) {
                throw new LogicException("Circular alias reference for [{$abstract}].");
            }

            $seen[$abstract] = true;

            $abstract = $this->aliases[$abstract];
        }

        return $abstract;
    }

    /**
     * Get the extender callbacks for a given type.
     *
     * @param  string  $abstract
     * @return array
     */
    protected function getExtenders($abstract)
    {

View on GitHub (pinned to e0f6eb3518)