Skip to content

Interface-to-implementation binding ignores existing definition for concrete class #278

Description

@dereuromark

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions