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

Merged
merged 20 commits into from
Dec 14, 2024
Merged
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 46 additions & 17 deletions src/Illuminate/Routing/ResolvesRouteDependencies.php
Original file line number Diff line number Diff line change
@@ -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;
@@ -51,18 +53,10 @@ public function resolveMethodDependencies(array $parameters, ReflectionFunctionA

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

// 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) || ! $this->alreadyInResolvedInterfaces($className, $resolvedInterfaces))) {
$instance = $this->transformDependency($parameter, $className);

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

if ($instance !== $skippableValue) {
@@ -83,22 +77,38 @@ 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, $className)
protected function transformDependency(ReflectionParameter $parameter, $parameters, $className, object $skippableValue, $resolvedInterfaces)
{
if ($attribute = Util::getContextualAttributeFromDependency($parameter)) {
return $this->container->resolveFromAttribute($attribute);
}

$isEnum = (new ReflectionClass($className))->isEnum();
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))) {
$isEnum = (new ReflectionClass($className))->isEnum();

return $parameter->isDefaultValueAvailable()
? ($isEnum ? $parameter->getDefaultValue() : null)
: $this->container->make($className);
return $parameter->isDefaultValueAvailable()
? ($isEnum ? $parameter->getDefaultValue() : null)
: $this->container->make($className);
}

return $skippableValue;
}

/**
@@ -118,6 +128,25 @@ 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.
*