[Symfony] refactor to ServiceMap

This commit is contained in:
TomasVotruba 2019-12-16 15:45:37 +01:00
parent 7292171e69
commit c1c0e37690
35 changed files with 796 additions and 694 deletions

View File

@ -221,6 +221,17 @@ vendor/bin/rector process src --match-git-diff
This option is useful in CI with pull-requests that only change few files.
### Symfony Container
To work with some Symfony rules, you now need to link your container XML file
```yaml
# rector.yaml
parameters:
# path to load services from
symfony_container_xml_path: 'var/cache/dev/AppKernelDevDebugContainer.xml'
```
## 3 Steps to Create Your Own Rector
First, make sure it's not covered by [any existing Rectors](/docs/AllRectorsOverview.md).

View File

@ -43,7 +43,6 @@ services:
# alises
Symfony\Contracts\EventDispatcher\EventDispatcherInterface: '@Rector\EventDispatcher\AutowiredEventDispatcher'
Rector\Bridge\Contract\AnalyzedApplicationContainerInterface: '@Rector\Symfony\Bridge\DefaultAnalyzedSymfonyApplicationContainer'
OndraM\CiDetector\CiDetector: ~

View File

@ -1,8 +1,6 @@
parameters:
# explicit environment of analyzed application
kernel_environment: ''
# explicit kernel class of analyzed application
kernel_class: ''
# path to load services from
symfony_container_xml_path: ''
services:
_defaults:
@ -13,4 +11,6 @@ services:
resource: '../src'
exclude:
- '../src/Rector/**/*Rector.php'
- '../src/Exception/*'
- '../src/ValueObject/*'
- '../src/PhpDocParser/Ast/PhpDoc/*'

View File

@ -1,164 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Symfony\Bridge;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\Bridge\Contract\AnalyzedApplicationContainerInterface;
use Rector\Configuration\Option;
use Rector\Exception\ShouldNotHappenException;
use Rector\Symfony\Bridge\DependencyInjection\SymfonyContainerFactory;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symplify\PackageBuilder\Parameter\ParameterProvider;
use Throwable;
final class DefaultAnalyzedSymfonyApplicationContainer implements AnalyzedApplicationContainerInterface
{
/**
* @var SymfonyKernelParameterGuard
*/
private $symfonyKernelParameterGuard;
/**
* @var SymfonyContainerFactory
*/
private $symfonyContainerFactory;
/**
* @var ParameterProvider
*/
private $parameterProvider;
/**
* @var ContainerInterface[]
*/
private $containerByKernelClass = [];
/**
* @var string[]
*/
private $commonNamesToTypes = [
'doctrine' => 'Symfony\Bridge\Doctrine\RegistryInterface',
'doctrine.orm.entity_manager' => 'Doctrine\ORM\EntityManagerInterface',
'doctrine.orm.default_entity_manager' => 'Doctrine\ORM\EntityManagerInterface',
];
public function __construct(
SymfonyKernelParameterGuard $symfonyKernelParameterGuard,
SymfonyContainerFactory $symfonyContainerFactory,
ParameterProvider $parameterProvider
) {
$this->symfonyKernelParameterGuard = $symfonyKernelParameterGuard;
$this->symfonyContainerFactory = $symfonyContainerFactory;
$this->parameterProvider = $parameterProvider;
}
public function getTypeForName(string $name): Type
{
if (isset($this->commonNamesToTypes[$name])) {
return new ObjectType($this->commonNamesToTypes[$name]);
}
// get known Symfony types
$container = $this->getContainer($name);
if (! $container->has($name)) {
return new MixedType();
}
try {
$service = $container->get($name);
} catch (Throwable $throwable) {
throw new ShouldNotHappenException(sprintf(
'Service type for "%s" name was not found in container of your Symfony application.',
$name
), $throwable->getCode(), $throwable);
}
if ($service === null) {
return new MixedType();
}
$serviceClass = get_class($service);
if ($container->has($serviceClass)) {
return new ObjectType($serviceClass);
}
// the type was not found in container → use it's interface or parent
// mimics: \Symfony\Component\DependencyInjection\Compiler\AutowirePass::getAliasesSuggestionForType()
foreach (class_implements($serviceClass) + class_parents($serviceClass) as $parent) {
if ($container->has($parent) && ! $container->findDefinition($parent)->isAbstract()) {
return new ObjectType($parent);
}
}
// make an assumption
return new ObjectType(get_class($service));
}
public function hasService(string $name): bool
{
$container = $this->getContainer($name);
return $container->has($name);
}
/**
* @return object
*/
public function getService(string $name)
{
$container = $this->getContainer($name);
return $container->get($name);
}
/**
* @return ContainerBuilder
*/
private function getContainer(string $requestServiceName): Container
{
$kernelClass = $this->resolveKernelClass();
if (isset($this->containerByKernelClass[$kernelClass])) {
return $this->containerByKernelClass[$kernelClass];
}
$this->symfonyKernelParameterGuard->ensureKernelClassIsValid($kernelClass, $requestServiceName);
/** @var string $kernelClass */
$container = $this->symfonyContainerFactory->createFromKernelClass($kernelClass);
$this->containerByKernelClass[$kernelClass] = $container;
return $container;
}
private function resolveKernelClass(): ?string
{
$kernelClassParameter = $this->parameterProvider->provideParameter(Option::KERNEL_CLASS_PARAMETER);
if ($kernelClassParameter) {
return $kernelClassParameter;
}
return $this->getDefaultKernelClass();
}
private function getDefaultKernelClass(): ?string
{
$possibleKernelClasses = ['App\Kernel', 'Kernel', 'AppKernel'];
foreach ($possibleKernelClasses as $possibleKernelClass) {
if (class_exists($possibleKernelClass)) {
return $possibleKernelClass;
}
}
return null;
}
}

View File

@ -1,127 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Symfony\Bridge\DependencyInjection;
use Nette\Utils\Random;
use Rector\Configuration\Option;
use Rector\Exception\ShouldNotHappenException;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\HttpKernel\Kernel;
use Symplify\PackageBuilder\Parameter\ParameterProvider;
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
use Symplify\PackageBuilder\Reflection\PrivatesCaller;
use Throwable;
final class SymfonyContainerFactory
{
/**
* @var string
*/
private const FALLBACK_ENVIRONMENT = 'dev';
/**
* @var Container[]
*/
private $containersByKernelClass = [];
/**
* @var ParameterProvider
*/
private $parameterProvider;
public function __construct(ParameterProvider $parameterProvider)
{
$this->parameterProvider = $parameterProvider;
}
public function createFromKernelClass(string $kernelClass): Container
{
if (isset($this->containersByKernelClass[$kernelClass])) {
return $this->containersByKernelClass[$kernelClass];
}
$environment = $this->resolveEnvironment();
try {
$debug = (bool) ($_ENV['APP_DEBUG'] ?? $_SERVER['APP_DEBUG'] ?? true);
$container = $this->createContainerFromKernelClass($kernelClass, $environment, $debug);
} catch (Throwable $throwable) {
throw new ShouldNotHappenException(sprintf(
'Kernel "%s" could not be instantiated for: %s.%sCurrent environment is "%s". Try changing it in "parameters > kernel_environment" in rector.yaml',
$kernelClass,
$throwable->getMessage(),
PHP_EOL . PHP_EOL,
$environment
), $throwable->getCode(), $throwable);
}
$this->containersByKernelClass[$kernelClass] = $container;
return $container;
}
private function resolveEnvironment(): string
{
/** @var string|null $kernelEnvironment */
$kernelEnvironment = $this->parameterProvider->provideParameter(Option::KERNEL_ENVIRONMENT_PARAMETER);
if ($kernelEnvironment) {
return $kernelEnvironment;
}
return $_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? self::FALLBACK_ENVIRONMENT;
}
/**
* Mimics https://github.com/symfony/symfony/blob/f834c9262b411aa5793fcea23694e3ad3b5acbb4/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php#L200-L203
*/
private function createContainerFromKernelClass(string $kernelClass, string $environment, bool $debug): Container
{
/** @var Kernel $kernel */
$kernel = new $kernelClass($environment, $debug);
// preloads all the extensions and $containerBuilder->compile()
$kernel->boot();
/** @var ContainerBuilder $containerBuilder */
$containerBuilder = (new PrivatesCaller())->callPrivateMethod($kernel, 'buildContainer');
$containerBuilder->getCompilerPassConfig()->setRemovingPasses([]);
// anonymous class on intention, since this depends on Symfony\DependencyInjection in rector-prefixed
$containerBuilder->getCompilerPassConfig()->addPass(new class() implements CompilerPassInterface {
public function process(ContainerBuilder $containerBuilder): void
{
foreach ($containerBuilder->getDefinitions() as $definition) {
$definition->setPublic(true);
}
foreach ($containerBuilder->getAliases() as $definition) {
$definition->setPublic(true);
}
}
});
$containerBuilder->compile(true);
// solves "You have requested a synthetic service ("kernel"). The DIC does not know how to construct this service"
$containerBuilder->set('kernel', $kernel);
// solves "You have requested a non-existent parameter "container.build_id"
if ($containerBuilder->getParameterBag() instanceof FrozenParameterBag) {
$unfrozenParameterBag = new ParameterBag($containerBuilder->getParameterBag()->all());
$privatesAccessor = new PrivatesAccessor();
$privatesAccessor->setPrivateProperty($containerBuilder, 'parameterBag', $unfrozenParameterBag);
}
if (! $containerBuilder->hasParameter('container.build_id')) {
$containerBuilder->setParameter('container.build_id', Random::generate(10));
}
return $containerBuilder;
}
}

View File

@ -1,55 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Symfony\Bridge;
use Rector\Configuration\Option;
use Rector\Exception\Configuration\InvalidConfigurationException;
use Symfony\Component\HttpKernel\Kernel;
final class SymfonyKernelParameterGuard
{
public function ensureKernelClassIsValid(?string $kernelClass, string $requestServiceName): void
{
// ensure value is not null nor empty
if ($kernelClass === null || $kernelClass === '') {
throw new InvalidConfigurationException(sprintf(
'Make sure "parameters > %s" is set in rector.yaml.%sIt is needed to resolve "%s" service name to type',
Option::KERNEL_CLASS_PARAMETER,
PHP_EOL,
$requestServiceName
));
}
$this->ensureKernelClassExists($kernelClass);
$this->ensureIsKernelInstance($kernelClass);
}
private function ensureKernelClassExists(string $kernelClass): void
{
if (class_exists($kernelClass)) {
return;
}
throw new InvalidConfigurationException(sprintf(
'Kernel class "%s" provided in "parameters > %s" is not autoloadable. Make sure composer.json of your application is valid and rector is loading "vendor/autoload.php" of your application.',
$kernelClass,
Option::KERNEL_CLASS_PARAMETER
));
}
private function ensureIsKernelInstance(string $kernelClass): void
{
if (is_a($kernelClass, Kernel::class, true)) {
return;
}
throw new InvalidConfigurationException(sprintf(
'Kernel class "%s" provided in "parameters > %s" is not instance of "%s". Make sure it is.',
$kernelClass,
'kernel_class',
Kernel::class
));
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Rector\Symfony\Contract\Tag;
interface TagInterface
{
public function getName(): string;
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Rector\Symfony\Exception;
use Exception;
final class XmlContainerNotExistsException extends Exception
{
}

View File

@ -13,11 +13,11 @@ use PhpParser\Node\Stmt\Class_;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\Bridge\Contract\AnalyzedApplicationContainerInterface;
use Rector\Exception\ShouldNotHappenException;
use Rector\Naming\PropertyNaming;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Rector\AbstractRector;
use Rector\Symfony\ServiceMapProvider;
abstract class AbstractToConstructorInjectionRector extends AbstractRector
{
@ -27,19 +27,19 @@ abstract class AbstractToConstructorInjectionRector extends AbstractRector
protected $propertyNaming;
/**
* @var AnalyzedApplicationContainerInterface
* @var ServiceMapProvider
*/
protected $analyzedApplicationContainer;
private $applicationServiceMapProvider;
/**
* @required
*/
public function setAbstractToConstructorInjectionRectorDependencies(
public function autowireAbstractToConstructorInjectionRectorDependencies(
PropertyNaming $propertyNaming,
AnalyzedApplicationContainerInterface $analyzedApplicationContainer
ServiceMapProvider $applicationServiceMapProvider
): void {
$this->propertyNaming = $propertyNaming;
$this->analyzedApplicationContainer = $analyzedApplicationContainer;
$this->applicationServiceMapProvider = $applicationServiceMapProvider;
}
protected function processMethodCallNode(MethodCall $methodCall): ?Node
@ -71,10 +71,10 @@ abstract class AbstractToConstructorInjectionRector extends AbstractRector
$argument = $methodCallNode->args[0]->value;
if ($argument instanceof String_) {
$serviceName = $argument->value;
$serviceMap = $this->applicationServiceMapProvider->provide();
return $this->analyzedApplicationContainer->getTypeForName($serviceName);
if ($argument instanceof String_) {
return $serviceMap->getServiceType($argument->value);
}
if (! $argument instanceof ClassConstFetch) {

View File

@ -0,0 +1,231 @@
<?php
declare(strict_types=1);
namespace Rector\Symfony;
use Nette\Utils\FileSystem;
use Nette\Utils\Json;
use Nette\Utils\Strings;
use Rector\Symfony\Contract\Tag\TagInterface;
use Rector\Symfony\Exception\XmlContainerNotExistsException;
use Rector\Symfony\ValueObject\ServiceDefinition;
use Rector\Symfony\ValueObject\ServiceMap\ServiceMap;
use Rector\Symfony\ValueObject\Tag;
use Rector\Symfony\ValueObject\Tag\EventListenerTag;
use SimpleXMLElement;
use Symplify\PackageBuilder\Parameter\ParameterProvider;
/**
* Inspired by https://github.com/phpstan/phpstan-symfony/tree/master/src/Symfony
*/
final class ServiceMapProvider
{
/**
* @var string
*/
private const SYMFONY_CONTAINER_XML_PATH_PARAMETER = 'symfony_container_xml_path';
/**
* @var ParameterProvider
*/
private $parameterProvider;
public function __construct(ParameterProvider $parameterProvider)
{
$this->parameterProvider = $parameterProvider;
}
public function provide(): ServiceMap
{
$symfonyContainerXmlPath = $this->getSymfonyContainerXmlPath();
if ($symfonyContainerXmlPath === '') {
return new ServiceMap([]);
}
$fileContents = FileSystem::read($symfonyContainerXmlPath);
return $this->createServiceMapFromXml($fileContents);
}
private function createServiceMapFromXml(string $fileContents): ServiceMap
{
$xml = @simplexml_load_string($fileContents);
if (! $xml) {
throw new XmlContainerNotExistsException(sprintf(
'Container %s cannot be parsed', $this->getSymfonyContainerXmlPath()
));
}
/** @var ServiceDefinition[] $services */
$services = [];
/** @var ServiceDefinition[] $aliases */
$aliases = [];
foreach ($xml->services->service as $def) {
/** @var SimpleXMLElement $attrs */
$attrs = $def->attributes();
if (! isset($attrs->id)) {
continue;
}
$def = $this->convertXmlToArray($def);
$tags = $this->createTagFromXmlElement($def);
$service = $this->createServiceFromXml($attrs, $tags);
if ($service->getAlias() !== null) {
$aliases[] = $service;
} else {
$services[$service->getId()] = $service;
}
}
$services = $this->createAliasServiceDefinitions($aliases, $services);
return new ServiceMap($services);
}
/**
* @param mixed[] $tags
*/
private function createServiceFromXml(SimpleXMLElement $attrs, array $tags): ServiceDefinition
{
$tags = $this->createTagsFromXml($tags);
return new ServiceDefinition(
strpos((string) $attrs->id, '.') === 0 ? Strings::substring((string) $attrs->id, 1) : (string) $attrs->id,
isset($attrs->class) ? (string) $attrs->class : null,
! isset($attrs->public) || (string) $attrs->public !== 'false',
isset($attrs->synthetic) && (string) $attrs->synthetic === 'true',
isset($attrs->alias) ? (string) $attrs->alias : null,
$tags
);
}
/**
* @param mixed[] $tags
* @return TagInterface[]
*/
private function createTagsFromXml(array $tags): array
{
$tagValueObjects = [];
foreach ($tags as $key => $tag) {
$data = $tag;
$name = $data['name'] ?? '';
if ($name === 'kernel.event_listener') {
$tagValueObjects[$key] = new EventListenerTag(
$data['event'] ?? '',
$data['method'] ?? '',
(int) ($data['priority'] ?? 0)
);
} else {
unset($data['name']);
$tagValueObjects[$key] = new Tag($name, $data);
}
}
return $tagValueObjects;
}
/**
* @return mixed[]
*/
private function convertXmlToArray(SimpleXMLElement $simpleXMLElement): array
{
$data = Json::decode(Json::encode((array) $simpleXMLElement), Json::FORCE_ARRAY);
$data = $this->unWrapAttributes($data);
foreach ($data as $key => $value) {
if (is_array($value)) {
$data = $this->convertedNestedArrayOrXml($value, $data, $key);
} elseif ($value instanceof SimpleXMLElement) {
$data[$key] = $this->convertXmlToArray($value);
}
}
return $data;
}
private function unWrapAttributes(array $data): array
{
if (isset($data['@attributes'])) {
foreach ($data['@attributes'] as $key => $value) {
$data[$key] = $value;
}
unset($data['@attributes']);
}
return $data;
}
private function createTagFromXmlElement($def): array
{
if (! isset($def['tag'])) {
return [];
}
$tags = [];
if (is_array($def['tag'])) {
foreach ($def['tag'] as $tag) {
$tags[] = $tag;
}
} else {
$tags[] = $def['tag'];
}
return $tags;
}
private function convertedNestedArrayOrXml(array $value, array $data, $key): array
{
foreach ($value as $subKey => $subValue) {
if ($subValue instanceof SimpleXMLElement) {
$data[$key][$subKey] = $this->convertXmlToArray($subValue);
} elseif (is_array($subValue)) {
$data[$key][$subKey] = $this->unWrapAttributes($subValue);
}
}
return $data;
}
/**
* @param ServiceDefinition[] $aliases
* @param ServiceDefinition[] $services
* @return ServiceDefinition[]
*/
private function createAliasServiceDefinitions(array $aliases, array $services): array
{
foreach ($aliases as $service) {
$alias = $service->getAlias();
if ($alias === null) {
continue;
}
if (! isset($services[$alias])) {
continue;
}
$id = $service->getId();
$services[$id] = new ServiceDefinition(
$id,
$services[$alias]->getClass(),
$service->isPublic(),
$service->isSynthetic(),
$alias,
[]
);
}
return $services;
}
private function getSymfonyContainerXmlPath(): string
{
return (string) $this->parameterProvider->provideParameter(self::SYMFONY_CONTAINER_XML_PATH_PARAMETER);
}
}

View File

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace Rector\Symfony\ValueObject;
use Rector\Symfony\Contract\Tag\TagInterface;
final class ServiceDefinition
{
/**
* @var string
*/
private $id;
/**
* @var string|null
*/
private $class;
/**
* @var bool
*/
private $public = false;
/**
* @var bool
*/
private $synthetic = false;
/**
* @var string|null
*/
private $alias;
/**
* @var TagInterface[]
*/
private $tags = [];
/**
* @param TagInterface[] $tags
*/
public function __construct(string $id, ?string $class, bool $public, bool $synthetic, ?string $alias, array $tags)
{
$this->id = $id;
$this->class = $class;
$this->public = $public;
$this->synthetic = $synthetic;
$this->alias = $alias;
$this->tags = $tags;
}
public function getId(): string
{
return $this->id;
}
public function getClass(): ?string
{
return $this->class;
}
public function isPublic(): bool
{
return $this->public;
}
public function isSynthetic(): bool
{
return $this->synthetic;
}
public function getAlias(): ?string
{
return $this->alias;
}
/**
* @return TagInterface[]
*/
public function getTags(): array
{
return $this->tags;
}
}

View File

@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace Rector\Symfony\ValueObject\ServiceMap;
use PhpParser\Node\Expr;
use PHPStan\Analyser\Scope;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;
use Rector\Symfony\ValueObject\ServiceDefinition;
final class ServiceMap
{
/**
* @var ServiceDefinition[]
*/
private $services = [];
/**
* @param ServiceDefinition[] $services
*/
public function __construct(array $services)
{
$this->services = $services;
}
/**
* @return ServiceDefinition[]
*/
public function getServices(): array
{
return $this->services;
}
public function hasService(string $id): bool
{
return isset($this->services[$id]);
}
public function getService(string $id): ?ServiceDefinition
{
return $this->services[$id] ?? null;
}
public function getServiceType(string $id): ?Type
{
$serviceDefinition = $this->getService($id);
if ($serviceDefinition === null) {
return null;
}
$class = $serviceDefinition->getClass();
if ($class === null) {
return null;
}
$interfaces = class_implements($class);
if (count($interfaces) > 0) {
foreach ($interfaces as $interface) {
// return first interface
return new ObjectType($interface);
}
}
return new ObjectType($class);
}
/**
* @return ServiceDefinition[]
*/
public function getServicesByTag(string $tagName): array
{
$servicesWithTag = [];
foreach ($this->services as $service) {
foreach ($service->getTags() as $tag) {
if ($tag->getName() !== $tagName) {
continue;
}
$servicesWithTag[] = $service;
continue 2;
}
}
return $servicesWithTag;
}
public function getServiceIdFromNode(Expr $expr, Scope $scope): ?string
{
$strings = TypeUtils::getConstantStrings($scope->getType($expr));
return count($strings) === 1 ? $strings[0]->getValue() : null;
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Rector\Symfony\ValueObject;
use Rector\Symfony\Contract\Tag\TagInterface;
final class Tag implements TagInterface
{
/**
* @var string
*/
private $name;
/**
* @var array
*/
private $data = [];
public function __construct(string $name, array $data)
{
$this->name = $name;
$this->data = $data;
}
public function getName(): string
{
return $this->name;
}
public function getData(): array
{
return $this->data;
}
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Rector\Symfony\ValueObject\Tag;
use Rector\Symfony\Contract\Tag\TagInterface;
final class EventListenerTag implements TagInterface
{
/**
* @var string
*/
private $event;
/**
* @var string
*/
private $method;
/**
* @var int
*/
private $priority;
public function __construct(string $event, string $method, int $priority)
{
$this->event = $event;
$this->method = $method;
$this->priority = $priority;
}
public function getName(): string
{
return 'kernel.event_listener';
}
public function getEvent(): string
{
return $this->event;
}
public function getMethod(): string
{
return $this->method;
}
public function getPriority(): int
{
return $this->priority;
}
}

View File

@ -1,47 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Symfony\Tests\FrameworkBundle\AbstractToConstructorInjectionRectorSource;
use Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeEntityManager;
use Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeTranslator;
use Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeTranslatorInterface;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel;
final class SomeKernelClass extends Kernel
{
public function registerBundles(): iterable
{
return [];
}
public function registerContainerConfiguration(LoaderInterface $loader): void
{
}
protected function build(ContainerBuilder $containerBuilder): void
{
$containerBuilder->register('stdClass', 'stdClass');
$containerBuilder->setAlias('some_service', 'stdClass');
$containerBuilder->register('translator.data_collector', SomeTranslator::class);
$containerBuilder->setAlias('translator', 'translator.data_collector');
$containerBuilder->setAlias(SomeTranslatorInterface::class, 'translator.data_collector');
$containerBuilder->register('entity.manager', SomeEntityManager::class);
$containerBuilder->setAlias(SomeEntityManager::class, 'entity.manager');
}
public function getCacheDir()
{
return sys_get_temp_dir() . '/_tmp';
}
public function getLogDir()
{
return sys_get_temp_dir() . '/_tmp';
}
}

View File

@ -7,7 +7,6 @@ namespace Rector\Symfony\Tests\Rector\FrameworkBundle\ContainerGetToConstructorI
use Iterator;
use Rector\Configuration\Option;
use Rector\Symfony\Rector\FrameworkBundle\ContainerGetToConstructorInjectionRector;
use Rector\Symfony\Tests\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeKernelClass;
use Rector\Symfony\Tests\FrameworkBundle\ContainerGetToConstructorInjectionRector\Source\ContainerAwareParentClass;
use Rector\Symfony\Tests\FrameworkBundle\ContainerGetToConstructorInjectionRector\Source\ContainerAwareParentCommand;
use Rector\Symfony\Tests\FrameworkBundle\ContainerGetToConstructorInjectionRector\Source\ThisClassCallsMethodInConstructor;
@ -20,7 +19,10 @@ final class ContainerGetToConstructorInjectionRectorTest extends AbstractRectorT
*/
public function test(string $file): void
{
$this->setParameter(Option::KERNEL_CLASS_PARAMETER, SomeKernelClass::class);
$this->setParameter(
Option::SYMFONY_CONTAINER_XML_PATH_PARAMETER,
__DIR__ . '/../GetToConstructorInjectionRector/xml/services.xml'
);
$this->doTestFile($file);
}

View File

@ -7,7 +7,6 @@ namespace Rector\Symfony\Tests\Rector\FrameworkBundle\GetToConstructorInjectionR
use Iterator;
use Rector\Configuration\Option;
use Rector\Symfony\Rector\FrameworkBundle\GetToConstructorInjectionRector;
use Rector\Symfony\Tests\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeKernelClass;
use Rector\Symfony\Tests\Rector\FrameworkBundle\GetToConstructorInjectionRector\Source\GetTrait;
use Rector\Symfony\Tests\Rector\Source\SymfonyController;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
@ -19,7 +18,7 @@ final class GetToConstructorInjectionRectorTest extends AbstractRectorTestCase
*/
public function test(string $file): void
{
$this->setParameter(Option::KERNEL_CLASS_PARAMETER, SomeKernelClass::class);
$this->setParameter(Option::SYMFONY_CONTAINER_XML_PATH_PARAMETER, __DIR__ . '/xml/services.xml');
$this->doTestFile($file);
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<!-- ->register(id, class) -->
<service id="stdClass" class="stdClass"></service>
<!-- ->setAlias(alias, id) -->
<service id="some_service" alias="stdClass"></service>
<!-- ->register(id, class) -->
<service id="translator.data_collector" class="Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeTranslator"></service>
<!-- ->setAlias(alias, id) -->
<service id="translator" alias="translator.data_collector"></service>
<!-- ->setAlias(alias, id) -->
<service id="Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeTranslatorInterface" alias="translator.data_collector"></service>
<!-- ->register(id, class) -->
<service id="entity.manager" class="Rector\Symfony\Tests\Rector\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeEntityManager"></service>
</services>
</container>

View File

@ -19,12 +19,13 @@ use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Return_;
use PHPStan\Type\ArrayType;
use PHPStan\Type\MixedType;
use Rector\Bridge\Contract\AnalyzedApplicationContainerInterface;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\Symfony\ServiceMapProvider;
use Rector\Symfony\ValueObject\ServiceDefinition;
use Rector\Symfony\ValueObject\Tag\EventListenerTag;
use Rector\ValueObject\PhpVersionFeature;
use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher;
/**
* @see \Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\EventListenerToEventSubscriberRectorTest
@ -66,12 +67,7 @@ final class EventListenerToEventSubscriberRector extends AbstractRector
];
/**
* @var AnalyzedApplicationContainerInterface
*/
private $analyzedApplicationContainer;
/**
* @var mixed[][]
* @var ServiceDefinition[][][]
*/
private $listenerClassesToEvents = [];
@ -80,9 +76,14 @@ final class EventListenerToEventSubscriberRector extends AbstractRector
*/
private $areListenerClassesLoaded = false;
public function __construct(AnalyzedApplicationContainerInterface $analyzedApplicationContainer)
/**
* @var ServiceMapProvider
*/
private $applicationServiceMapProvider;
public function __construct(ServiceMapProvider $applicationServiceMapProvider)
{
$this->analyzedApplicationContainer = $analyzedApplicationContainer;
$this->applicationServiceMapProvider = $applicationServiceMapProvider;
}
public function getDefinition(): RectorDefinition
@ -182,7 +183,7 @@ PHP
}
/**
* @return string[][]
* @return ServiceDefinition[][][]
*/
private function getListenerClassesToEventsToMethods(): array
{
@ -190,33 +191,22 @@ PHP
return $this->listenerClassesToEvents;
}
if (! $this->analyzedApplicationContainer->hasService('event_dispatcher')) {
$this->areListenerClassesLoaded = true;
$serviceMap = $this->applicationServiceMapProvider->provide();
$eventListeners = $serviceMap->getServicesByTag('kernel.event_listener');
return [];
}
foreach ($eventListeners as $eventListener) {
// skip Symfony core listeners
if (Strings::match((string) $eventListener->getClass(), '#^(Symfony|Sensio|Doctrine)\b#')) {
continue;
}
/** @var TraceableEventDispatcher $applicationEventDispatcher */
$applicationEventDispatcher = $this->analyzedApplicationContainer->getService('event_dispatcher');
foreach ($applicationEventDispatcher->getListeners() as $eventName => $listenersInEvent) {
foreach ($listenersInEvent as $listener) {
// must be array → class and method
if (! is_array($listener)) {
foreach ($eventListener->getTags() as $tag) {
if (! $tag instanceof EventListenerTag) {
continue;
}
$listenerClass = get_class($listener[0]);
// skip Symfony core listeners
if (Strings::match($listenerClass, '#^(Symfony|Sensio|Doctrine)\\\\#')) {
continue;
}
$listenerPriority = $applicationEventDispatcher->getListenerPriority($eventName, $listener);
// group event name - method - class :)
$this->listenerClassesToEvents[$listenerClass][$eventName][] = [$listener[1], $listenerPriority];
$eventName = $tag->getEvent();
$this->listenerClassesToEvents[$eventListener->getClass()][$eventName][] = $eventListener;
}
}
@ -237,8 +227,8 @@ PHP
$classShortName = Strings::replace($classShortName, '#^(.*?)(Listener)?$#', '$1');
$class->name = new Identifier($classShortName . 'EventSubscriber');
$clasMethod = $this->createGetSubscribedEventsClassMethod($eventsToMethods);
$class->stmts[] = $clasMethod;
$classMethod = $this->createGetSubscribedEventsClassMethod($eventsToMethods);
$class->stmts[] = $classMethod;
return $class;
}
@ -255,12 +245,17 @@ PHP
$this->makeStatic($getSubscribedEventsMethod);
foreach ($eventsToMethods as $eventName => $methodNamesWithPriorities) {
$eventName = $this->createEventName($eventName);
$eventNameExpr = $this->createEventName($eventName);
if (count($methodNamesWithPriorities) === 1) {
$this->createSingleMethod($methodNamesWithPriorities, $eventName, $eventsToMethodsArray);
$this->createSingleMethod($methodNamesWithPriorities, $eventNameExpr, $eventsToMethodsArray);
} else {
$this->createMultipleMethods($methodNamesWithPriorities, $eventName, $eventsToMethodsArray);
$this->createMultipleMethods(
$methodNamesWithPriorities,
$eventNameExpr,
$eventsToMethodsArray,
$eventName
);
}
}
@ -291,16 +286,21 @@ PHP
/**
* @param ClassConstFetch|String_ $expr
* @param mixed[] $methodNamesWithPriorities
* @param ServiceDefinition[] $methodNamesWithPriorities
*/
private function createSingleMethod(
array $methodNamesWithPriorities,
Expr $expr,
Array_ $eventsToMethodsArray
): void {
[$methodName, $priority] = $methodNamesWithPriorities[0];
if ($priority) {
/** @var EventListenerTag $eventTag */
$eventTag = $methodNamesWithPriorities[0]->getTags()[0];
$methodName = $eventTag->getMethod();
$priority = $eventTag->getPriority();
if ($priority !== 0) {
$methodNameWithPriorityArray = new Array_();
$methodNameWithPriorityArray->items[] = new ArrayItem(new String_($methodName));
$methodNameWithPriorityArray->items[] = new ArrayItem(new LNumber((int) $priority));
@ -313,29 +313,39 @@ PHP
/**
* @param ClassConstFetch|String_ $expr
* @param mixed[] $methodNamesWithPriorities
* @param ServiceDefinition[] $methodNamesWithPriorities
*/
private function createMultipleMethods(
array $methodNamesWithPriorities,
Expr $expr,
Array_ $eventsToMethodsArray
Array_ $eventsToMethodsArray,
string $eventName
): void {
$multipleMethodsArray = new Array_();
$eventItems = [];
$alreadyUsedTags = [];
foreach ($methodNamesWithPriorities as $methodNamesWithPriority) {
[$methodName, $priority] = $methodNamesWithPriority;
foreach ($methodNamesWithPriority->getTags() as $tag) {
if (! $tag instanceof EventListenerTag) {
continue;
}
if ($priority) {
$methodNameWithPriorityArray = new Array_();
$methodNameWithPriorityArray->items[] = new ArrayItem(new String_($methodName));
$methodNameWithPriorityArray->items[] = new ArrayItem(new LNumber((int) $priority));
if ($eventName !== $tag->getEvent()) {
continue;
}
$multipleMethodsArray->items[] = new ArrayItem($methodNameWithPriorityArray);
} else {
$multipleMethodsArray->items[] = new ArrayItem(new String_($methodName));
if (in_array($tag, $alreadyUsedTags, true)) {
continue;
}
$eventItems[] = $this->createEventItem($tag);
$alreadyUsedTags[] = $tag;
}
}
$multipleMethodsArray = new Array_($eventItems);
$eventsToMethodsArray->items[] = new ArrayItem($multipleMethodsArray, $expr);
}
@ -348,4 +358,17 @@ PHP
$arrayMixedType = new ArrayType(new MixedType(), new MixedType(true));
$this->docBlockManipulator->addReturnTag($classMethod, $arrayMixedType);
}
private function createEventItem(EventListenerTag $eventListenerTag): ArrayItem
{
if ($eventListenerTag->getPriority() !== 0) {
$methodNameWithPriorityArray = new Array_();
$methodNameWithPriorityArray->items[] = new ArrayItem(new String_($eventListenerTag->getMethod()));
$methodNameWithPriorityArray->items[] = new ArrayItem(new LNumber($eventListenerTag->getPriority()));
return new ArrayItem($methodNameWithPriorityArray);
}
return new ArrayItem(new String_($eventListenerTag->getMethod()));
}
}

View File

@ -6,7 +6,6 @@ namespace Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubs
use Rector\Configuration\Option;
use Rector\SymfonyCodeQuality\Rector\Class_\EventListenerToEventSubscriberRector;
use Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Source\ListenersKernel;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class EventListenerToEventSubscriberRectorTest extends AbstractRectorTestCase
@ -14,8 +13,11 @@ final class EventListenerToEventSubscriberRectorTest extends AbstractRectorTestC
public function test(): void
{
// wtf: all test have to be in single file due to autoloading race-condigition and container creating issue of fixture
$this->setParameter(Option::KERNEL_CLASS_PARAMETER, ListenersKernel::class);
$this->doTestFile(__DIR__ . '/Fixture/fixture.php.inc');
$this->setParameter(Option::SYMFONY_CONTAINER_XML_PATH_PARAMETER, __DIR__ . '/config/listener_services.xml');
$this->doTestFile(__DIR__ . '/Fixture/some_listener.php.inc');
$this->doTestFile(__DIR__ . '/Fixture/with_priority_listener.php.inc');
$this->doTestFile(__DIR__ . '/Fixture/multiple_listeners.php.inc');
}
protected function getRectorClass(): string

View File

@ -2,20 +2,6 @@
namespace Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Fixture;
class SomeListener
{
public function methodToBeCalled()
{
}
}
class WithPriorityListener
{
public function callMe()
{
}
}
class MultipleMethods
{
public function callMe()
@ -37,34 +23,6 @@ class MultipleMethods
namespace Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Fixture;
class SomeEventSubscriber implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
{
public function methodToBeCalled()
{
}
/**
* @return mixed[]
*/
public static function getSubscribedEvents(): array
{
return ['some_event' => 'methodToBeCalled'];
}
}
class WithPriorityEventSubscriber implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
{
public function callMe()
{
}
/**
* @return mixed[]
*/
public static function getSubscribedEvents(): array
{
return ['some_event' => ['callMe', 1540]];
}
}
class MultipleMethodsEventSubscriber implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
{
public function callMe()

View File

@ -0,0 +1,32 @@
<?php
namespace Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Fixture;
class SomeListener
{
public function methodToBeCalled()
{
}
}
?>
-----
<?php
namespace Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Fixture;
class SomeEventSubscriber implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
{
public function methodToBeCalled()
{
}
/**
* @return mixed[]
*/
public static function getSubscribedEvents(): array
{
return ['some_event' => 'methodToBeCalled'];
}
}
?>

View File

@ -0,0 +1,32 @@
<?php
namespace Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Fixture;
class WithPriorityListener
{
public function callMe()
{
}
}
?>
-----
<?php
namespace Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Fixture;
class WithPriorityEventSubscriber implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
{
public function callMe()
{
}
/**
* @return mixed[]
*/
public static function getSubscribedEvents(): array
{
return ['some_event' => ['callMe', 1540]];
}
}
?>

View File

@ -1,90 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Source;
use Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Fixture\MultipleCallsListener;
use Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Fixture\MultipleMethods;
use Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Fixture\SomeListener;
use Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Fixture\WithPriorityListener;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpKernel\Kernel;
final class ListenersKernel extends Kernel
{
public function registerBundles(): iterable
{
return [];
}
public function registerContainerConfiguration(LoaderInterface $loader): void
{
}
protected function build(ContainerBuilder $containerBuilder): void
{
$eventDispatcherDefinition = $containerBuilder->register('event_dispatcher', EventDispatcher::class);
$this->registerSimpleListener($containerBuilder, $eventDispatcherDefinition);
$this->registerWithPriority($containerBuilder, $eventDispatcherDefinition);
$this->registerMultiple($containerBuilder, $eventDispatcherDefinition);
}
public function getCacheDir()
{
return sys_get_temp_dir() . '/_tmp';
}
public function getLogDir()
{
return sys_get_temp_dir() . '/_tmp';
}
private function registerSimpleListener(ContainerBuilder $containerBuilder, Definition $eventDispatcherDefinition): void
{
$containerBuilder->register(SomeListener::class);
/* @see \Symfony\Component\EventDispatcher\EventDispatcher::addListener() */
$eventDispatcherDefinition->addMethodCall(
'addListener',
['some_event', [new Reference(SomeListener::class), 'methodToBeCalled']]
);
}
private function registerWithPriority(ContainerBuilder $containerBuilder, Definition $eventDispatcherDefinition): void
{
$containerBuilder->register(WithPriorityListener::class);
/* @see \Symfony\Component\EventDispatcher\EventDispatcher::addListener() */
$eventDispatcherDefinition->addMethodCall(
'addListener',
['some_event', [new Reference(WithPriorityListener::class), 'callMe'], 1540]
);
}
private function registerMultiple(ContainerBuilder $containerBuilder, Definition $eventDispatcherDefinition): void
{
$containerBuilder->register(MultipleMethods::class);
/* @see \Symfony\Component\EventDispatcher\EventDispatcher::addListener() */
$eventDispatcherDefinition->addMethodCall(
'addListener',
['single_event', [new Reference(MultipleMethods::class), 'singles']]
);
$eventDispatcherDefinition->addMethodCall(
'addListener',
['multi_event', [new Reference(MultipleMethods::class), 'callMe']]
);
$eventDispatcherDefinition->addMethodCall(
'addListener',
['multi_event', [new Reference(MultipleMethods::class), 'meToo']]
);
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="first_listener" class="Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Fixture\SomeListener">
<tag name="kernel.event_listener" event="some_event" method="methodToBeCalled"/>
</service>
<service id="second_listener" class="Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Fixture\WithPriorityListener">
<tag name="kernel.event_listener" event="some_event" method="callMe" priority="1540" />
</service>
<service id="third_listener" class="Rector\SymfonyCodeQuality\Tests\Rector\Class_\EventListenerToEventSubscriberRector\Fixture\MultipleMethods">
<tag name="kernel.event_listener" event="single_event" method="singles"/>
<tag name="kernel.event_listener" event="multi_event" method="callMe"/>
<tag name="kernel.event_listener" event="multi_event" method="meToo"/>
</service>
<service id="event_dispatcher" class="Symfony\Component\EventDispatcher\EventDispatcher"/>
</services>
</container>

View File

@ -227,3 +227,12 @@ parameters:
-
message: '#Call to function in_array\(\) with arguments PhpParser\\Node\\Expr\\Variable, array\(\) and true will always evaluate to false#'
path: packages/Php56/src/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector.php
# known values
-
message: '#Method Rector\\Rector\\Property\\InjectAnnotationClassRector\:\:resolveJMSDIInjectType\(\) should return PHPStan\\Type\\Type but returns PHPStan\\Type\\Type\|null#'
path: src/Rector/Property/InjectAnnotationClassRector.php
-
message: '#Method Rector\\Symfony\\Rector\\FrameworkBundle\\AbstractToConstructorInjectionRector\:\:getServiceTypeFromMethodCallArgument\(\) should return PHPStan\\Type\\Type but returns PHPStan\\Type\\Type\|null#'
path: packages/Symfony/src/Rector/FrameworkBundle/AbstractToConstructorInjectionRector.php

View File

@ -1,9 +1,6 @@
imports:
- { resource: "create-rector.yaml", ignore_errors: true }
services:
Rector\PSR4\Rector\Namespace_\NormalizeNamespaceByPSR4ComposerAutoloadRector: ~
parameters:
exclude_paths:
- "/Fixture/"

View File

@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Bridge\Contract;
use PHPStan\Type\Type;
interface AnalyzedApplicationContainerInterface
{
public function getTypeForName(string $name): Type;
public function hasService(string $name): bool;
/**
* @return object
*/
public function getService(string $name);
}

View File

@ -26,16 +26,6 @@ final class Option
*/
public const OPTION_OUTPUT_FORMAT = 'output-format';
/**
* @var string
*/
public const KERNEL_CLASS_PARAMETER = 'kernel_class';
/**
* @var string
*/
public const KERNEL_ENVIRONMENT_PARAMETER = 'kernel_environment';
/**
* @var string
*/
@ -70,4 +60,9 @@ final class Option
* @var string
*/
public const MATCH_GIT_DIFF = 'match-git-diff';
/**
* @var string
*/
public const SYMFONY_CONTAINER_XML_PATH_PARAMETER = 'symfony_container_xml_path';
}

View File

@ -10,11 +10,11 @@ use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Type\ObjectType;
use Rector\Bridge\Contract\AnalyzedApplicationContainerInterface;
use Rector\Configuration\Rector\Architecture\DependencyInjection\VariablesToPropertyFetchCollection;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\Symfony\ServiceMapProvider;
/**
* @see \Rector\Tests\Rector\Architecture\DependencyInjection\ActionInjectionToConstructorInjectionRector\ActionInjectionToConstructorInjectionRectorTest
@ -27,16 +27,16 @@ final class ActionInjectionToConstructorInjectionRector extends AbstractRector
private $variablesToPropertyFetchCollection;
/**
* @var AnalyzedApplicationContainerInterface
* @var ServiceMapProvider
*/
private $analyzedApplicationContainer;
private $applicationServiceMapProvider;
public function __construct(
VariablesToPropertyFetchCollection $variablesToPropertyFetchCollection,
AnalyzedApplicationContainerInterface $analyzedApplicationContainer
ServiceMapProvider $applicationServiceMapProvider
) {
$this->variablesToPropertyFetchCollection = $variablesToPropertyFetchCollection;
$this->analyzedApplicationContainer = $analyzedApplicationContainer;
$this->applicationServiceMapProvider = $applicationServiceMapProvider;
}
public function getDefinition(): RectorDefinition
@ -49,7 +49,7 @@ final class SomeController
public function default(ProductRepository $productRepository)
{
$products = $productRepository->fetchAll();
}
}
}
PHP
,
@ -64,11 +64,11 @@ final class SomeController
{
$this->productRepository = $productRepository;
}
public function default()
{
$products = $this->productRepository->fetchAll();
}
}
}
PHP
),
@ -135,7 +135,8 @@ PHP
return false;
}
/** @var string $typehint */
return $this->analyzedApplicationContainer->hasService($paramStaticType->getClassName());
$serviceMap = $this->applicationServiceMapProvider->provide();
return $serviceMap->hasService($paramStaticType->getClassName());
}
}

View File

@ -11,19 +11,19 @@ use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\Application\ErrorAndDiffCollector;
use Rector\BetterPhpDocParser\PhpDocNode\JMS\JMSInjectTagValueNode;
use Rector\BetterPhpDocParser\PhpDocNode\PHPDI\PHPDIInjectTagValueNode;
use Rector\Bridge\Contract\AnalyzedApplicationContainerInterface;
use Rector\Exception\NotImplementedException;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\ConfiguredCodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\Symfony\ServiceMapProvider;
use Symplify\SmartFileSystem\SmartFileInfo;
use Throwable;
/**
* @see https://jmsyst.com/bundles/JMSDiExtraBundle/master/annotations#inject
@ -40,11 +40,6 @@ final class InjectAnnotationClassRector extends AbstractRector
JMSInject::class => JMSInjectTagValueNode::class,
];
/**
* @var AnalyzedApplicationContainerInterface
*/
private $analyzedApplicationContainer;
/**
* @var ErrorAndDiffCollector
*/
@ -55,17 +50,22 @@ final class InjectAnnotationClassRector extends AbstractRector
*/
private $annotationClasses = [];
/**
* @var ServiceMapProvider
*/
private $serviceMapProvider;
/**
* @param string[] $annotationClasses
*/
public function __construct(
AnalyzedApplicationContainerInterface $analyzedApplicationContainer,
ServiceMapProvider $serviceMapProvider,
ErrorAndDiffCollector $errorAndDiffCollector,
array $annotationClasses = []
) {
$this->analyzedApplicationContainer = $analyzedApplicationContainer;
$this->errorAndDiffCollector = $errorAndDiffCollector;
$this->annotationClasses = $annotationClasses;
$this->serviceMapProvider = $serviceMapProvider;
}
public function getDefinition(): RectorDefinition
@ -199,14 +199,19 @@ PHP
private function resolveJMSDIInjectType(Node $node, JMSInjectTagValueNode $jmsInjectTagValueNode): Type
{
$serviceMap = $this->serviceMapProvider->provide();
$serviceName = $jmsInjectTagValueNode->getServiceName();
if ($serviceName) {
try {
if ($this->analyzedApplicationContainer->hasService($serviceName)) {
return $this->analyzedApplicationContainer->getTypeForName($serviceName);
if (class_exists($serviceName)) {
return new ObjectType($serviceName);
}
if ($serviceMap->hasService($serviceName)) {
$serviceType = $serviceMap->getServiceType($serviceName);
if ($serviceType !== null) {
return $serviceType;
}
} catch (Throwable $throwable) {
// resolve later in errorAndDiffCollector if @var not found
}
}

View File

@ -9,7 +9,6 @@ use Rector\Configuration\Option;
use Rector\Rector\Architecture\DependencyInjection\ActionInjectionToConstructorInjectionRector;
use Rector\Rector\Architecture\DependencyInjection\ReplaceVariableByPropertyFetchRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\Tests\Rector\Architecture\DependencyInjection\ActionInjectionToConstructorInjectionRector\Source\SomeKernelClass;
final class ActionInjectionToConstructorInjectionRectorTest extends AbstractRectorTestCase
{
@ -18,7 +17,8 @@ final class ActionInjectionToConstructorInjectionRectorTest extends AbstractRect
*/
public function test(string $file): void
{
$this->setParameter(Option::KERNEL_CLASS_PARAMETER, SomeKernelClass::class);
$this->setParameter(Option::SYMFONY_CONTAINER_XML_PATH_PARAMETER, __DIR__ . '/xml/services.xml');
$this->doTestFile($file);
}

View File

@ -1,36 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Rector\Architecture\DependencyInjection\ActionInjectionToConstructorInjectionRector\Source;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel;
final class SomeKernelClass extends Kernel
{
public function registerBundles(): iterable
{
return [];
}
public function registerContainerConfiguration(LoaderInterface $loader): void
{
}
protected function build(ContainerBuilder $containerBuilder): void
{
$containerBuilder->register(ProductRepository::class);
}
public function getCacheDir()
{
return sys_get_temp_dir() . '/_tmp';
}
public function getLogDir()
{
return sys_get_temp_dir() . '/_tmp';
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="Rector\Tests\Rector\Architecture\DependencyInjection\ActionInjectionToConstructorInjectionRector\Source\ProductRepository"></service>
</services>
</container>

View File

@ -9,7 +9,6 @@ use Iterator;
use JMS\DiExtraBundle\Annotation\Inject;
use Rector\Configuration\Option;
use Rector\Rector\Property\InjectAnnotationClassRector;
use Rector\Symfony\Tests\FrameworkBundle\AbstractToConstructorInjectionRectorSource\SomeKernelClass;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class InjectAnnotationClassRectorTest extends AbstractRectorTestCase
@ -19,7 +18,11 @@ final class InjectAnnotationClassRectorTest extends AbstractRectorTestCase
*/
public function test(string $file): void
{
$this->setParameter(Option::KERNEL_CLASS_PARAMETER, SomeKernelClass::class);
$this->setParameter(
Option::SYMFONY_CONTAINER_XML_PATH_PARAMETER,
__DIR__ . '/../../../../packages/Symfony/tests/Rector/FrameworkBundle/GetToConstructorInjectionRector/xml/services.xml'
);
$this->doTestFile($file);
}