getgrav/grav · error · Pimple\Exception\ExpectedInvokableException

Service definition is not a Closure or invokable object.

Error message

Service definition is not a Closure or invokable object.

What it means

Container::factory() marks a service definition so every retrieval invokes it again instead of caching and freezing the result. Pimple tracks definitions in SplObjectStorage, so the definition must be a Closure or another object with __invoke(); a plain object without __invoke() throws ExpectedInvokableException.

Source

Thrown at system/src/Pimple/Container.php:214

            }

            unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]);
        }
    }

    /**
     * Marks a callable as being a factory service.
     *
     * @param callable $callable A service definition to be used as a factory
     *
     * @return callable The passed callable
     *
     * @throws ExpectedInvokableException Service definition has to be a closure or an invokable object
     */
    public function factory(object $callable): object
    {
        if (!method_exists($callable, '__invoke')) {
            throw new ExpectedInvokableException('Service definition is not a Closure or invokable object.');
        }

        $this->factories->attach($callable);

        return $callable;
    }

    /**
     * Protects a callable from being interpreted as a service.
     *
     * This is useful when you want to store a callable as a parameter.
     *
     * @param callable $callable A callable to protect from being evaluated
     *
     * @return callable The passed callable
     *
     * @throws ExpectedInvokableException Service definition has to be a closure or an invokable object
     */

View on GitHub (pinned to 6040efed04)

Solutions

  1. Wrap construction in a closure: $container['service'] = $container->factory(fn (Container $c) => new SomeFactory(...));
  2. Make the passed object invokable by implementing __invoke(Container $container).
  3. Do not call factory() on an already constructed service; assign it as a normal value if no invocation is wanted.

Example fix

// before
$container['report'] = $container->factory(new ReportBuilder()); // ReportBuilder has no __invoke

// after
$container['report'] = $container->factory(
    fn (Container $c) => new ReportBuilder($c['logger'])
);
Defensive patterns

Strategy: type-guard

Validate before calling

if (!is_object($definition) || !method_exists($definition, '__invoke')) {
    throw new InvalidArgumentException('A factory definition must be a Closure or invokable object.');
}
$definition = $container->factory($definition);

Type guard

function isInvokableDefinition(mixed $definition): bool
{
    return is_object($definition) && method_exists($definition, '__invoke');
}

Try / catch

try {
    $container->factory($definition);
} catch (ExpectedInvokableException $e) {
    throw new InvalidArgumentException('Wrap object construction in a closure before marking it as a factory.', 0, $e);
}

Prevention

When it happens

Trigger: Calling $container->factory(new SomeFactory()) where SomeFactory has no __invoke() method, or passing an object that is merely an instantiated service rather than a factory definition. String and array callables fail the object parameter type before reaching this check.

Common situations: Confusing the factory definition with the product it creates, migrating callable code from another container that accepts first-class callable strings, or decorating a class without adding an invoker.

Related errors


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