From 5d47f63ed17d4d1a40ce37541c86870c37b54674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Nowak?= Date: Tue, 13 Aug 2024 11:37:53 +0200 Subject: [PATCH] coding standards fix --- src/Symfony/Message.php | 60 ++--- src/Symfony/MessageMap.php | 52 ++-- src/Symfony/MessageMapFactory.php | 227 +++++++++--------- src/Symfony/Service.php | 15 +- src/Symfony/ServiceDefinition.php | 3 +- src/Symfony/ServiceTag.php | 46 ++-- src/Symfony/ServiceTagDefinition.php | 8 +- src/Symfony/XmlServiceMapFactory.php | 16 +- ...rHandleTraitDynamicReturnTypeExtension.php | 134 ++++++----- tests/Type/Symfony/ExtensionTest.php | 2 +- 10 files changed, 291 insertions(+), 272 deletions(-) diff --git a/src/Symfony/Message.php b/src/Symfony/Message.php index c53958af..dac5b23c 100644 --- a/src/Symfony/Message.php +++ b/src/Symfony/Message.php @@ -1,35 +1,37 @@ -class = $class; - $this->returnTypes = $returnTypes; - } - - public function getClass(): string - { - return $this->class; - } - - public function getReturnTypes(): array - { - return $this->returnTypes; - } - - public function countReturnTypes(): int - { - return count($this->returnTypes); - } + + /** @var string */ + private $class; + + /** @var array */ + private $returnTypes; + + public function __construct(string $class, array $returnTypes) + { + $this->class = $class; + $this->returnTypes = $returnTypes; + } + + public function getClass(): string + { + return $this->class; + } + + public function getReturnTypes(): array + { + return $this->returnTypes; + } + + public function countReturnTypes(): int + { + return count($this->returnTypes); + } + } diff --git a/src/Symfony/MessageMap.php b/src/Symfony/MessageMap.php index eaeaadee..a96f3521 100644 --- a/src/Symfony/MessageMap.php +++ b/src/Symfony/MessageMap.php @@ -1,31 +1,33 @@ -messages[$message->getClass()] = $message; - } - } - - public function getMessageForClass(string $class): ?Message - { - return $this->messages[$class] ?? null; - } - - public function hasMessageForClass(string $class): bool - { - return array_key_exists($class, $this->messages); - } + + /** @var Message[] */ + private $messages = []; + + /** + * @param Message[] $messages + */ + public function __construct(array $messages) + { + foreach ($messages as $message) { + $this->messages[$message->getClass()] = $message; + } + } + + public function getMessageForClass(string $class): ?Message + { + return $this->messages[$class] ?? null; + } + + public function hasMessageForClass(string $class): bool + { + return array_key_exists($class, $this->messages); + } + } diff --git a/src/Symfony/MessageMapFactory.php b/src/Symfony/MessageMapFactory.php index e774c34b..896f4674 100644 --- a/src/Symfony/MessageMapFactory.php +++ b/src/Symfony/MessageMapFactory.php @@ -1,4 +1,4 @@ -serviceMap = $symfonyServiceMap; - $this->reflectionProvider = $reflectionProvider; - } - - public function create(): MessageMap - { - $returnTypesMap = []; - - foreach ($this->serviceMap->getServices() as $service) { - $serviceClass = $service->getClass(); - - // todo handle abstract/parent services somehow? - if (is_null($serviceClass)) { - continue; - } - - foreach ($service->getTags() as $tag) { - // todo could there be more tags with the same name for the same service? - // todo check if message handler tag name is constant or configurable - if ($tag->getName() !== 'messenger.message_handler') { - continue; - } - - $tagAttributes = $tag->getAttributes(); - $reflectionClass = $this->reflectionProvider->getClass($serviceClass); - - if (isset($tagAttributes['handles'])) { - $handles = isset($tag['method']) ? [$tag['handles'] => $tag['method']] : [$tag['handles']]; - } else { - $handles = $this->guessHandledMessages($reflectionClass); - } - - foreach ($handles as $messageClassName => $options) { - if (\is_int($messageClassName) && \is_string($options)) { - $messageClassName = $options; - $options = []; - } - - $options['method'] = $options['method'] ?? '__invoke'; - - $methodReflection = $reflectionClass->getNativeMethod($options['method']); - $variant = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants()); - - $returnTypesMap[$messageClassName][] = $variant->getReturnType(); - } - } - } - - $messages = []; - foreach ($returnTypesMap as $messageClassName => $returnTypes) { - $messages[] = new Message($messageClassName, $returnTypes); - } - - return new MessageMap($messages); - } - - private function guessHandledMessages(ClassReflection $reflectionClass): iterable - { - if ($reflectionClass->implementsInterface(MessageSubscriberInterface::class)) { - // todo handle different return formats - return $reflectionClass->getName()::getHandledMessages(); - } - - // todo handle if doesn't exists - $methodReflection = $reflectionClass->getNativeMethod('__invoke'); - - $variant = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants()); - $parameters = $variant->getParameters(); - - if (1 !== count($parameters)) { - // todo handle error - throw new \RuntimeException('invalid handler'); - } - - $type = $parameters[0]->getType(); - - if ($type instanceof UnionType) { - $types = []; - foreach ($type->getTypes() as $type) { - if (!$type instanceof ObjectType) { - // todo handle error - throw new \RuntimeException('invalid handler'); - } - - $types[] = $type->getClassName(); - } - - if ($types) { - return $types; - } - - // todo handle error - throw new \RuntimeException('invalid handler'); - } - - if (!$type instanceof ObjectType) { - throw new \RuntimeException('invalid handler'); - } - - return [$type->getClassName()]; - } + + /** @var ReflectionProvider */ + private $reflectionProvider; + + /** @var ServiceMap */ + private $serviceMap; + + public function __construct(ServiceMap $symfonyServiceMap, ReflectionProvider $reflectionProvider) + { + $this->serviceMap = $symfonyServiceMap; + $this->reflectionProvider = $reflectionProvider; + } + + public function create(): MessageMap + { + $returnTypesMap = []; + + foreach ($this->serviceMap->getServices() as $service) { + $serviceClass = $service->getClass(); + + // todo handle abstract/parent services somehow? + if (is_null($serviceClass)) { + continue; + } + + foreach ($service->getTags() as $tag) { + // todo could there be more tags with the same name for the same service? + // todo check if message handler tag name is constant or configurable + if ($tag->getName() !== 'messenger.message_handler') { + continue; + } + + $tagAttributes = $tag->getAttributes(); + $reflectionClass = $this->reflectionProvider->getClass($serviceClass); + + if (isset($tagAttributes['handles'])) { + $handles = isset($tag['method']) ? [$tag['handles'] => $tag['method']] : [$tag['handles']]; + } else { + $handles = $this->guessHandledMessages($reflectionClass); + } + + foreach ($handles as $messageClassName => $options) { + if (is_int($messageClassName) && is_string($options)) { + $messageClassName = $options; + $options = []; + } + + $options['method'] = $options['method'] ?? '__invoke'; + + $methodReflection = $reflectionClass->getNativeMethod($options['method']); + $variant = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants()); + + $returnTypesMap[$messageClassName][] = $variant->getReturnType(); + } + } + } + + $messages = []; + foreach ($returnTypesMap as $messageClassName => $returnTypes) { + $messages[] = new Message($messageClassName, $returnTypes); + } + + return new MessageMap($messages); + } + + private function guessHandledMessages(ClassReflection $reflectionClass): iterable + { + if ($reflectionClass->implementsInterface(MessageSubscriberInterface::class)) { + // todo handle different return formats + return $reflectionClass->getName()::getHandledMessages(); + } + + // todo handle if doesn't exists + $methodReflection = $reflectionClass->getNativeMethod('__invoke'); + + $variant = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants()); + $parameters = $variant->getParameters(); + + if (count($parameters) !== 1) { + // todo handle error + throw new RuntimeException('invalid handler'); + } + + $type = $parameters[0]->getType(); + + if ($type instanceof UnionType) { + $types = []; + foreach ($type->getTypes() as $type) { + if (!$type instanceof ObjectType) { + // todo handle error + throw new RuntimeException('invalid handler'); + } + + $types[] = $type->getClassName(); + } + + if ($types) { + return $types; + } + + // todo handle error + throw new RuntimeException('invalid handler'); + } + + if (!$type instanceof ObjectType) { + throw new RuntimeException('invalid handler'); + } + + return [$type->getClassName()]; + } + } diff --git a/src/Symfony/Service.php b/src/Symfony/Service.php index 5d72c3e4..09152d58 100644 --- a/src/Symfony/Service.php +++ b/src/Symfony/Service.php @@ -20,8 +20,8 @@ final class Service implements ServiceDefinition /** @var string|null */ private $alias; - /** @var array */ - private $tags; + /** @var array */ + private $tags; public function __construct( string $id, @@ -29,7 +29,7 @@ public function __construct( bool $public, bool $synthetic, ?string $alias, - array $tags = [] + array $tags = [] ) { $this->id = $id; @@ -65,8 +65,9 @@ public function getAlias(): ?string return $this->alias; } - public function getTags(): array - { - return $this->tags; - } + public function getTags(): array + { + return $this->tags; + } + } diff --git a/src/Symfony/ServiceDefinition.php b/src/Symfony/ServiceDefinition.php index d509b8dc..c943e2a8 100644 --- a/src/Symfony/ServiceDefinition.php +++ b/src/Symfony/ServiceDefinition.php @@ -15,5 +15,6 @@ public function isSynthetic(): bool; public function getAlias(): ?string; - public function getTags(): array; + public function getTags(): array; + } diff --git a/src/Symfony/ServiceTag.php b/src/Symfony/ServiceTag.php index 880a72cd..c02be7c5 100644 --- a/src/Symfony/ServiceTag.php +++ b/src/Symfony/ServiceTag.php @@ -1,28 +1,30 @@ -name = $name; - $this->attributes = $attributes; - } - - public function getName(): string - { - return $this->name; - } - - public function getAttributes(): array - { - return $this->attributes; - } + + /** @var string */ + private $name; + + /** @var array */ + private $attributes; + + public function __construct(string $name, array $attributes = []) + { + $this->name = $name; + $this->attributes = $attributes; + } + + public function getName(): string + { + return $this->name; + } + + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Symfony/ServiceTagDefinition.php b/src/Symfony/ServiceTagDefinition.php index f2154db4..19bd84a3 100644 --- a/src/Symfony/ServiceTagDefinition.php +++ b/src/Symfony/ServiceTagDefinition.php @@ -1,10 +1,12 @@ -tag as $tag) { - $tagAttrs = ((array) $tag->attributes())['@attributes'] ?? []; - $tagName = $tagAttrs['name']; - unset($tagAttrs['name']); + $serviceTags = []; + foreach ($def->tag as $tag) { + $tagAttrs = ((array) $tag->attributes())['@attributes'] ?? []; + $tagName = $tagAttrs['name']; + unset($tagAttrs['name']); - $serviceTags[] = new ServiceTag($tagName, $tagAttrs); - } + $serviceTags[] = new ServiceTag($tagName, $tagAttrs); + } $service = new Service( $this->cleanServiceId((string) $attrs->id), @@ -62,7 +62,7 @@ public function create(): ServiceMap isset($attrs->public) && (string) $attrs->public === 'true', isset($attrs->synthetic) && (string) $attrs->synthetic === 'true', isset($attrs->alias) ? $this->cleanServiceId((string) $attrs->alias) : null, - $serviceTags + $serviceTags ); if ($service->getAlias() !== null) { diff --git a/src/Type/Symfony/MessengerHandleTraitDynamicReturnTypeExtension.php b/src/Type/Symfony/MessengerHandleTraitDynamicReturnTypeExtension.php index 6462a14c..a0f06a5a 100644 --- a/src/Type/Symfony/MessengerHandleTraitDynamicReturnTypeExtension.php +++ b/src/Type/Symfony/MessengerHandleTraitDynamicReturnTypeExtension.php @@ -1,4 +1,4 @@ -messageMapFactory = $symfonyMessageMapFactory; - } - - public function getClass(): string - { - // todo traits are not supported yet in phpstan for this extension (https://github.com/phpstan/phpstan/issues/5761) - // return HandleTrait::class; - - // todo or make it configurable with passing concrete classes names to extension config - // todo or use reflection somehow to get all classes that use HandleTrait and configure it dynamically - - // todo temporarily hardcoded test class here - return HandleTraitClass::class; - } - - public function isMethodSupported(MethodReflection $methodReflection): bool - { - // todo additional reflection checker that it comes only from trait? - return $methodReflection->getName() === 'handle'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type - { - // todo handle different cases: - // - [X] regular message classes - // - [ ] interfaces for message classes - // - [ ] many handlers for one message? it would throw exception in HandleTrait anyway - // - [x] many messages for one handler - // - [partially] cover MessageSubscriberInterface - // - [partially] custom method names for handlers (different than default "__invoke" magic method) - // - [] read SF doc to determine any other cases to covers - - $arg = $methodCall->getArgs()[0]->value; - $argType = $scope->getType($arg); - - if ($argType instanceof ObjectType) { - $messageMap = $this->getMessageMap(); - if ($messageMap->hasMessageForClass($argType->getClassName())) { - $message = $messageMap->getMessageForClass($argType->getClassName()); - - if (1 === $message->countReturnTypes()) { - return $message->getReturnTypes()[0]; - } - } - } - - return null; - } - - private function getMessageMap(): MessageMap - { - if (!$this->messageMap) { - $this->messageMap = $this->messageMapFactory->create(); - } - - return $this->messageMap; - } + + /** @var MessageMapFactory */ + private $messageMapFactory; + + /** @var MessageMap */ + private $messageMap; + + public function __construct(MessageMapFactory $symfonyMessageMapFactory) + { + $this->messageMapFactory = $symfonyMessageMapFactory; + } + + public function getClass(): string + { + // todo traits are not supported yet in phpstan for this extension (https://github.com/phpstan/phpstan/issues/5761) + // return HandleTrait::class; + + // todo or make it configurable with passing concrete classes names to extension config + // todo or use reflection somehow to get all classes that use HandleTrait and configure it dynamically + + // todo temporarily hardcoded test class here + return HandleTraitClass::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + // todo additional reflection checker that it comes only from trait? + return $methodReflection->getName() === 'handle'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + // todo handle different cases: + // - [X] regular message classes + // - [ ] interfaces for message classes + // - [ ] many handlers for one message? it would throw exception in HandleTrait anyway + // - [x] many messages for one handler + // - [partially] cover MessageSubscriberInterface + // - [partially] custom method names for handlers (different than default "__invoke" magic method) + // - [] read SF doc to determine any other cases to covers + + $arg = $methodCall->getArgs()[0]->value; + $argType = $scope->getType($arg); + + if ($argType instanceof ObjectType) { + $messageMap = $this->getMessageMap(); + if ($messageMap->hasMessageForClass($argType->getClassName())) { + $message = $messageMap->getMessageForClass($argType->getClassName()); + + if ($message->countReturnTypes() === 1) { + return $message->getReturnTypes()[0]; + } + } + } + + return null; + } + + private function getMessageMap(): MessageMap + { + if (!$this->messageMap) { + $this->messageMap = $this->messageMapFactory->create(); + } + + return $this->messageMap; + } + } diff --git a/tests/Type/Symfony/ExtensionTest.php b/tests/Type/Symfony/ExtensionTest.php index 85bbb140..40420be0 100644 --- a/tests/Type/Symfony/ExtensionTest.php +++ b/tests/Type/Symfony/ExtensionTest.php @@ -14,7 +14,7 @@ class ExtensionTest extends TypeInferenceTestCase /** @return mixed[] */ public function dataFileAsserts(): iterable { - yield from $this->gatherAssertTypes(__DIR__ . '/data/messenger_handle_trait.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/messenger_handle_trait.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/envelope_all.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/header_bag_get.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/response_header_bag_get_cookies.php');