getgrav/grav · error · Pimple\Exception\InvalidServiceIdentifierException
Identifier "%s" does not contain an object definition.
Error message
Identifier "%s" does not contain an object definition.
What it means
Container::offsetGet() accepts a mixed offset because Container implements ArrayAccess, but it immediately requires the service ID to be a string. A non-string offset throws InvalidServiceIdentifierException; the exception text about an object definition is generic and can be misleading because the actual check at Container.php:125-127 is only is_string($id).
Source
Thrown at system/src/Pimple/Container.php:126
}
$this->values[$id] = $value;
$this->keys[$id] = true;
}
/**
* Gets a parameter or an object.
*
* @param string $id The unique identifier for the parameter or object
*
* @return mixed The value of the parameter or an object
*
* @throws UnknownIdentifierException If the identifier is not defined
*/
public function offsetGet(mixed $id): mixed
{
if (!is_string($id)) {
throw new InvalidServiceIdentifierException($id);
}
if (!isset($this->keys[$id])) {
throw new UnknownIdentifierException($id);
}
if (
isset($this->raw[$id])
|| !is_object($this->values[$id])
|| isset($this->protected[$this->values[$id]])
|| !method_exists($this->values[$id], '__invoke')
) {
return $this->values[$id];
}
if (isset($this->factories[$this->values[$id]])) {
return $this->values[$id]($this);
}View on GitHub (pinned to 6040efed04)
Solutions
- Check or cast the variable before access: use $container[(string) $id] only after confirming it is a non-empty string.
- Fail early when the configured service name is missing instead of passing null to the container.
- Store service names in configuration as quoted strings.
- Use a known literal service name when possible.
Example fix
// before
$id = $settings['service'] ?? null;
$service = $container[$id]; // Identifier "" does not contain...
// after
$id = $settings['service'] ?? null;
if (!is_string($id) || $id === '') {
throw new InvalidArgumentException('settings.service must be a service ID string');
}
$service = $container[$id]; Defensive patterns
Strategy: type-guard
Validate before calling
if (!is_string($id)) {
throw new InvalidArgumentException(sprintf('Container ID must be a string, got %s.', get_debug_type($id)));
}
$service = $container[$id]; Type guard
function isContainerServiceId(mixed $id): bool
{
return is_string($id);
} Try / catch
try {
$service = $container[$id];
} catch (InvalidServiceIdentifierException $e) {
throw new InvalidArgumentException(sprintf('Invalid dynamic container ID type: %s', get_debug_type($id)), 0, $e);
} Prevention
- Normalize configuration-derived service names to non-empty strings.
- Fail on missing IDs instead of passing null to the container.
- Quote numeric or symbolic IDs in YAML.
- Enable static analysis on code that uses dynamic ArrayAccess offsets.
When it happens
Trigger: Calling $container[$id] where $id is an integer, null, boolean, array, or object. Common shapes are $container[0], $container[$config['service'] ?? null], or an ID taken from a YAML map whose PHP key was converted to an integer.
Common situations: Dynamic service names loaded from configuration or request arrays, numeric YAML keys, a missing environment variable becoming null, or code written for a plain PHP array being reused against the DI container.
Related errors
- Service definition is not a Closure or invokable object.
- Callable is not a Closure or invokable object.
- Cannot override frozen service "%s".
- Identifier "%s" is not defined.
- Extension service definition is not a Closure or invokable o
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/a68ba727878031af.
Report an issue: GitHub.