1414use PHPStan \Rules \RuleError ;
1515use PHPStan \Rules \RuleErrorBuilder ;
1616use PHPStan \ShouldNotHappenException ;
17- use PHPStan \Type \Constant \ConstantArrayType ;
18- use PHPStan \Type \Constant \ConstantStringType ;
1917use PHPStan \Type \IntegerRangeType ;
2018use PHPStan \Type \IntegerType ;
2119use PHPStan \Type \ObjectType ;
@@ -113,7 +111,8 @@ public function processNode(Node $callLike, Scope $scope): array
113111
114112 $ tableExpr = $ args [0 ]->value ;
115113 $ tableType = $ scope ->getType ($ tableExpr );
116- if (! $ tableType instanceof ConstantStringType) {
114+ $ tableNames = $ tableType ->getConstantStrings ();
115+ if (\count ($ tableNames ) === 0 ) {
117116 return [
118117 RuleErrorBuilder::message ('Argument #0 expects a constant string, got ' . $ tableType ->describe (VerbosityLevel::precise ()))->line ($ callLike ->getLine ())->build (),
119118 ];
@@ -126,74 +125,81 @@ public function processNode(Node $callLike, Scope $scope): array
126125
127126 $ checkIntegerRanges = QueryReflection::getRuntimeConfiguration ()->isParameterTypeValidationStrict ();
128127
129- // Table name may be escaped with backticks
130- $ argTableName = trim ($ tableType ->getValue (), '` ' );
131- $ table = $ schemaReflection ->getTable ($ argTableName );
132- if (null === $ table ) {
133- return [
134- RuleErrorBuilder::message ('Query error: Table " ' . $ argTableName . '" does not exist ' )->line ($ callLike ->getLine ())->build (),
135- ];
136- }
137-
138- // All array arguments should have table columns as keys
139128 $ errors = [];
140- foreach ($ arrayArgPositions as $ arrayArgPosition ) {
141- // If the argument doesn't exist, just skip it since we don't want
142- // to error in case it has a default value
143- if (! \array_key_exists ($ arrayArgPosition , $ args )) {
144- continue ;
145- }
146-
147- $ argType = $ scope ->getType ($ args [$ arrayArgPosition ]->value );
148- if (! $ argType instanceof ConstantArrayType) {
149- $ errors [] = 'Argument # ' . $ arrayArgPosition . ' is not a constant array, got ' . $ argType ->describe (VerbosityLevel::precise ());
129+ foreach ($ tableNames as $ tableName ) {
130+ // Table name may be escaped with backticks
131+ $ tableName = trim ($ tableName ->getValue (), '` ' );
132+ $ table = $ schemaReflection ->getTable ($ tableName );
133+ if (null === $ table ) {
134+ $ errors [] = 'Table " ' . $ tableName . '" does not exist ' ;
150135 continue ;
151136 }
152137
153- foreach ($ argType ->getKeyTypes () as $ keyIndex => $ keyType ) {
154- if (! $ keyType instanceof ConstantStringType) {
155- $ errors [] = 'Element # ' . $ keyIndex . ' of argument # ' . $ arrayArgPosition . ' must have a string key, got ' . $ keyType ->describe (VerbosityLevel::precise ());
138+ // All array arguments should have table columns as keys
139+ foreach ($ arrayArgPositions as $ arrayArgPosition ) {
140+ // If the argument doesn't exist, just skip it since we don't want
141+ // to error in case it has a default value
142+ if (! \array_key_exists ($ arrayArgPosition , $ args )) {
156143 continue ;
157144 }
158145
159- // Column name may be escaped with backticks
160- $ argColumnName = trim ($ keyType ->getValue (), '` ' );
161-
162- $ argColumn = null ;
163- foreach ($ table ->getColumns () as $ column ) {
164- if ($ argColumnName === $ column ->getName ()) {
165- $ argColumn = $ column ;
166- }
167- }
168- if (null === $ argColumn ) {
169- $ errors [] = 'Column " ' . $ table ->getName () . '. ' . $ argColumnName . '" does not exist ' ;
146+ $ argType = $ scope ->getType ($ args [$ arrayArgPosition ]->value );
147+ $ argArrays = $ argType ->getConstantArrays ();
148+ if (\count ($ argArrays ) === 0 ) {
149+ $ errors [] = 'Argument # ' . $ arrayArgPosition . ' is not a constant array, got ' . $ argType ->describe (VerbosityLevel::precise ());
170150 continue ;
171151 }
172152
173- $ argColumnType = $ argColumn ->getType ();
174- $ valueType = $ argType ->getValueTypes ()[$ keyIndex ];
175-
176- if (false === $ checkIntegerRanges ) {
177- // Convert IntegerRangeType column types into IntegerType so
178- // that any integer value is accepted for integer columns,
179- // since it is uncommon to check integer value ranges.
180- if ($ argColumnType instanceof IntegerRangeType) {
181- $ argColumnType = new IntegerType ();
182- } elseif ($ argColumnType instanceof UnionType) {
183- $ newTypes = [];
184- foreach ($ argColumnType ->getTypes () as $ type ) {
185- if ($ type instanceof IntegerRangeType) {
186- $ type = new IntegerType ();
153+ foreach ($ argArrays as $ argArray ) {
154+ foreach ($ argArray ->getKeyTypes () as $ keyIndex => $ keyType ) {
155+ $ keyNames = $ keyType ->getConstantStrings ();
156+ if (\count ($ keyNames ) === 0 ) {
157+ $ errors [] = 'Element # ' . $ keyIndex . ' of argument # ' . $ arrayArgPosition . ' must have a string key, got ' . $ keyType ->describe (VerbosityLevel::precise ());
158+ continue ;
159+ }
160+
161+ foreach ($ keyNames as $ keyName ) {
162+ // Column name may be escaped with backticks
163+ $ argColumnName = trim ($ keyName ->getValue (), '` ' );
164+
165+ $ argColumn = null ;
166+ foreach ($ table ->getColumns () as $ column ) {
167+ if ($ argColumnName === $ column ->getName ()) {
168+ $ argColumn = $ column ;
169+ }
170+ }
171+ if (null === $ argColumn ) {
172+ $ errors [] = 'Column " ' . $ table ->getName () . '. ' . $ argColumnName . '" does not exist ' ;
173+ continue ;
174+ }
175+
176+ $ argColumnType = $ argColumn ->getType ();
177+ $ valueType = $ argArray ->getValueTypes ()[$ keyIndex ];
178+
179+ if (false === $ checkIntegerRanges ) {
180+ // Convert IntegerRangeType column types into IntegerType so
181+ // that any integer value is accepted for integer columns,
182+ // since it is uncommon to check integer value ranges.
183+ if ($ argColumnType instanceof IntegerRangeType) {
184+ $ argColumnType = new IntegerType ();
185+ } elseif ($ argColumnType instanceof UnionType) {
186+ $ newTypes = [];
187+ foreach ($ argColumnType ->getTypes () as $ type ) {
188+ if ($ type instanceof IntegerRangeType) {
189+ $ type = new IntegerType ();
190+ }
191+ $ newTypes [] = $ type ;
192+ }
193+ $ argColumnType = TypeCombinator::union (...$ newTypes );
194+ }
195+ }
196+
197+ if (! $ argColumnType ->isSuperTypeOf ($ valueType )->yes ()) {
198+ $ errors [] = 'Column " ' . $ table ->getName () . '. ' . $ argColumnName . '" expects value type ' . $ argColumnType ->describe (VerbosityLevel::precise ()) . ', got type ' . $ valueType ->describe (VerbosityLevel::precise ());
187199 }
188- $ newTypes [] = $ type ;
189200 }
190- $ argColumnType = TypeCombinator::union (...$ newTypes );
191201 }
192202 }
193-
194- if (! $ argColumnType ->isSuperTypeOf ($ valueType )->yes ()) {
195- $ errors [] = 'Column " ' . $ table ->getName () . '. ' . $ argColumnName . '" expects value type ' . $ argColumnType ->describe (VerbosityLevel::precise ()) . ', got type ' . $ valueType ->describe (VerbosityLevel::precise ());
196- }
197203 }
198204 }
199205
0 commit comments