Description
When binding an interface to a concrete class that has its own registered definition with arguments, the container ignores the concrete class's definition and instantiates it with zero arguments instead.
Example
$container->add(CakeOrmAcmeRepository::class)
->addArgument(AcmeFactory::class)
->addArgument(AcmeTable::class)
->addArgument(AcmeNumbersTable::class);
$container->add(AcmeRepositoryInterface::class, CakeOrmAcmeRepository::class);
// Works - uses the definition with 3 arguments
$container->get(CakeOrmAcmeRepository::class);
// Fails - "Too few arguments to function ...__construct(), 0 passed and exactly 3 expected"
$container->get(AcmeRepositoryInterface::class);
Root cause
In Definition::resolveNew(), the class_exists() check is evaluated before the container lookup:
// This is checked first - instantiates via reflection with only THIS definition's (empty) arguments
if (is_string($concrete) && class_exists($concrete)) {
$concrete = $this->resolveClass($concrete);
}
// This would correctly delegate to the existing definition, but is never reached
if (is_string($concrete) && $container instanceof ContainerInterface && $container->has($concrete)) {
$this->recursiveCheck[] = $concrete;
$concrete = $container->get($concrete);
}
When $concrete is a string like CakeOrmAcmeRepository::class, class_exists() returns true, so resolveClass() is called. This uses only the current (interface) definition's arguments (which is empty), completely bypassing the existing container definition for the concrete class.
The second block that calls $container->get($concrete) would correctly resolve through the registered definition (with all its arguments), but it's never reached.
Expected behavior
$container->get(AcmeRepositoryInterface::class) should resolve CakeOrmAcmeRepository using its registered definition and configured arguments.
Suggested fix
Check for an existing container definition before falling back to class_exists reflection:
if (is_string($concrete) && $container instanceof ContainerInterface && $container->has($concrete)) {
$this->recursiveCheck[] = $concrete;
$concrete = $container->get($concrete);
} elseif (is_string($concrete) && class_exists($concrete)) {
$concrete = $this->resolveClass($concrete);
}
This way, if the container already has a definition for the concrete class (with arguments, method calls, etc.), that definition is used. Otherwise it falls back to reflection-based instantiation as before.
Version
league/container 5.x
Description
When binding an interface to a concrete class that has its own registered definition with arguments, the container ignores the concrete class's definition and instantiates it with zero arguments instead.
Example
Root cause
In
Definition::resolveNew(), theclass_exists()check is evaluated before the container lookup:When
$concreteis a string likeCakeOrmAcmeRepository::class,class_exists()returns true, soresolveClass()is called. This uses only the current (interface) definition's arguments (which is empty), completely bypassing the existing container definition for the concrete class.The second block that calls
$container->get($concrete)would correctly resolve through the registered definition (with all its arguments), but it's never reached.Expected behavior
$container->get(AcmeRepositoryInterface::class)should resolveCakeOrmAcmeRepositoryusing its registered definition and configured arguments.Suggested fix
Check for an existing container definition before falling back to
class_existsreflection:This way, if the container already has a definition for the concrete class (with arguments, method calls, etc.), that definition is used. Otherwise it falls back to reflection-based instantiation as before.
Version
league/container 5.x