laravel/framework · error · TypeError
{self::class}::bind(): Argument #2 ($concrete) must be of ty
Error message
{self::class}::bind(): Argument #2 ($concrete) must be of type Closure|string|null What it means
Thrown as a TypeError by Container::bind when the $concrete argument is neither a Closure nor a string nor null (e.g. an integer, array, object instance). The bind signature accepts mixed only because the first argument can be a Closure; once it is a string the concrete must be Closure|string|null.
Source
Thrown at src/Illuminate/Container/Container.php:381
$abstract, $concrete, $shared
);
}
$this->dropStaleInstances($abstract);
// If no concrete type was given, we will simply set the concrete type to the
// abstract type. After that, the concrete type to be registered as shared
// without being forced to state their classes in both of the parameters.
if (is_null($concrete)) {
$concrete = $abstract;
}
// If the factory is not a Closure, it means it is just a class name which is
// bound into this container to the abstract type and we will just wrap it
// up inside its own Closure to give us more convenience when extending.
if (! $concrete instanceof Closure) {
if (! is_string($concrete)) {
throw new TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');
}
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = ['concrete' => $concrete, 'shared' => $shared];
// If the abstract type was already resolved in this container we'll fire the
// rebound listener so that any objects which have already gotten resolved
// can have their copy of the object updated via the listener callbacks.
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
/**
* Get the Closure to be used when building a type.
*View on GitHub (pinned to bd6b5437e6)
Solutions
- Pass a class name string: $container->bind(PaymentContract::class, StripeGateway::class).
- Pass a Closure: $container->bind('key', fn ($app) => new MyService($app['config'])).
- To register a pre-built object use instance(): $container->instance('key', $object).
- Verify the variable you are passing is a string class name or Closure before calling bind.
Example fix
// before
$container->bind('cache.store', new FileStore(...));
// after
$container->instance('cache.store', new FileStore(...));
// or
$container->bind('cache.store', fn () => new FileStore(...)); Defensive patterns
Strategy: type-guard
Validate before calling
if (! ($concrete instanceof \Closure || is_string($concrete) || is_null($concrete))) {
throw new \TypeError('concrete must be Closure|string|null');
}
$container->bind($abstract, $concrete); Type guard
function isBindConcrete(mixed $c): bool
{
return $c instanceof \Closure || is_string($c) || $c === null;
} Try / catch
try {
$container->bind($key, $concrete);
} catch (\TypeError $e) {
if (str_contains($e->getMessage(), 'must be of type Closure|string|null')) {
$container->instance($key, $concrete); // fallback for objects
} else { throw $e; }
} Prevention
- Use instance() to register pre-built objects.
- Add static analysis (phpstan level 6+) to catch wrong types at lint time.
- Review service-provider bind calls in code review.
When it happens
Trigger: Calling $container->bind('key', 42), $container->bind('key', ['a','b']), or $container->bind('key', $someObject) (object instance rather than class name string).
Common situations: Confusing bind() with instance() (use instance() to register an already-built object); passing a config array as concrete; dynamic value resolved to a non-string; copy-paste mistake between singleton($abstract, $concreteClass) and a real instance.
Related errors
- Target [$concrete] is not instantiable while building [$prev
- Target [$concrete] is not instantiable.
- The environment property must be set and cannot be empty.
- Method not provided.
- Unable to resolve dependency [{$parameter}] in class {$param
AI-assisted analysis of laravel/framework@bd6b5437e6 (2026-08-06).
Data as JSON: /data/errors/707a75d527913c2b.json.
Report an issue: GitHub.