{"record":{"id":"1e5dd83d0e87b04c","repo":"twigphp/Twig","slug":"callback-for-s-s-is-not-callable-in-the-current-scope","errorCode":null,"errorMessage":"Callback for %s \"%s\" is not callable in the current scope.","messagePattern":"Callback for (.+?) \"(.+?)\" is not callable in the current scope\\.","errorType":"exception","errorClass":"LogicException","httpStatus":null,"severity":"error","filePath":"src/Util/ReflectionCallable.php","lineNumber":48,"sourceCode":"    ) {\n        $callable = $twigCallable->getCallable();\n        if (\\is_string($callable) && false !== $pos = strpos($callable, '::')) {\n            $callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)];\n        }\n\n        if (\\is_array($callable) && method_exists($callable[0], $callable[1])) {\n            $this->reflector = $r = new \\ReflectionMethod($callable[0], $callable[1]);\n            $this->callable = $callable;\n            $this->name = $r->class.'::'.$r->name;\n\n            return;\n        }\n\n        $checkVisibility = $callable instanceof \\Closure;\n        try {\n            $closure = \\Closure::fromCallable($callable);\n        } catch (\\TypeError $e) {\n            throw new \\LogicException(\\sprintf('Callback for %s \"%s\" is not callable in the current scope.', $twigCallable->getType(), $twigCallable->getName()), 0, $e);\n        }\n        $this->reflector = $r = new \\ReflectionFunction($closure);\n\n        if (str_contains($r->name, '{closure')) {\n            $this->callable = $callable;\n            $this->name = 'Closure';\n\n            return;\n        }\n\n        if ($object = $r->getClosureThis()) {\n            $callable = [$object, $r->name];\n            $this->name = get_debug_type($object).'::'.$r->name;\n        } elseif (\\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) {\n            $callable = [$class->name, $r->name];\n            $this->name = $class->name.'::'.$r->name;\n        } elseif (\\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) {\n            $callable = [\\is_array($callable) ? $callable[0] : $class->name, $r->name];","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/twigphp/Twig/blob/a414c3a491defb5a60f2fc88ef79ff37c90010cd/src/Util/ReflectionCallable.php#L30-L66","documentation":"Twig's ReflectionCallable wraps a Twig callable (filter, function, test, macro) into a Closure and ReflectionFunction for argument introspection. Closure::fromCallable() performs a scope-aware callability check at call time; when the callable cannot actually be invoked from the current scope (e.g. a private/protected method), Twig converts the resulting TypeError into this LogicException naming the Twig callable's type and name.","triggerScenarios":"Registering a Twig filter/function/test whose callback is a private or protected method (Closure::fromCallable fails from outside the class scope), e.g. new TwigFilter('f', [$obj, 'privateMethod']); using scope-dependent array-callables like 'self::method'; passing a method name string that is not accessible in the calling scope.","commonSituations":"Custom Twig extension exposing an internal/private helper as a filter; refactoring a method to private/protected after registering it; typos in the method name of an array callable; wrong scope prefix ('self::' instead of the class name).","solutions":["Make the registered callback method public, or wrap it in a closure defined where it is visible: new TwigFilter('f', fn ($v) => $this->privateMethod($v))","Replace array-callables like [$obj, 'method'] or 'self::method' with a closure / first-class callable created in an accessible scope","Verify the class/method exist with no typos in the callable","Read the chained TypeError (previous exception) for the exact reason Closure::fromCallable failed"],"exampleFix":"// before\n$twig->addFilter(new \\Twig\\TwigFilter('shout', [$this, 'shoutInternal'])); // private method\n// after\n$twig->addFilter(new \\Twig\\TwigFilter('shout', fn ($v) => $this->shoutInternal($v))); // closure bound to class scope","handlingStrategy":"validation","validationCode":"try {\n    \\Closure::fromCallable($callback);\n} catch (\\TypeError $e) {\n    throw new \\InvalidArgumentException('Callback not callable in current scope', 0, $e);\n}","typeGuard":"function isScopeCallable(callable|array|string $cb): bool\n{\n    try { \\Closure::fromCallable($cb); return true; }\n    catch (\\TypeError) { return false; }\n}","tryCatchPattern":"try {\n    $twig->addFilter(new \\Twig\\TwigFilter('f', $callback));\n} catch (\\LogicException $e) {\n    // callback not callable in this scope; wrap in a closure with access\n    $twig->addFilter(new \\Twig\\TwigFilter('f', fn ($v) => $obj->method($v)));\n}","preventionTips":["Expose only public methods as Twig filters/functions/tests","Prefer closures or first-class callable syntax over array-callables to private/protected methods","Verify with Closure::fromCallable/is_callable before registering","Watch for typos in string/array method callables during refactors","Smoke-test each registered filter/function by rendering a template"],"tags":["php","twig","callable","reflection","visibility"],"backgroundTag":"invalid-argument-value","analyzedSha":"a414c3a491defb5a60f2fc88ef79ff37c90010cd","analyzedAt":"2026-09-13T15:10:46.849Z","contentChangedAt":"2026-09-13T15:10:46.849Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}