{"record":{"id":"9950a2662ed47cba","repo":"getgrav/grav","slug":"identifier-s-is-not-defined-9950a2","errorCode":null,"errorMessage":"Identifier \"%s\" is not defined.","messagePattern":"Identifier \"(.+?)\" is not defined\\.","errorType":"exception","errorClass":"Pimple\\Exception\\UnknownIdentifierException","httpStatus":null,"severity":"error","filePath":"system/src/Pimple/Psr11/ServiceLocator.php","lineNumber":62,"sourceCode":"     * @param PimpleContainer $container The Container instance used to locate services\n     * @param array           $ids       Array of service ids that can be located. String keys can be used to define aliases\n     */\n    public function __construct(PimpleContainer $container, array $ids)\n    {\n        $this->container = $container;\n\n        foreach ($ids as $key => $id) {\n            $this->aliases[\\is_int($key) ? $id : $key] = $id;\n        }\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function get(string $id)\n    {\n        if (!isset($this->aliases[$id])) {\n            throw new UnknownIdentifierException($id);\n        }\n\n        return $this->container[$this->aliases[$id]];\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function has(string $id): bool\n    {\n        return isset($this->aliases[$id]) && isset($this->container[$this->aliases[$id]]);\n    }\n}\n","sourceCodeStart":44,"sourceCodeEnd":76,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Pimple/Psr11/ServiceLocator.php#L44-L76","documentation":"Pimple's PSR-11 ServiceLocator is deliberately narrower than the container: its constructor receives a whitelist of IDs, and string keys become aliases. ServiceLocator::get() throws UnknownIdentifierException when the requested ID is not in that alias map, even when the underlying Pimple container defines it.","triggerScenarios":"Construct new ServiceLocator($container, ['assets']) and then call $locator->get('config'); request an alias typo such as 'cfg' when 'config' => 'config' was supplied; or pass a locator where plugin code expects the full Grav container.","commonSituations":"PSR-11 integration code assumes service-locator methods expose every container entry, plugin APIs accept a locator with a limited interface, or an alias is renamed without updating consumers.","solutions":["Add the missing service ID to the ServiceLocator constructor array, using an alias only when a different public name is intended.","Call $locator->has($id) before get() and treat false as a missing optional dependency.","Pass the full Pimple container when code genuinely needs arbitrary service access.","Correct alias and consumer naming so both use the exact same string."],"exampleFix":"// before\n$locator = new ServiceLocator($container, ['assets']);\n$config = $locator->get('config'); // not whitelisted\n\n// after\n$locator = new ServiceLocator($container, ['assets', 'config']);\n$config = $locator->has('config') ? $locator->get('config') : null;","handlingStrategy":"validation","validationCode":"if (!$locator->has($id)) {\n    throw new NotFoundException(sprintf('Service \"%s\" is not exposed by this locator.', $id));\n}\n$service = $locator->get($id);","typeGuard":"function locatorExposes(Psr\\Container\\ContainerInterface $locator, string $id): bool\n{\n    return $locator->has($id);\n}","tryCatchPattern":"try {\n    return $locator->get($id);\n} catch (UnknownIdentifierException $e) {\n    if ($optional) {\n        return $default;\n    }\n    throw new NotFoundException(sprintf('Add \"%s\" to the ServiceLocator whitelist.', $id), 0, $e);\n}","preventionTips":["Construct ServiceLocator with every ID consumers may legally request.","Prefer has() for optional dependencies.","Keep aliases under version control with the consumer API.","Do not pass a narrow locator to code that expects the full Grav container."],"tags":["pimple","psr-11","service-locator","dependency-injection","php"],"backgroundTag":"unknown-service-identifier","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}