Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[11.x] fix: allows injection using multiple interfaces with the same concrete implementation #53275

Open
wants to merge 10 commits into
base: 11.x
Choose a base branch
from
52 changes: 46 additions & 6 deletions src/Illuminate/Routing/ResolvesRouteDependencies.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
namespace Illuminate\Routing;

use Illuminate\Container\Util;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Arr;
use Illuminate\Support\Reflector;
use ReflectionClass;
use ReflectionException;
use ReflectionFunctionAbstract;
use ReflectionMethod;
use ReflectionParameter;
Expand Down Expand Up @@ -47,15 +49,22 @@ public function resolveMethodDependencies(array $parameters, ReflectionFunctionA

$skippableValue = new stdClass;

$resolvedInterfaces = [];

foreach ($reflector->getParameters() as $key => $parameter) {
$instance = $this->transformDependency($parameter, $parameters, $skippableValue);
$className = Reflector::getParameterClassName($parameter);
$instance = $this->transformDependency($parameter, $parameters, $className, $skippableValue, $resolvedInterfaces);

if ($instance !== $skippableValue && ! $this->alreadyInResolvedInterfaces($className, $resolvedInterfaces)) {
$resolvedInterfaces[] = $className;
}

if ($instance !== $skippableValue) {
$instanceCount++;

$this->spliceIntoParameters($parameters, $key, $instance);
} elseif (! isset($values[$key - $instanceCount]) &&
$parameter->isDefaultValueAvailable()) {
$parameter->isDefaultValueAvailable()) {
$this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue());
}

Expand All @@ -68,23 +77,30 @@ public function resolveMethodDependencies(array $parameters, ReflectionFunctionA
/**
* Attempt to transform the given parameter into a class instance.
*
* @param \ReflectionParameter $parameter
* @param ReflectionParameter $parameter
* @param array $parameters
* @param $className
* @param object $skippableValue
* @param $resolvedInterfaces
* @return mixed
*
* @throws BindingResolutionException
* @throws ReflectionException
*/
protected function transformDependency(ReflectionParameter $parameter, $parameters, $skippableValue)
protected function transformDependency(ReflectionParameter $parameter, $parameters, $className, object $skippableValue, $resolvedInterfaces)
{
if ($attribute = Util::getContextualAttributeFromDependency($parameter)) {
return $this->container->resolveFromAttribute($attribute);
}

$className = Reflector::getParameterClassName($parameter);
if ($this->shouldResolveInterface($className, $parameters, $resolvedInterfaces)) {
return $this->container->make($className);
}

// If the parameter has a type-hinted class, we will check to see if it is already in
// the list of parameters. If it is we will just skip it as it is probably a model
// binding and we do not want to mess with those; otherwise, we resolve it here.
if ($className && ! $this->alreadyInParameters($className, $parameters)) {
if ($className && (! $this->alreadyInParameters($className, $parameters))) {
$isEnum = (new ReflectionClass($className))->isEnum();

return $parameter->isDefaultValueAvailable()
Expand All @@ -107,6 +123,30 @@ protected function alreadyInParameters($class, array $parameters)
return ! is_null(Arr::first($parameters, fn ($value) => $value instanceof $class));
}

protected function alreadyInResolvedInterfaces($class, array $resolvedInterfaces)
{
return in_array($class, $resolvedInterfaces);
}

/**
* Determines if an interface should be resolved, even if
* a concrete class that implements it already exists
* in the list of parameters.
*
* @param $className
* @param array $parameters
* @param $resolvedInterfaces
* @return bool
*/
protected function shouldResolveInterface($className, array $parameters, $resolvedInterfaces): bool
{
return $className &&
$this->alreadyInParameters($className, $parameters) &&
interface_exists($className) &&
! $this->alreadyInResolvedInterfaces($className, $resolvedInterfaces) &&
(new ReflectionClass($className))->isInterface();
}

/**
* Splice the given value into the parameter list.
*
Expand Down