21
21
use PHPStan \PhpDocParser \Parser \PhpDocParser ;
22
22
use PHPStan \PhpDocParser \Parser \TokenIterator ;
23
23
use PHPStan \PhpDocParser \Parser \TypeParser ;
24
- use Symfony \Component \PropertyInfo \PhpStan \NameScopeFactory ;
25
24
use Symfony \Component \PropertyInfo \PropertyTypeExtractorInterface ;
26
- use Symfony \Component \PropertyInfo \Type ;
27
- use Symfony \Component \PropertyInfo \Util \PhpStanTypeHelper ;
25
+ use Symfony \Component \PropertyInfo \Util \BackwardCompatibilityHelper ;
26
+ use Symfony \Component \TypeInfo \Exception \LogicException ;
27
+ use Symfony \Component \TypeInfo \Exception \UnsupportedException ;
28
+ use Symfony \Component \TypeInfo \Type ;
29
+ use Symfony \Component \TypeInfo \TypeContext \TypeContextFactory ;
30
+ use Symfony \Component \TypeInfo \TypeResolver \StringTypeResolver ;
28
31
29
32
/**
30
33
* Extracts data using PHPStan parser.
@@ -39,11 +42,12 @@ final class PhpStanExtractor implements PropertyTypeExtractorInterface, Construc
39
42
40
43
private PhpDocParser $ phpDocParser ;
41
44
private Lexer $ lexer ;
42
- private NameScopeFactory $ nameScopeFactory ;
45
+
46
+ private StringTypeResolver $ stringTypeResolver ;
47
+ private TypeContextFactory $ typeContextFactory ;
43
48
44
49
/** @var array<string, array{PhpDocNode|null, int|null, string|null, string|null}> */
45
50
private array $ docBlocks = [];
46
- private PhpStanTypeHelper $ phpStanTypeHelper ;
47
51
private array $ mutatorPrefixes ;
48
52
private array $ accessorPrefixes ;
49
53
private array $ arrayMutatorPrefixes ;
@@ -63,103 +67,100 @@ public function __construct(array $mutatorPrefixes = null, array $accessorPrefix
63
67
throw new \LogicException (sprintf ('Unable to use the "%s" class as the "phpstan/phpdoc-parser" package is not installed. Try running composer require "phpstan/phpdoc-parser". ' , __CLASS__ ));
64
68
}
65
69
66
- $ this ->phpStanTypeHelper = new PhpStanTypeHelper ();
67
70
$ this ->mutatorPrefixes = $ mutatorPrefixes ?? ReflectionExtractor::$ defaultMutatorPrefixes ;
68
71
$ this ->accessorPrefixes = $ accessorPrefixes ?? ReflectionExtractor::$ defaultAccessorPrefixes ;
69
72
$ this ->arrayMutatorPrefixes = $ arrayMutatorPrefixes ?? ReflectionExtractor::$ defaultArrayMutatorPrefixes ;
70
73
71
74
$ this ->phpDocParser = new PhpDocParser (new TypeParser (new ConstExprParser ()), new ConstExprParser ());
72
75
$ this ->lexer = new Lexer ();
73
- $ this ->nameScopeFactory = new NameScopeFactory ();
76
+ $ this ->stringTypeResolver = new StringTypeResolver ();
77
+ $ this ->typeContextFactory = new TypeContextFactory ($ this ->stringTypeResolver );
74
78
}
75
79
76
- public function getTypes (string $ class , string $ property , array $ context = []): ?array
80
+ public function getType (string $ class , string $ property , array $ context = []): ?Type
77
81
{
82
+ $ backwardCompatible = \func_get_args ()[3 ] ?? false ;
83
+
78
84
/** @var PhpDocNode|null $docNode */
79
85
[$ docNode , $ source , $ prefix , $ declaringClass ] = $ this ->getDocBlock ($ class , $ property );
80
- $ nameScope = $ this -> nameScopeFactory -> create ( $ class , $ declaringClass );
86
+
81
87
if (null === $ docNode ) {
82
88
return null ;
83
89
}
84
90
85
- switch ($ source ) {
86
- case self ::PROPERTY :
87
- $ tag = '@var ' ;
88
- break ;
89
-
90
- case self ::ACCESSOR :
91
- $ tag = '@return ' ;
92
- break ;
91
+ $ typeContext = $ this ->typeContextFactory ->createFromClassName ($ class , $ declaringClass );
93
92
94
- case self ::MUTATOR :
95
- $ tag = '@param ' ;
96
- break ;
97
- }
93
+ $ tag = match ($ source ) {
94
+ self ::PROPERTY => '@var ' ,
95
+ self ::ACCESSOR => '@return ' ,
96
+ self ::MUTATOR => '@param ' ,
97
+ default => null ,
98
+ };
98
99
99
- $ parentClass = null ;
100
100
$ types = [];
101
+
101
102
foreach ($ docNode ->getTagsByName ($ tag ) as $ tagDocNode ) {
102
103
if ($ tagDocNode ->value instanceof InvalidTagValueNode) {
103
104
continue ;
104
105
}
105
106
106
- if (
107
- $ tagDocNode ->value instanceof ParamTagValueNode
108
- && null === $ prefix
109
- && $ tagDocNode ->value ->parameterName !== '$ ' .$ property
110
- ) {
107
+ if ($ tagDocNode ->value instanceof ParamTagValueNode && null === $ prefix && $ tagDocNode ->value ->parameterName !== '$ ' .$ property ) {
111
108
continue ;
112
109
}
113
110
114
- foreach ($ this ->phpStanTypeHelper ->getTypes ($ tagDocNode ->value , $ nameScope ) as $ type ) {
115
- switch ($ type ->getClassName ()) {
116
- case 'self ' :
117
- case 'static ' :
118
- $ resolvedClass = $ class ;
119
- break ;
120
-
121
- case 'parent ' :
122
- if (false !== $ resolvedClass = $ parentClass ??= get_parent_class ($ class )) {
123
- break ;
124
- }
125
- // no break
126
-
127
- default :
128
- $ types [] = $ type ;
129
- continue 2 ;
111
+ try {
112
+ $ types [] = $ this ->stringTypeResolver ->resolve ((string ) $ tagDocNode ->value ->type , $ typeContext , $ backwardCompatible );
113
+ } catch (UnsupportedException ) {
114
+ // BC layer to handle "void" type in "getTypes"
115
+ if ('void ' === (string ) $ tagDocNode ->value ->type && $ backwardCompatible ) {
116
+ return Type::template ('void ' );
117
+ }
118
+ } catch (LogicException $ e ) {
119
+ // BC layer to handle "parent" type without existing parent
120
+ if ('parent ' === (string ) $ tagDocNode ->value ->type && $ backwardCompatible ) {
121
+ return Type::object ('parent ' );
130
122
}
131
123
132
- $ types [] = new Type (Type:: BUILTIN_TYPE_OBJECT , $ type -> isNullable (), $ resolvedClass , $ type -> isCollection (), $ type -> getCollectionKeyTypes (), $ type -> getCollectionValueTypes ()) ;
124
+ throw $ e ;
133
125
}
134
126
}
135
127
136
- if (! isset ( $ types [0 ])) {
128
+ if (null === ( $ type = $ types [0 ] ?? null )) {
137
129
return null ;
138
130
}
139
131
140
- if (!\in_array ($ prefix , $ this ->arrayMutatorPrefixes , true )) {
141
- return $ types ;
132
+ if (!\in_array ($ prefix , $ this ->arrayMutatorPrefixes )) {
133
+ return $ type ;
142
134
}
143
135
144
- return [ new Type (Type:: BUILTIN_TYPE_ARRAY , false , null , true , new Type (Type:: BUILTIN_TYPE_INT ), $ types [ 0 ])] ;
136
+ return Type:: list ( $ type ) ;
145
137
}
146
138
147
- public function getTypesFromConstructor (string $ class , string $ property ): ?array
139
+ public function getTypes (string $ class , string $ property , array $ context = []): ?array
140
+ {
141
+ trigger_deprecation ('symfony/property-info ' , '7.1 ' , 'The "%s()" method is deprecated. Use "%s::getType()" instead. ' , __METHOD__ , self ::class);
142
+
143
+ return BackwardCompatibilityHelper::convertTypeToLegacyTypes ($ this ->getType ($ class , $ property , $ context , true ));
144
+ }
145
+
146
+ public function getTypeFromConstructor (string $ class , string $ property ): ?Type
148
147
{
148
+ $ backwardCompatible = \func_get_args ()[2 ] ?? false ;
149
+
149
150
if (null === $ tagDocNode = $ this ->getDocBlockFromConstructor ($ class , $ property )) {
150
151
return null ;
151
152
}
152
153
153
- $ types = [];
154
- foreach ($ this ->phpStanTypeHelper ->getTypes ($ tagDocNode , $ this ->nameScopeFactory ->create ($ class )) as $ type ) {
155
- $ types [] = $ type ;
156
- }
154
+ $ typeContext = $ this ->typeContextFactory ->createFromClassName ($ class );
157
155
158
- if (!isset ($ types [0 ])) {
159
- return null ;
160
- }
156
+ return $ this ->stringTypeResolver ->resolve ((string ) $ tagDocNode ->type , $ typeContext , $ backwardCompatible );
157
+ }
158
+
159
+ public function getTypesFromConstructor (string $ class , string $ property ): ?array
160
+ {
161
+ trigger_deprecation ('symfony/property-info ' , '7.1 ' , 'The "%s()" method is deprecated. Use "%s::getTypeFromConstructor()" instead. ' , __METHOD__ , self ::class);
161
162
162
- return $ types ;
163
+ return BackwardCompatibilityHelper:: convertTypeToLegacyTypes ( $ this -> getTypeFromConstructor ( $ class , $ property , true )) ;
163
164
}
164
165
165
166
private function getDocBlockFromConstructor (string $ class , string $ property ): ?ParamTagValueNode
0 commit comments