-
-
Notifications
You must be signed in to change notification settings - Fork 372
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Compatibility with BetterReflection 6.x on ClassFromEnumFactory (#3150)
* Compatibility with BetterReflection 6.x on ClassFromEnumFactory * Fix * temporary use symplify/autowire-array-parameter:dev-main * remove symplify-array-parameter * lets copy * [ci-review] Rector Rectify * copy DefinitionFinder * copy ParamTypeDocBlockResolver * Copy others * [ci-review] Rector Rectify * exception class * Fix phpstan Co-authored-by: GitHub Action <[email protected]>
- Loading branch information
1 parent
e14aac7
commit efd09bb
Showing
10 changed files
with
518 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
192 changes: 192 additions & 0 deletions
192
src/DependencyInjection/CompilerPass/AutowireArrayParameterCompilerPass.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Rector\Core\DependencyInjection\CompilerPass; | ||
|
||
use Nette\Utils\Strings; | ||
use Rector\Core\DependencyInjection\DefinitionFinder; | ||
use Rector\Core\DependencyInjection\DocBlock\ParamTypeDocBlockResolver; | ||
use Rector\Core\DependencyInjection\Skipper\ParameterSkipper; | ||
use Rector\Core\DependencyInjection\TypeResolver\ParameterTypeResolver; | ||
use ReflectionClass; | ||
use ReflectionMethod; | ||
use Symfony\Component\Config\Loader\LoaderInterface; | ||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\DependencyInjection\Definition; | ||
use Symfony\Component\DependencyInjection\Reference; | ||
|
||
/** | ||
* @inspiration https://github.com/nette/di/pull/178 | ||
*/ | ||
final class AutowireArrayParameterCompilerPass implements CompilerPassInterface | ||
{ | ||
/** | ||
* These namespaces are already configured by their bundles/extensions. | ||
* | ||
* @var string[] | ||
*/ | ||
private const EXCLUDED_NAMESPACES = ['Doctrine', 'JMS', 'Symfony', 'Sensio', 'Knp', 'EasyCorp', 'Sonata', 'Twig']; | ||
|
||
/** | ||
* Classes that create circular dependencies | ||
* | ||
* @var class-string<LoaderInterface>[]|string[] | ||
*/ | ||
private const EXCLUDED_FATAL_CLASSES = [ | ||
'Symfony\Component\Form\FormExtensionInterface', | ||
'Symfony\Component\Asset\PackageInterface', | ||
'Symfony\Component\Config\Loader\LoaderInterface', | ||
'Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface', | ||
'EasyCorp\Bundle\EasyAdminBundle\Form\Type\Configurator\TypeConfiguratorInterface', | ||
'Sonata\CoreBundle\Model\Adapter\AdapterInterface', | ||
'Sonata\Doctrine\Adapter\AdapterChain', | ||
'Sonata\Twig\Extension\TemplateExtension', | ||
'Symfony\Component\HttpKernel\KernelInterface', | ||
]; | ||
|
||
private readonly DefinitionFinder $definitionFinder; | ||
|
||
private readonly ParameterTypeResolver $parameterTypeResolver; | ||
|
||
private readonly ParameterSkipper $parameterSkipper; | ||
|
||
/** | ||
* @param string[] $excludedFatalClasses | ||
*/ | ||
public function __construct(array $excludedFatalClasses = []) | ||
{ | ||
$this->definitionFinder = new DefinitionFinder(); | ||
|
||
$paramTypeDocBlockResolver = new ParamTypeDocBlockResolver(); | ||
$this->parameterTypeResolver = new ParameterTypeResolver($paramTypeDocBlockResolver); | ||
|
||
$this->parameterSkipper = new ParameterSkipper($this->parameterTypeResolver, $excludedFatalClasses); | ||
} | ||
|
||
public function process(ContainerBuilder $containerBuilder): void | ||
{ | ||
$definitions = $containerBuilder->getDefinitions(); | ||
|
||
foreach ($definitions as $definition) { | ||
if ($this->shouldSkipDefinition($containerBuilder, $definition)) { | ||
continue; | ||
} | ||
|
||
/** @var ReflectionClass<object> $reflectionClass */ | ||
$reflectionClass = $containerBuilder->getReflectionClass($definition->getClass()); | ||
|
||
/** @var ReflectionMethod $constructorReflectionMethod */ | ||
$constructorReflectionMethod = $reflectionClass->getConstructor(); | ||
|
||
$this->processParameters($containerBuilder, $constructorReflectionMethod, $definition); | ||
} | ||
} | ||
|
||
private function shouldSkipDefinition(ContainerBuilder $containerBuilder, Definition $definition): bool | ||
{ | ||
if ($definition->isAbstract()) { | ||
return true; | ||
} | ||
|
||
if ($definition->getClass() === null) { | ||
return true; | ||
} | ||
|
||
// here class name can be "%parameter.class%" | ||
$parameterBag = $containerBuilder->getParameterBag(); | ||
$resolvedClassName = $parameterBag->resolveValue($definition->getClass()); | ||
|
||
// skip 3rd party classes, they're autowired by own config | ||
$excludedNamespacePattern = '#^(' . implode('|', self::EXCLUDED_NAMESPACES) . ')\\\\#'; | ||
if (Strings::match($resolvedClassName, $excludedNamespacePattern)) { | ||
return true; | ||
} | ||
|
||
if (in_array($resolvedClassName, self::EXCLUDED_FATAL_CLASSES, true)) { | ||
return true; | ||
} | ||
|
||
if ($definition->getFactory()) { | ||
return true; | ||
} | ||
|
||
if (! class_exists($definition->getClass())) { | ||
return true; | ||
} | ||
|
||
$reflectionClass = $containerBuilder->getReflectionClass($definition->getClass()); | ||
if (! $reflectionClass instanceof ReflectionClass) { | ||
return true; | ||
} | ||
|
||
if (! $reflectionClass->hasMethod('__construct')) { | ||
return true; | ||
} | ||
|
||
/** @var ReflectionMethod $constructorReflectionMethod */ | ||
$constructorReflectionMethod = $reflectionClass->getConstructor(); | ||
return ! $constructorReflectionMethod->getParameters(); | ||
} | ||
|
||
private function processParameters( | ||
ContainerBuilder $containerBuilder, | ||
ReflectionMethod $reflectionMethod, | ||
Definition $definition | ||
): void { | ||
$reflectionParameters = $reflectionMethod->getParameters(); | ||
foreach ($reflectionParameters as $reflectionParameter) { | ||
if ($this->parameterSkipper->shouldSkipParameter($reflectionMethod, $definition, $reflectionParameter)) { | ||
continue; | ||
} | ||
|
||
$parameterType = $this->parameterTypeResolver->resolveParameterType( | ||
$reflectionParameter->getName(), | ||
$reflectionMethod | ||
); | ||
|
||
if ($parameterType === null) { | ||
continue; | ||
} | ||
|
||
$definitionsOfType = $this->definitionFinder->findAllByType($containerBuilder, $parameterType); | ||
$definitionsOfType = $this->filterOutAbstractDefinitions($definitionsOfType); | ||
|
||
$argumentName = '$' . $reflectionParameter->getName(); | ||
$definition->setArgument($argumentName, $this->createReferencesFromDefinitions($definitionsOfType)); | ||
} | ||
} | ||
|
||
/** | ||
* Abstract definitions cannot be the target of references | ||
* | ||
* @param Definition[] $definitions | ||
* @return Definition[] | ||
*/ | ||
private function filterOutAbstractDefinitions(array $definitions): array | ||
{ | ||
foreach ($definitions as $key => $definition) { | ||
if ($definition->isAbstract()) { | ||
unset($definitions[$key]); | ||
} | ||
} | ||
|
||
return $definitions; | ||
} | ||
|
||
/** | ||
* @param Definition[] $definitions | ||
* @return Reference[] | ||
*/ | ||
private function createReferencesFromDefinitions(array $definitions): array | ||
{ | ||
$references = []; | ||
$definitionOfTypeNames = array_keys($definitions); | ||
foreach ($definitionOfTypeNames as $definitionOfTypeName) { | ||
$references[] = new Reference($definitionOfTypeName); | ||
} | ||
|
||
return $references; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Rector\Core\DependencyInjection; | ||
|
||
use Rector\Core\DependencyInjection\Exception\DefinitionForTypeNotFoundException; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\DependencyInjection\Definition; | ||
use Throwable; | ||
|
||
/** | ||
* @api | ||
*/ | ||
final class DefinitionFinder | ||
{ | ||
/** | ||
* @return Definition[] | ||
*/ | ||
public function findAllByType(ContainerBuilder $containerBuilder, string $type): array | ||
{ | ||
$definitions = []; | ||
$containerBuilderDefinitions = $containerBuilder->getDefinitions(); | ||
foreach ($containerBuilderDefinitions as $name => $definition) { | ||
$class = $definition->getClass() ?: $name; | ||
if (! $this->doesClassExists($class)) { | ||
continue; | ||
} | ||
|
||
if (is_a($class, $type, true)) { | ||
$definitions[$name] = $definition; | ||
} | ||
} | ||
|
||
return $definitions; | ||
} | ||
|
||
public function getByType(ContainerBuilder $containerBuilder, string $type): Definition | ||
{ | ||
$definition = $this->getByTypeIfExists($containerBuilder, $type); | ||
if ($definition !== null) { | ||
return $definition; | ||
} | ||
|
||
throw new DefinitionForTypeNotFoundException(sprintf('Definition for type "%s" was not found.', $type)); | ||
} | ||
|
||
private function getByTypeIfExists(ContainerBuilder $containerBuilder, string $type): ?Definition | ||
{ | ||
$containerBuilderDefinitions = $containerBuilder->getDefinitions(); | ||
foreach ($containerBuilderDefinitions as $name => $definition) { | ||
$class = $definition->getClass() ?: $name; | ||
if (! $this->doesClassExists($class)) { | ||
continue; | ||
} | ||
|
||
if (is_a($class, $type, true)) { | ||
return $definition; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private function doesClassExists(string $class): bool | ||
{ | ||
try { | ||
return class_exists($class); | ||
} catch (Throwable) { | ||
return false; | ||
} | ||
} | ||
} |
Oops, something went wrong.