Search by

projek-xyz / callable

feryardiant

Auto-wire and invoke any callable through PSR-11

Package info

github.com/projek-xyz/callable

pkg:composer/projek-xyz/callable

Fund package maintenance!

feryardiant

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-10-05 07:58 UTC

This package is auto-updated.

Last update: 2026-10-05 08:00:31 UTC


README

Version License Actions Status

Auto-wire and invoke any callable through PSR-11

Resolve any callable through a PSR-11 container and invoke it with dependency injection — same semantics as native call_user_func_array(), except missing parameters auto-wire from the container instead of raising ArgumentCountError.

Installation

composer require projek-xyz/callable

Resolving and invoking callables

Works with any PSR-11 container; the examples below use projek-xyz/container.

Invoking with Handler

Handler::handle() accepts any callable shape — a function name, Class::method string, [Class::class, 'method'], [$object, 'method'], a closure, or an __invoke() object — and binds arguments with native call_user_func_array() semantics: integer keys are positional (in order), string keys are named, and a trailing variadic absorbs the rest. Whatever you do not pass is auto-wired from the container (then its default value) instead of raising ArgumentCountError:

use Projek\Callable\Handler;
use Projek\Container;

class Clock
{
    public function now(): int
    {
        return time();
    }
}

class Greeter
{
    public function __construct(private Clock $clock) {}

    public function greet(string $who): string
    {
        return sprintf('Hello %s (at %d)', $who, $this->clock->now());
    }
}

$container = new Container([Clock::class => Clock::class]);
$handler = new Handler($container);

// $who is bound by name; the Greeter receiver is built automatically and
// its Clock dependency comes from the container.
echo $handler->handle([Greeter::class, 'greet'], ['who' => 'world']);
// e.g. "Hello world (at 1791177991)"

// Positional arguments behave like any native call...
$handler->handle('strtoupper', ['hello']); // "HELLO"

// ...and so do Class::method strings, closures, and __invoke() objects.
$handler->handle(Greeter::class.'::greet', ['who' => 'PHP']);

Resolving without invoking

Resolver is the layer Handler is built on; reach for it directly when you only need the callable:

use Projek\Callable\Resolver;

$callable = (new Resolver($container))->resolveCallable(Greeter::class.'::greet');
// [new Greeter(new Clock), 'greet']

Error handling

Failures are named the way PHP itself would name them:

  • DependencyException — a parameter could not be provided or auto-wired. The message mirrors native TypeError phrasing with the 1-based argument position:

    $handler->handle(fn (string $dsn) => $dsn);
    // Projek\Callable\DependencyException:
    // {closure}(): Argument #1 ($dsn) is not resolvable
  • UnresolvableException — the callable itself cannot be resolved:

    Instance of missing_function is not resolvable
    Method Greeter::nope() does not exist
    
  • TypeError — argument type mismatches surface PHP's own TypeError untouched: invocation runs natively under strict_types.

License

This library is open-sourced software licensed under MIT license.