laravel/framework · error · InvalidArgumentException

Callback must be a callable, callback array, or a…

Error message

Callback must be a callable, callback array, or a 'Class@method' string.

What it means

Thrown by Gate::define() when the supplied callback is neither a PHP callable, an array whose first element is a class string, nor a 'Class@method' string. The Gate normalizes array callbacks to 'Class@method', stores string callbacks for lazy resolution, and callable values directly — anything else is rejected as a misconfiguration of an ability.

Solutions

  1. Pass a closure: Gate::define('update-post', fn (User $user, Post $post) => $user->id === $post->user_id);
  2. Pass a 'Class@method' string: Gate::define('update-post', 'PostPolicy@update');
  3. Pass a callable array with a class string first element: Gate::define('update-post', [PostPolicy::class, 'update']);
  4. Inspect the $callback variable at the call site to confirm its runtime type before calling define().

Example fix

// before
Gate::define('update-post', $policyInstance); // an object, not invokable

// after
Gate::define('update-post', [PostPolicy::class, 'update']);
// or
Gate::define('update-post', PostPolicy::class . '@update');
Defensive patterns

Strategy: validation

Validate before calling

// PHP — validate before Gate::define
function isValidGateCallback($callback): bool {
    if (is_callable($callback)) return true;
    if (is_string($callback)) return true;
    if (is_array($callback) && isset($callback[0]) && is_string($callback[0])) return true;
    return false;
}
if (! isValidGateCallback($cb)) {
    throw new \InvalidArgumentException('Invalid gate callback supplied for ability.');
}
Gate::define($ability, $cb);

Type guard

function isValidGateCallback($callback): bool {
    return is_callable($callback)
        || is_string($callback)
        || (is_array($callback) && isset($callback[0]) && is_string($callback[0]));
}

Try / catch

try {
    Gate::define($ability, $callback);
} catch (\InvalidArgumentException $e) {
    // log the ability + callback type for diagnosis
    report(['ability' => $ability, 'type' => get_debug_type($callback), 'err' => $e->getMessage()]);
    throw $e;
}

Prevention

When it happens

Trigger: Calling Gate::define($ability, $callback) (or Authorizable::define / Auth::define via the gate facade) with a value such as null, an integer, an object that is not __invoke-able, or an array lacking a string class key.

Common situations: Passing a non-static method array ['SomeClass', 'method'] where the class entry was overwritten; passing a service-container-resolved instance by mistake instead of a class string; typos in 'Class@method' that produce a non-string after concatenation; passing null from an optional config value.

Related errors


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

Appendix: source

Thrown at src/Illuminate/Auth/Access/Gate.php:212

     *
     * @throws \InvalidArgumentException
     */
    public function define($ability, $callback)
    {
        $ability = enum_value($ability);

        if (is_array($callback) && isset($callback[0]) && is_string($callback[0])) {
            $callback = $callback[0].'@'.$callback[1];
        }

        if (is_callable($callback)) {
            $this->abilities[$ability] = $callback;
        } elseif (is_string($callback)) {
            $this->stringCallbacks[$ability] = $callback;

            $this->abilities[$ability] = $this->buildAbilityCallback($ability, $callback);
        } else {
            throw new InvalidArgumentException("Callback must be a callable, callback array, or a 'Class@method' string.");
        }

        return $this;
    }

    /**
     * Define abilities for a resource.
     *
     * @param  string  $name
     * @param  string  $class
     * @param  array|null  $abilities
     * @return $this
     */
    public function resource($name, $class, ?array $abilities = null)
    {
        $abilities = $abilities ?: [
            'viewAny' => 'viewAny',
            'view' => 'view',

View on GitHub (pinned to e0f6eb3518)