laravel/framework · error · RuntimeException

Unable to bind custom driver callback

Error message

Unable to bind custom driver callback

What it means

Thrown by AuthManager::extend() on the null-coalesce-throw line when bindCallbackToSelf($callback) returns null. extend() attempts to rebind the supplied Closure so that $this inside it refers to the AuthManager; if the rebinder cannot produce a bound closure (returns null), it throws this RuntimeException. This is distinct from the ReflectionException branch on the next line.

Solutions

  1. Pass a non-static closure so it can be rebound to the AuthManager instance.
  2. If the closure must be static, restructure to avoid relying on $this binding, or wrap logic in an invokable class.
  3. Ensure the closure is a real Closure (not a first-class callable string in a context that strips scope).
  4. Confirm PHP version compatibility for Closure::bind / Closure::bindTo behavior.

Example fix

// before — static closure cannot be rebound
Auth::extend('jwt', static function ($app, array $config) {
    return new JwtGuard(...);
});

// after — non-static closure
Auth::extend('jwt', function ($app, array $config) {
    return new JwtGuard(Auth::createUserProvider($config['provider'] ?? null));
});
Defensive patterns

Strategy: validation

Validate before calling

// PHP — ensure the closure is non-static and rebindable before extend()
$ref = new \ReflectionFunction($callback);
if ($ref->isStatic()) {
    throw new \RuntimeException('Cannot extend() with a static closure; use a non-static closure.');
}
Auth::extend($driver, $callback);

Type guard

function isRebindableClosure(\Closure $c): bool {
    return ! (new \ReflectionFunction($c))->isStatic();
}

Try / catch

try {
    Auth::extend($driver, $callback);
} catch (\RuntimeException $e) {
    Log::error('Auth::extend failed to bind', ['driver' => $driver, 'err' => $e->getMessage()]);
    throw $e;
}

Prevention

When it happens

Trigger: Calling Auth::extend($driver, $callback) with a closure that bindCallbackToSelf refuses or cannot rebind (returns null), e.g. a static closure (Closure::bind returns null for static closures) or a Closure from an incompatible scope that cannot be rebound to the manager instance.

Common situations: Registering a custom auth driver with a static closure (static fn (...) => ...); passing a closure built from ReflectionClosure whose declared scope cannot be reassigned; edge cases after upgrading PHP where Closure::bind semantics changed for static closures.

Related errors


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

Appendix: source

Thrown at src/Illuminate/Auth/AuthManager.php:280

        $this->userResolver = $userResolver;

        return $this;
    }

    /**
     * Register a custom driver creator Closure.
     *
     * @param  string  $driver
     * @param  \Closure  $callback
     *
     * @param-closure-this  $this  $callback
     *
     * @return $this
     */
    public function extend($driver, Closure $callback)
    {
        try {
            $callback = $this->bindCallbackToSelf($callback) ?? throw new RuntimeException('Unable to bind custom driver callback');
        } catch (ReflectionException $e) {
            throw new RuntimeException('Unable to bind custom driver callback', previous: $e);
        }

        $this->customCreators[$driver] = $callback;

        return $this;
    }

    /**
     * Register a custom provider creator Closure.
     *
     * @param  string  $name
     * @param  \Closure  $callback
     * @return $this
     */
    public function provider($name, Closure $callback)
    {

View on GitHub (pinned to e0f6eb3518)