7
7
use Iterator ;
8
8
use PDO ;
9
9
use PDOException ;
10
- use PDOStatement ;
11
10
use PHPStan \ShouldNotHappenException ;
12
- use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
13
- use PHPStan \Type \Constant \ConstantIntegerType ;
14
- use PHPStan \Type \Constant \ConstantStringType ;
15
11
use PHPStan \Type \Type ;
16
- use staabm \PHPStanDba \Error ;
17
12
use staabm \PHPStanDba \TypeMapping \MysqlTypeMapper ;
18
- use function strtoupper ;
13
+ use staabm \ PHPStanDba \ TypeMapping \ TypeMapper ;
19
14
20
15
/**
21
- * @phpstan-type ColumnMeta array{name: string, table: string, native_type: string, len: int, flags: array<int, string>, precision: int<0, max>, pdo_type: PDO::PARAM_* }
16
+ * @phpstan-import- type ColumnMeta from BasePdoQueryReflector
22
17
*/
23
- final class PdoQueryReflector implements QueryReflector
18
+ final class PdoQueryReflector extends BasePdoQueryReflector implements QueryReflector
24
19
{
25
- private const PSQL_INVALID_TEXT_REPRESENTATION = '22P02 ' ;
26
- private const PSQL_UNDEFINED_COLUMN = '42703 ' ;
27
- private const PSQL_UNDEFINED_TABLE = '42P01 ' ;
28
-
29
- private const MYSQL_SYNTAX_ERROR_CODE = '42000 ' ;
30
- private const MYSQL_UNKNOWN_COLUMN_IN_FIELDLIST = '42S22 ' ;
31
- private const MYSQL_UNKNOWN_TABLE = '42S02 ' ;
32
-
33
- private const PDO_SYNTAX_ERROR_CODES = [
34
- self ::MYSQL_SYNTAX_ERROR_CODE ,
35
- self ::PSQL_INVALID_TEXT_REPRESENTATION ,
36
- ];
37
-
38
- private const PDO_ERROR_CODES = [
39
- self ::PSQL_INVALID_TEXT_REPRESENTATION ,
40
- self ::PSQL_UNDEFINED_COLUMN ,
41
- self ::PSQL_UNDEFINED_TABLE ,
42
- self ::MYSQL_SYNTAX_ERROR_CODE ,
43
- self ::MYSQL_UNKNOWN_COLUMN_IN_FIELDLIST ,
44
- self ::MYSQL_UNKNOWN_TABLE ,
45
- ];
46
-
47
- private const MAX_CACHE_SIZE = 50 ;
48
-
49
- /**
50
- * @var array<string, PDOException|array<int<0, max>, ColumnMeta>|null>
51
- */
52
- private array $ cache = [];
53
-
54
- private MysqlTypeMapper $ typeMapper ;
55
-
56
- /**
57
- * @var PDOStatement<array{COLUMN_TYPE: string, COLUMN_NAME: string, EXTRA: string}>|null
58
- */
59
- private ?PDOStatement $ stmt = null ;
60
- /**
61
- * @var array<string, array<string, list<string>>>
62
- */
63
- private array $ emulatedFlags = [];
64
-
65
- public function __construct (private PDO $ pdo )
20
+ public function __construct (PDO $ pdo )
66
21
{
67
- $ this -> pdo ->setAttribute (PDO ::ATTR_ERRMODE , PDO ::ERRMODE_EXCEPTION );
22
+ $ pdo ->setAttribute (PDO ::ATTR_ERRMODE , PDO ::ERRMODE_EXCEPTION );
68
23
69
- $ this ->typeMapper = new MysqlTypeMapper ();
70
- }
71
-
72
- public function validateQueryString (string $ queryString ): ?Error
73
- {
74
- $ result = $ this ->simulateQuery ($ queryString );
75
-
76
- if (!$ result instanceof PDOException) {
77
- return null ;
78
- }
79
-
80
- $ e = $ result ;
81
- if (\in_array ($ e ->getCode (), self ::PDO_ERROR_CODES , true )) {
82
- if (
83
- \in_array ($ e ->getCode (), self ::PDO_SYNTAX_ERROR_CODES , true )
84
- && QueryReflection::getRuntimeConfiguration ()->isDebugEnabled ()
85
- ) {
86
- return Error::forSyntaxError ($ e , $ e ->getCode (), $ queryString );
87
- }
88
-
89
- return Error::forException ($ e , $ e ->getCode ());
90
- }
91
-
92
- return null ;
93
- }
94
-
95
- /**
96
- * @param self::FETCH_TYPE* $fetchType
97
- */
98
- public function getResultType (string $ queryString , int $ fetchType ): ?Type
99
- {
100
- $ result = $ this ->simulateQuery ($ queryString );
101
-
102
- if (!\is_array ($ result )) {
103
- return null ;
104
- }
105
-
106
- $ arrayBuilder = ConstantArrayTypeBuilder::createEmpty ();
107
-
108
- $ i = 0 ;
109
- foreach ($ result as $ val ) {
110
- if (self ::FETCH_TYPE_ASSOC === $ fetchType || self ::FETCH_TYPE_BOTH === $ fetchType ) {
111
- $ arrayBuilder ->setOffsetValueType (
112
- new ConstantStringType ($ val ['name ' ]),
113
- $ this ->typeMapper ->mapToPHPStanType ($ val ['native_type ' ], $ val ['flags ' ], $ val ['len ' ])
114
- );
115
- }
116
- if (self ::FETCH_TYPE_NUMERIC === $ fetchType || self ::FETCH_TYPE_BOTH === $ fetchType ) {
117
- $ arrayBuilder ->setOffsetValueType (
118
- new ConstantIntegerType ($ i ),
119
- $ this ->typeMapper ->mapToPHPStanType ($ val ['native_type ' ], $ val ['flags ' ], $ val ['len ' ])
120
- );
121
- }
122
- ++$ i ;
123
- }
124
-
125
- return $ arrayBuilder ->getArray ();
24
+ parent ::__construct ($ pdo , new MysqlTypeMapper ());
126
25
}
127
26
128
27
/** @return PDOException|list<ColumnMeta>|null */
129
- private function simulateQuery (string $ queryString )
28
+ protected function simulateQuery (string $ queryString )
130
29
{
131
30
if (\array_key_exists ($ queryString , $ this ->cache )) {
132
31
return $ this ->cache [$ queryString ];
@@ -172,7 +71,7 @@ private function simulateQuery(string $queryString)
172
71
throw new ShouldNotHappenException ('Failed to get column meta for column index ' .$ columnIndex );
173
72
}
174
73
175
- $ flags = $ this ->emulateMysqlFlags ($ columnMeta ['native_type ' ], $ columnMeta ['table ' ], $ columnMeta ['name ' ]);
74
+ $ flags = $ this ->emulateFlags ($ columnMeta ['native_type ' ], $ columnMeta ['table ' ], $ columnMeta ['name ' ]);
176
75
foreach ($ flags as $ flag ) {
177
76
$ columnMeta ['flags ' ][] = $ flag ;
178
77
}
@@ -186,41 +85,9 @@ private function simulateQuery(string $queryString)
186
85
}
187
86
188
87
/**
189
- * @return list <string>
88
+ * @return Iterator <string, TypeMapper::FLAG_* >
190
89
*/
191
- private function emulateMysqlFlags (string $ mysqlType , string $ tableName , string $ columnName ): array
192
- {
193
- if (\array_key_exists ($ tableName , $ this ->emulatedFlags )) {
194
- $ emulatedFlags = [];
195
- if (\array_key_exists ($ columnName , $ this ->emulatedFlags [$ tableName ])) {
196
- $ emulatedFlags = $ this ->emulatedFlags [$ tableName ][$ columnName ];
197
- }
198
-
199
- if ($ this ->isNumericCol ($ mysqlType )) {
200
- $ emulatedFlags [] = MysqlTypeMapper::FLAG_NUMERIC ;
201
- }
202
-
203
- return $ emulatedFlags ;
204
- }
205
-
206
- $ this ->emulatedFlags [$ tableName ] = [];
207
-
208
- // determine flags of all columns of the given table once
209
- $ schemaFlags = $ this ->checkInformationSchema ($ tableName );
210
- foreach ($ schemaFlags as $ schemaColumnName => $ flag ) {
211
- if (!\array_key_exists ($ schemaColumnName , $ this ->emulatedFlags [$ tableName ])) {
212
- $ this ->emulatedFlags [$ tableName ][$ schemaColumnName ] = [];
213
- }
214
- $ this ->emulatedFlags [$ tableName ][$ schemaColumnName ][] = $ flag ;
215
- }
216
-
217
- return $ this ->emulateMysqlFlags ($ mysqlType , $ tableName , $ columnName );
218
- }
219
-
220
- /**
221
- * @return Iterator<string, MysqlTypeMapper::FLAG_*>
222
- */
223
- private function checkInformationSchema (string $ tableName ): Iterator
90
+ protected function checkInformationSchema (string $ tableName ): Iterator
224
91
{
225
92
if (null === $ this ->stmt ) {
226
93
$ this ->stmt = $ this ->pdo ->prepare (
@@ -243,25 +110,11 @@ private function checkInformationSchema(string $tableName): Iterator
243
110
$ columnName = $ row ['COLUMN_NAME ' ];
244
111
245
112
if (str_contains ($ extra , 'auto_increment ' )) {
246
- yield $ columnName => MysqlTypeMapper ::FLAG_AUTO_INCREMENT ;
113
+ yield $ columnName => TypeMapper ::FLAG_AUTO_INCREMENT ;
247
114
}
248
115
if (str_contains ($ columnType , 'unsigned ' )) {
249
- yield $ columnName => MysqlTypeMapper ::FLAG_UNSIGNED ;
116
+ yield $ columnName => TypeMapper ::FLAG_UNSIGNED ;
250
117
}
251
118
}
252
119
}
253
-
254
- private function isNumericCol (string $ mysqlType ): bool
255
- {
256
- return match (strtoupper ($ mysqlType )) {
257
- 'LONGLONG ' ,
258
- 'LONG ' ,
259
- 'SHORT ' ,
260
- 'TINY ' ,
261
- 'YEAR ' ,
262
- 'BIT ' ,
263
- 'INT24 ' => true ,
264
- default => false ,
265
- };
266
- }
267
120
}
0 commit comments