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

  1. Add the missing service ID to the ServiceLocator constructor array, using an alias only when a different public name is intended.
  2. Call $locator->has($id) before get() and treat false as a missing optional dependency.
  3. Pass the full Pimple container when code genuinely needs arbitrary service access.
  4. 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

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


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/9950a2662ed47cba. Report an issue: GitHub.