88use mysqli_result ;
99use mysqli_sql_exception ;
1010use PHPStan \ShouldNotHappenException ;
11- use PHPStan \Type \Accessory \AccessoryNumericStringType ;
1211use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
1312use PHPStan \Type \Constant \ConstantIntegerType ;
1413use PHPStan \Type \Constant \ConstantStringType ;
15- use PHPStan \Type \FloatType ;
16- use PHPStan \Type \IntegerType ;
17- use PHPStan \Type \IntersectionType ;
18- use PHPStan \Type \MixedType ;
19- use PHPStan \Type \StringType ;
2014use PHPStan \Type \Type ;
21- use PHPStan \Type \TypeCombinator ;
22- use PHPStan \Type \UnionType ;
2315use staabm \PHPStanDba \Error ;
24- use staabm \PHPStanDba \Types \MysqlIntegerRanges ;
16+ use staabm \PHPStanDba \TypeMapping \MysqliTypeMapper ;
17+ use staabm \PHPStanDba \TypeMapping \TypeMapper ;
2518
2619final class MysqliQueryReflector implements QueryReflector
2720{
@@ -35,24 +28,12 @@ final class MysqliQueryReflector implements QueryReflector
3528
3629 private const MAX_CACHE_SIZE = 50 ;
3730
38- /**
39- * @var mysqli
40- */
41- private $ db ;
31+ private mysqli $ db ;
4232
43- /**
44- * @var array<string, mysqli_sql_exception|list<object>|null>
45- */
46- private $ cache = [];
33+ /** @var array<string, mysqli_sql_exception|list<object>|null> */
34+ private array $ cache = [];
4735
48- /**
49- * @var array<int, string>
50- */
51- private $ nativeTypes ;
52- /**
53- * @var array<int, string>
54- */
55- private $ nativeFlags ;
36+ private TypeMapper $ typeMapper ;
5637
5738 public function __construct (mysqli $ mysqli )
5839 {
@@ -62,29 +43,7 @@ public function __construct(mysqli $mysqli)
6243 // enable exception throwing on php <8.1
6344 mysqli_report (\MYSQLI_REPORT_ERROR | \MYSQLI_REPORT_STRICT );
6445
65- $ this ->nativeTypes = [];
66- $ this ->nativeFlags = [];
67-
68- $ constants = get_defined_constants (true );
69- foreach ($ constants ['mysqli ' ] as $ c => $ n ) {
70- if (!\is_int ($ n )) {
71- // skip bool constants like MYSQLI_IS_MARIADB
72- continue ;
73- }
74- if (preg_match ('/^MYSQLI_TYPE_(.*)/ ' , $ c , $ m )) {
75- if (!\is_string ($ m [1 ])) {
76- throw new ShouldNotHappenException ();
77- }
78- $ this ->nativeTypes [$ n ] = $ m [1 ];
79- } elseif (preg_match ('/MYSQLI_(.*)_FLAG$/ ' , $ c , $ m )) {
80- if (!\is_string ($ m [1 ])) {
81- throw new ShouldNotHappenException ();
82- }
83- if (!\array_key_exists ($ n , $ this ->nativeFlags )) {
84- $ this ->nativeFlags [$ n ] = $ m [1 ];
85- }
86- }
87- }
46+ $ this ->typeMapper = new MysqliTypeMapper ();
8847 }
8948
9049 public function validateQueryString (string $ queryString ): ?Error
@@ -103,7 +62,10 @@ public function validateQueryString(string $queryString): ?Error
10362 $ message = str_replace (' MariaDB server ' , ' MySQL/MariaDB server ' , $ message );
10463
10564 // to ease debugging, print the error we simulated
106- if (self ::MYSQL_SYNTAX_ERROR_CODE === $ e ->getCode () && QueryReflection::getRuntimeConfiguration ()->isDebugEnabled ()) {
65+ if (
66+ self ::MYSQL_SYNTAX_ERROR_CODE === $ e ->getCode ()
67+ && QueryReflection::getRuntimeConfiguration ()->isDebugEnabled ()
68+ ) {
10769 $ simulatedQuery = QuerySimulation::simulate ($ queryString );
10870 $ message = $ message ."\n\nSimulated query: " .$ simulatedQuery ;
10971 }
@@ -128,20 +90,25 @@ public function getResultType(string $queryString, int $fetchType): ?Type
12890
12991 $ i = 0 ;
13092 foreach ($ result as $ val ) {
131- if (!property_exists ($ val , 'name ' ) || !property_exists ($ val , 'type ' ) || !property_exists ($ val , 'flags ' ) || !property_exists ($ val , 'length ' )) {
93+ if (
94+ !property_exists ($ val , 'name ' )
95+ || !property_exists ($ val , 'type ' )
96+ || !property_exists ($ val , 'flags ' )
97+ || !property_exists ($ val , 'length ' )
98+ ) {
13299 throw new ShouldNotHappenException ();
133100 }
134101
135102 if (self ::FETCH_TYPE_ASSOC === $ fetchType || self ::FETCH_TYPE_BOTH === $ fetchType ) {
136103 $ arrayBuilder ->setOffsetValueType (
137104 new ConstantStringType ($ val ->name ),
138- $ this ->mapMysqlToPHPStanType ($ val ->type , $ val ->flags , $ val ->length )
105+ $ this ->typeMapper -> mapToPHPStanType ($ val ->type , $ val ->flags , $ val ->length )
139106 );
140107 }
141108 if (self ::FETCH_TYPE_NUMERIC === $ fetchType || self ::FETCH_TYPE_BOTH === $ fetchType ) {
142109 $ arrayBuilder ->setOffsetValueType (
143110 new ConstantIntegerType ($ i ),
144- $ this ->mapMysqlToPHPStanType ($ val ->type , $ val ->flags , $ val ->length )
111+ $ this ->typeMapper -> mapToPHPStanType ($ val ->type , $ val ->flags , $ val ->length )
145112 );
146113 }
147114 ++$ i ;
@@ -184,158 +151,4 @@ private function simulateQuery(string $queryString)
184151 return $ this ->cache [$ queryString ] = $ e ;
185152 }
186153 }
187-
188- private function mapMysqlToPHPStanType (int $ mysqlType , int $ mysqlFlags , int $ length ): Type
189- {
190- $ numeric = false ;
191- $ notNull = false ;
192- $ unsigned = false ;
193- $ autoIncrement = false ;
194-
195- foreach ($ this ->flags2txt ($ mysqlFlags ) as $ flag ) {
196- switch ($ flag ) {
197- case 'NUM ' :
198- $ numeric = true ;
199- break ;
200-
201- case 'NOT_NULL ' :
202- $ notNull = true ;
203- break ;
204-
205- case 'AUTO_INCREMENT ' :
206- $ autoIncrement = true ;
207- break ;
208-
209- case 'UNSIGNED ' :
210- $ unsigned = true ;
211- break ;
212-
213- // ???
214- case 'PRI_KEY ' :
215- case 'PART_KEY ' :
216- case 'MULTIPLE_KEY ' :
217- case 'NO_DEFAULT_VALUE ' :
218- }
219- }
220-
221- $ phpstanType = null ;
222- $ mysqlIntegerRanges = new MysqlIntegerRanges ();
223-
224- if ($ numeric ) {
225- if ($ unsigned ) {
226- if (3 === $ length ) { // bool aka tinyint(1)
227- $ phpstanType = $ mysqlIntegerRanges ->unsignedTinyInt ();
228- }
229- if (4 === $ length ) {
230- $ phpstanType = $ mysqlIntegerRanges ->unsignedTinyInt ();
231- }
232- if (5 === $ length ) {
233- $ phpstanType = $ mysqlIntegerRanges ->unsignedSmallInt ();
234- }
235- if (8 === $ length ) {
236- $ phpstanType = $ mysqlIntegerRanges ->unsignedMediumInt ();
237- }
238- if (10 === $ length ) {
239- $ phpstanType = $ mysqlIntegerRanges ->unsignedInt ();
240- }
241- if (20 === $ length ) {
242- $ phpstanType = $ mysqlIntegerRanges ->unsignedBigInt ();
243- }
244- } else {
245- if (1 == $ length ) {
246- $ phpstanType = $ mysqlIntegerRanges ->signedTinyInt ();
247- }
248- if (4 === $ length ) {
249- $ phpstanType = $ mysqlIntegerRanges ->signedTinyInt ();
250- }
251- if (6 === $ length ) {
252- $ phpstanType = $ mysqlIntegerRanges ->signedSmallInt ();
253- }
254- if (9 === $ length ) {
255- $ phpstanType = $ mysqlIntegerRanges ->signedMediumInt ();
256- }
257- if (11 === $ length ) {
258- $ phpstanType = $ mysqlIntegerRanges ->signedInt ();
259- }
260- if (20 === $ length ) {
261- $ phpstanType = $ mysqlIntegerRanges ->signedBigInt ();
262- }
263- if (22 === $ length ) {
264- $ phpstanType = $ mysqlIntegerRanges ->signedBigInt ();
265- }
266- }
267- }
268-
269- if ($ autoIncrement ) {
270- $ phpstanType = $ mysqlIntegerRanges ->unsignedInt ();
271- }
272-
273- if (null === $ phpstanType ) {
274- switch ($ this ->type2txt ($ mysqlType )) {
275- case 'DOUBLE ' :
276- case 'NEWDECIMAL ' :
277- $ phpstanType = new FloatType ();
278- break ;
279- case 'LONGLONG ' :
280- case 'LONG ' :
281- case 'SHORT ' :
282- case 'YEAR ' :
283- case 'BIT ' :
284- case 'INT24 ' :
285- $ phpstanType = new IntegerType ();
286- break ;
287- case 'BLOB ' :
288- case 'CHAR ' :
289- case 'STRING ' :
290- case 'VAR_STRING ' :
291- case 'JSON ' :
292- case 'DATE ' :
293- case 'TIME ' :
294- case 'DATETIME ' :
295- case 'TIMESTAMP ' :
296- $ phpstanType = new StringType ();
297- break ;
298- default :
299- $ phpstanType = new MixedType ();
300- }
301- }
302-
303- if (QueryReflection::getRuntimeConfiguration ()->isStringifyTypes ()) {
304- $ numberType = new UnionType ([new IntegerType (), new FloatType ()]);
305- $ isNumber = $ numberType ->isSuperTypeOf ($ phpstanType )->yes ();
306-
307- if ($ isNumber ) {
308- $ phpstanType = new IntersectionType ([
309- new StringType (),
310- new AccessoryNumericStringType (),
311- ]);
312- }
313- }
314-
315- if (false === $ notNull ) {
316- $ phpstanType = TypeCombinator::addNull ($ phpstanType );
317- }
318-
319- return $ phpstanType ;
320- }
321-
322- private function type2txt (int $ typeId ): ?string
323- {
324- return \array_key_exists ($ typeId , $ this ->nativeTypes ) ? $ this ->nativeTypes [$ typeId ] : null ;
325- }
326-
327- /**
328- * @return list<string>
329- */
330- private function flags2txt (int $ flagId ): array
331- {
332- $ result = [];
333- foreach ($ this ->nativeFlags as $ n => $ t ) {
334- if ($ flagId & $ n ) {
335- $ result [] = $ t ;
336- }
337- }
338-
339- return $ result ;
340- }
341154}
0 commit comments