getgrav/grav · error · Pimple\Exception\UnknownIdentifierException
Identifier "%s" is not defined.
Error message
Identifier "%s" is not defined.
What it means
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.
Source
Thrown at system/src/Pimple/Psr11/ServiceLocator.php:62
* @param PimpleContainer $container The Container instance used to locate services
* @param array $ids Array of service ids that can be located. String keys can be used to define aliases
*/
public function __construct(PimpleContainer $container, array $ids)
{
$this->container = $container;
foreach ($ids as $key => $id) {
$this->aliases[\is_int($key) ? $id : $key] = $id;
}
}
/**
* {@inheritdoc}
*/
public function get(string $id)
{
if (!isset($this->aliases[$id])) {
throw new UnknownIdentifierException($id);
}
return $this->container[$this->aliases[$id]];
}
/**
* {@inheritdoc}
*/
public function has(string $id): bool
{
return isset($this->aliases[$id]) && isset($this->container[$this->aliases[$id]]);
}
}
View on GitHub (pinned to 6040efed04)
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.
Example fix
// before
$locator = new ServiceLocator($container, ['assets']);
$config = $locator->get('config'); // not whitelisted
// after
$locator = new ServiceLocator($container, ['assets', 'config']);
$config = $locator->has('config') ? $locator->get('config') : null; Defensive patterns
Strategy: validation
Validate before calling
if (!$locator->has($id)) {
throw new NotFoundException(sprintf('Service "%s" is not exposed by this locator.', $id));
}
$service = $locator->get($id); Type guard
function locatorExposes(Psr\Container\ContainerInterface $locator, string $id): bool
{
return $locator->has($id);
} Try / catch
try {
return $locator->get($id);
} catch (UnknownIdentifierException $e) {
if ($optional) {
return $default;
}
throw new NotFoundException(sprintf('Add "%s" to the ServiceLocator whitelist.', $id), 0, $e);
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Cannot override frozen service "%s".
- Identifier "%s" does not contain an object definition.
- Identifier "%s" is not defined.
- Service definition is not a Closure or invokable object.
- Callable is not a Closure or invokable object.
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/9950a2662ed47cba.
Report an issue: GitHub.