{"record":{"id":"8631e3accc3265bf","repo":"getgrav/grav","slug":"callable-is-not-a-closure-or-invokable-object","errorCode":null,"errorMessage":"Callable is not a Closure or invokable object.","messagePattern":"Callable is not a Closure or invokable object\\.","errorType":"exception","errorClass":"Pimple\\Exception\\ExpectedInvokableException","httpStatus":null,"severity":"error","filePath":"system/src/Pimple/Container.php","lineNumber":236,"sourceCode":"\n        return $callable;\n    }\n\n    /**\n     * Protects a callable from being interpreted as a service.\n     *\n     * This is useful when you want to store a callable as a parameter.\n     *\n     * @param callable $callable A callable to protect from being evaluated\n     *\n     * @return callable The passed callable\n     *\n     * @throws ExpectedInvokableException Service definition has to be a closure or an invokable object\n     */\n    public function protect(object $callable): object\n    {\n        if (!method_exists($callable, '__invoke')) {\n            throw new ExpectedInvokableException('Callable is not a Closure or invokable object.');\n        }\n\n        $this->protected->attach($callable);\n\n        return $callable;\n    }\n\n    /**\n     * Gets a parameter or the closure defining an object.\n     *\n     * @param string $id The unique identifier for the parameter or object\n     *\n     * @return mixed The value of the parameter or the closure defining an object\n     *\n     * @throws UnknownIdentifierException If the identifier is not defined\n     */\n    public function raw(string $id): mixed\n    {","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Pimple/Container.php#L218-L254","documentation":"Container::protect() prevents a callable definition from being executed when it is read, allowing it to be stored as a parameter. Because the protected value is tracked as an object, it must be a Closure or invokable object; a non-invokable object throws ExpectedInvokableException.","triggerScenarios":"Calling $container->protect(new Formatter()) when Formatter has no __invoke(), or trying to protect a native function name or array callable, which instead fails the object parameter type before the method body.","commonSituations":"Storing a callback in configuration, preserving a closure for later dispatch, or passing a normal value object to protect() because its name suggests general value protection.","solutions":["Use $container['callback'] = $container->protect(fn () => $callback()); for a callable that must not be invoked on access.","Store a non-callable value directly: $container['formatter'] = $formatter.","Add __invoke() to the object if it really is the callable being protected."],"exampleFix":"// before\n$container['normalizer'] = $container->protect(new Normalizer()); // no __invoke\n\n// after\n$container['normalizer'] = new Normalizer(); // plain parameter\n$container['normalizer_factory'] = $container->protect(fn () => new Normalizer()); // protected callable","handlingStrategy":"type-guard","validationCode":"if (!is_object($callback) || !method_exists($callback, '__invoke')) {\n    throw new InvalidArgumentException('Only closures and invokable objects can be protected.');\n}\n$container['callback'] = $container->protect($callback);","typeGuard":"function isProtectableCallable(mixed $value): bool\n{\n    return is_object($value) && method_exists($value, '__invoke');\n}","tryCatchPattern":"try {\n    $container->protect($callable);\n} catch (ExpectedInvokableException $e) {\n    throw new InvalidArgumentException('Store non-callable values directly; protect() expects a closure or invokable object.', 0, $e);\n}","preventionTips":["Call protect() only when reading the value would otherwise invoke it.","Store ordinary parameters without protect().","Use closures for callbacks stored in the container.","Add __invoke() when an object itself must be callable."],"tags":["pimple","dependency-injection","php","protect","invokable","type-error"],"backgroundTag":"non-invokable-callable","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}