Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit fa54bd1

Browse files
Merge remote-tracking branch 'origin/1.4.x' into 2.0.x
2 parents 1ef4dce + dd1aaa7 commit fa54bd1

12 files changed

+482
-1
lines changed

‎extension.neon‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ services:
122122
-
123123
factory: @symfony.parameterMapFactory::create()
124124

125+
# message map
126+
symfony.messageMapFactory:
127+
class: PHPStan\Symfony\MessageMapFactory
128+
factory: PHPStan\Symfony\MessageMapFactory
129+
-
130+
factory: @symfony.messageMapFactory::create()
131+
125132
# ControllerTrait::get()/has() return type
126133
-
127134
factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface)
@@ -185,6 +192,11 @@ services:
185192
factory: PHPStan\Type\Symfony\EnvelopeReturnTypeExtension
186193
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
187194

195+
# Messenger HandleTrait::handle() return type
196+
-
197+
class: PHPStan\Type\Symfony\MessengerHandleTraitReturnTypeExtension
198+
tags: [phpstan.broker.expressionTypeResolverExtension]
199+
188200
# InputInterface::getArgument() return type
189201
-
190202
factory: PHPStan\Type\Symfony\InputInterfaceGetArgumentDynamicReturnTypeExtension

‎src/Symfony/MessageMap.php‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Symfony;
4+
5+
use PHPStan\Type\Type;
6+
7+
final class MessageMap
8+
{
9+
10+
/** @var array<string, Type> */
11+
private $messageMap;
12+
13+
/** @param array<string, Type> $messageMap */
14+
public function __construct(array $messageMap)
15+
{
16+
$this->messageMap = $messageMap;
17+
}
18+
19+
public function getTypeForClass(string $class): ?Type
20+
{
21+
return $this->messageMap[$class] ?? null;
22+
}
23+
24+
}

‎src/Symfony/MessageMapFactory.php‎

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Symfony;
4+
5+
use PHPStan\Reflection\ClassReflection;
6+
use PHPStan\Reflection\ReflectionProvider;
7+
use Symfony\Component\Messenger\Handler\MessageSubscriberInterface;
8+
use function class_exists;
9+
use function count;
10+
use function is_array;
11+
use function is_int;
12+
use function is_string;
13+
14+
final class MessageMapFactory
15+
{
16+
17+
private const MESSENGER_HANDLER_TAG = 'messenger.message_handler';
18+
private const DEFAULT_HANDLER_METHOD = '__invoke';
19+
20+
/** @var ReflectionProvider */
21+
private $reflectionProvider;
22+
23+
/** @var ServiceMap */
24+
private $serviceMap;
25+
26+
public function __construct(ServiceMap $symfonyServiceMap, ReflectionProvider $reflectionProvider)
27+
{
28+
$this->serviceMap = $symfonyServiceMap;
29+
$this->reflectionProvider = $reflectionProvider;
30+
}
31+
32+
public function create(): MessageMap
33+
{
34+
$returnTypesMap = [];
35+
36+
foreach ($this->serviceMap->getServices() as $service) {
37+
$serviceClass = $service->getClass();
38+
39+
if ($serviceClass === null) {
40+
continue;
41+
}
42+
43+
foreach ($service->getTags() as $tag) {
44+
if ($tag->getName() !== self::MESSENGER_HANDLER_TAG) {
45+
continue;
46+
}
47+
48+
if (!$this->reflectionProvider->hasClass($serviceClass)) {
49+
continue;
50+
}
51+
52+
$reflectionClass = $this->reflectionProvider->getClass($serviceClass);
53+
54+
/** @var array{handles?: class-string, method?: string} $tagAttributes */
55+
$tagAttributes = $tag->getAttributes();
56+
57+
if (isset($tagAttributes['handles'])) {
58+
$handles = [$tagAttributes['handles'] => ['method' => $tagAttributes['method'] ?? self::DEFAULT_HANDLER_METHOD]];
59+
} else {
60+
$handles = $this->guessHandledMessages($reflectionClass);
61+
}
62+
63+
foreach ($handles as $messageClassName => $options) {
64+
$methodName = $options['method'] ?? self::DEFAULT_HANDLER_METHOD;
65+
66+
if (!$reflectionClass->hasNativeMethod($methodName)) {
67+
continue;
68+
}
69+
70+
$methodReflection = $reflectionClass->getNativeMethod($methodName);
71+
72+
foreach ($methodReflection->getVariants() as $variant) {
73+
$returnTypesMap[$messageClassName][] = $variant->getReturnType();
74+
}
75+
}
76+
}
77+
}
78+
79+
$messageMap = [];
80+
foreach ($returnTypesMap as $messageClassName => $returnTypes) {
81+
if (count($returnTypes) !== 1) {
82+
continue;
83+
}
84+
85+
$messageMap[$messageClassName] = $returnTypes[0];
86+
}
87+
88+
return new MessageMap($messageMap);
89+
}
90+
91+
/** @return iterable<string, array<string, string>> */
92+
private function guessHandledMessages(ClassReflection $reflectionClass): iterable
93+
{
94+
if ($reflectionClass->implementsInterface(MessageSubscriberInterface::class)) {
95+
$className = $reflectionClass->getName();
96+
97+
foreach ($className::getHandledMessages() as $index => $value) {
98+
$containOptions = self::containOptions($index, $value);
99+
if ($containOptions === true) {
100+
yield $index => $value;
101+
} elseif ($containOptions === false) {
102+
yield $value => ['method' => self::DEFAULT_HANDLER_METHOD];
103+
}
104+
}
105+
106+
return;
107+
}
108+
109+
if (!$reflectionClass->hasNativeMethod(self::DEFAULT_HANDLER_METHOD)) {
110+
return;
111+
}
112+
113+
$methodReflection = $reflectionClass->getNativeMethod(self::DEFAULT_HANDLER_METHOD);
114+
115+
$variants = $methodReflection->getVariants();
116+
if (count($variants) !== 1) {
117+
return;
118+
}
119+
120+
$parameters = $variants[0]->getParameters();
121+
122+
if (count($parameters) !== 1) {
123+
return;
124+
}
125+
126+
$classNames = $parameters[0]->getType()->getObjectClassNames();
127+
128+
if (count($classNames) !== 1) {
129+
return;
130+
}
131+
132+
yield $classNames[0] => ['method' => self::DEFAULT_HANDLER_METHOD];
133+
}
134+
135+
/**
136+
* @param mixed $index
137+
* @param mixed $value
138+
* @phpstan-assert-if-true =class-string $index
139+
* @phpstan-assert-if-true =array<string, mixed> $value
140+
* @phpstan-assert-if-false =int $index
141+
* @phpstan-assert-if-false =class-string $value
142+
*/
143+
private static function containOptions($index, $value): ?bool
144+
{
145+
if (is_string($index) && class_exists($index) && is_array($value)) {
146+
return true;
147+
} elseif (is_int($index) && is_string($value) && class_exists($value)) {
148+
return false;
149+
}
150+
151+
return null;
152+
}
153+
154+
}

‎src/Symfony/Service.php‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,25 @@ final class Service implements ServiceDefinition
1515

1616
private ?string $alias = null;
1717

18+
/** @var ServiceTag[] */
19+
private $tags;
20+
21+
/** @param ServiceTag[] $tags */
1822
public function __construct(
1923
string $id,
2024
?string $class,
2125
bool $public,
2226
bool $synthetic,
23-
?string $alias
27+
?string $alias,
28+
array $tags = []
2429
)
2530
{
2631
$this->id = $id;
2732
$this->class = $class;
2833
$this->public = $public;
2934
$this->synthetic = $synthetic;
3035
$this->alias = $alias;
36+
$this->tags = $tags;
3137
}
3238

3339
public function getId(): string
@@ -55,4 +61,9 @@ public function getAlias(): ?string
5561
return $this->alias;
5662
}
5763

64+
public function getTags(): array
65+
{
66+
return $this->tags;
67+
}
68+
5869
}

‎src/Symfony/ServiceDefinition.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@ public function isSynthetic(): bool;
1818

1919
public function getAlias(): ?string;
2020

21+
/** @return ServiceTag[] */
22+
public function getTags(): array;
23+
2124
}

‎src/Symfony/ServiceTag.php‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Symfony;
4+
5+
final class ServiceTag implements ServiceTagDefinition
6+
{
7+
8+
/** @var string */
9+
private $name;
10+
11+
/** @var array<string, string> */
12+
private $attributes;
13+
14+
/** @param array<string, string> $attributes */
15+
public function __construct(string $name, array $attributes = [])
16+
{
17+
$this->name = $name;
18+
$this->attributes = $attributes;
19+
}
20+
21+
public function getName(): string
22+
{
23+
return $this->name;
24+
}
25+
26+
public function getAttributes(): array
27+
{
28+
return $this->attributes;
29+
}
30+
31+
}

‎src/Symfony/ServiceTagDefinition.php‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Symfony;
4+
5+
interface ServiceTagDefinition
6+
{
7+
8+
public function getName(): string;
9+
10+
/** @return array<string, string> */
11+
public function getAttributes(): array;
12+
13+
}

‎src/Symfony/XmlServiceMapFactory.php‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,22 @@ public function create(): ServiceMap
4646
continue;
4747
}
4848

49+
$serviceTags = [];
50+
foreach ($def->tag as $tag) {
51+
$tagAttrs = ((array) $tag->attributes())['@attributes'] ?? [];
52+
$tagName = $tagAttrs['name'];
53+
unset($tagAttrs['name']);
54+
55+
$serviceTags[] = new ServiceTag($tagName, $tagAttrs);
56+
}
57+
4958
$service = new Service(
5059
$this->cleanServiceId((string) $attrs->id),
5160
isset($attrs->class) ? (string) $attrs->class : null,
5261
isset($attrs->public) && (string) $attrs->public === 'true',
5362
isset($attrs->synthetic) && (string) $attrs->synthetic === 'true',
5463
isset($attrs->alias) ? $this->cleanServiceId((string) $attrs->alias) : null,
64+
$serviceTags,
5565
);
5666

5767
if ($service->getAlias() !== null) {

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /