Skip to content

Commit

Permalink
Compatibility with BetterReflection 6.x on ClassFromEnumFactory (#3150)
Browse files Browse the repository at this point in the history
* 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 <action@github.com>
  • Loading branch information
samsonasik and actions-user committed Dec 4, 2022
1 parent e14aac7 commit efd09bb
Show file tree
Hide file tree
Showing 10 changed files with 518 additions and 7 deletions.
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
"symfony/dependency-injection": "6.1.*",
"symfony/finder": "^6.2",
"symfony/string": "^6.2",
"symplify/autowire-array-parameter": "^11.1.17",
"symplify/easy-parallel": "^11.1.17",
"symplify/rule-doc-generator-contracts": "^11.1.17",
"webmozart/assert": "^1.11"
Expand Down
23 changes: 22 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,9 @@ parameters:

-
message: '#Only booleans are allowed in an if condition, array\|null given#'
path: rules/Naming/Naming/PropertyNaming.php
paths:
- rules/Naming/Naming/PropertyNaming.php
- src/DependencyInjection/CompilerPass/AutowireArrayParameterCompilerPass.php

# mapper re-use
- '#Parameter \#1 \$type of method Rector\\PHPStanStaticTypeMapper\\TypeMapper\\ObjectWithoutClassTypeMapper\:\:mapToPhpParserNode\(\) expects PHPStan\\Type\\ObjectWithoutClassType, PHPStan\\Type\\Accessory\\Has(Property|Method)Type given#'
Expand Down Expand Up @@ -486,7 +488,13 @@ parameters:
- src/Bootstrap/ExtensionConfigResolver.php
# for config class reflection
- packages/NodeTypeResolver/DependencyInjection/PHPStanServicesFactory.php
- src/DependencyInjection/CompilerPass/AutowireArrayParameterCompilerPass.php
- src/DependencyInjection/Skipper/ParameterSkipper.php
- src/DependencyInjection/DefinitionFinder.php

-
message: '#Function "interface_exists\(\)" cannot be used/left in the code\: use ReflectionProvider\->has\*\(\) instead#'
path: src/DependencyInjection/Skipper/ParameterSkipper.php

# use of internal phpstan classes
-
Expand Down Expand Up @@ -690,6 +698,14 @@ parameters:
message: '#Only booleans are allowed in a negated boolean, string\|false given#'
path: packages/Testing/Fixture/FixtureFileUpdater.php

-
message: '#Only booleans are allowed in a negated boolean, array<int, ReflectionParameter> given#'
path: src/DependencyInjection/CompilerPass/AutowireArrayParameterCompilerPass.php

-
message: '#Only booleans are allowed in an if condition, array\|string\|null give#'
path: src/DependencyInjection/CompilerPass/AutowireArrayParameterCompilerPass.php

-
message: '#Content of method "(.*?)" is duplicated\. Use unique content or service instead#'
paths:
Expand Down Expand Up @@ -780,3 +796,8 @@ parameters:
# on purpose, as rule it about to be removed
- '#Register "Rector\\Php74\\Rector\\Property\\TypedPropertyRector" service to "php74\.php" config set#'
- '#Cognitive complexity for "Rector\\TypeDeclaration\\Rector\\Property\\TypedPropertyFromStrictConstructorRector\:\:refactor\(\)" is 12, keep it under 10#'

-
message: '#Short ternary operator is not allowed\. Use null coalesce operator if applicable or consider using long ternary#'
paths:
- src/DependencyInjection/DefinitionFinder.php
18 changes: 14 additions & 4 deletions rules/Php81/NodeFactory/ClassFromEnumFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,22 @@ public function createFromEnum(Enum_ $enum): Class_
}

$constValue = $this->createConstValue($stmt);
$classStmts[] = new ClassConst([new Const_($stmt->name, $constValue)], Visibility::PUBLIC);
$classStmts[] = new ClassConst([new Const_($stmt->name, $constValue)], Visibility::PUBLIC, [
'startLine' => $stmt->getStartLine(),
'endLine' => $stmt->getEndLine(),
]);
}

$class = new Class_($shortClassName, [
'stmts' => $classStmts,
]);
$class = new Class_(
$shortClassName,
[
'stmts' => $classStmts,
],
[
'startLine' => $enum->getStartLine(),
'endLine' => $enum->getEndLine(),
]
);

$class->namespacedName = $enum->namespacedName;

Expand Down
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;
}
}
73 changes: 73 additions & 0 deletions src/DependencyInjection/DefinitionFinder.php
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;
}
}
}

0 comments on commit efd09bb

Please sign in to comment.