@@ -83,7 +83,7 @@ class AsyncQueryBuilder
83
83
* @var array<string> Raw OR WHERE conditions.
84
84
*/
85
85
protected array $ orWhereRaw = [];
86
-
86
+
87
87
/**
88
88
* @var array<string> The GROUP BY clauses for the query.
89
89
*/
@@ -110,9 +110,18 @@ class AsyncQueryBuilder
110
110
protected ?int $ offset = null ;
111
111
112
112
/**
113
- * @var array<mixed> The parameter bindings for the query.
113
+ * @var array<string, array< mixed>> The parameter bindings for the query, grouped by type .
114
114
*/
115
- protected array $ bindings = [];
115
+ protected array $ bindings = [
116
+ 'where ' => [],
117
+ 'whereIn ' => [],
118
+ 'whereNotIn ' => [],
119
+ 'whereBetween ' => [],
120
+ 'whereRaw ' => [],
121
+ 'orWhere ' => [],
122
+ 'orWhereRaw ' => [],
123
+ 'having ' => [],
124
+ ];
116
125
117
126
/**
118
127
* @var int The current binding index counter.
@@ -225,7 +234,7 @@ public function where(string $column, mixed $operator = null, mixed $value = nul
225
234
226
235
$ placeholder = $ this ->getPlaceholder ();
227
236
$ this ->where [] = "{$ column } {$ operator } {$ placeholder }" ;
228
- $ this ->bindings [] = $ value ;
237
+ $ this ->bindings [' where ' ][ ] = $ value ;
229
238
230
239
return $ this ;
231
240
}
@@ -252,7 +261,7 @@ public function orWhere(string $column, mixed $operator = null, mixed $value = n
252
261
253
262
$ placeholder = $ this ->getPlaceholder ();
254
263
$ this ->orWhere [] = "{$ column } {$ operator } {$ placeholder }" ;
255
- $ this ->bindings [] = $ value ;
264
+ $ this ->bindings [' orWhere ' ][ ] = $ value ;
256
265
257
266
return $ this ;
258
267
}
@@ -269,7 +278,7 @@ public function whereIn(string $column, array $values): self
269
278
$ placeholders = [];
270
279
foreach ($ values as $ value ) {
271
280
$ placeholders [] = $ this ->getPlaceholder ();
272
- $ this ->bindings [] = $ value ;
281
+ $ this ->bindings [' whereIn ' ][ ] = $ value ;
273
282
}
274
283
$ this ->whereIn [] = "{$ column } IN ( " . implode (', ' , $ placeholders ) . ') ' ;
275
284
@@ -288,7 +297,7 @@ public function whereNotIn(string $column, array $values): self
288
297
$ placeholders = [];
289
298
foreach ($ values as $ value ) {
290
299
$ placeholders [] = $ this ->getPlaceholder ();
291
- $ this ->bindings [] = $ value ;
300
+ $ this ->bindings [' whereNotIn ' ][ ] = $ value ;
292
301
}
293
302
$ this ->whereNotIn [] = "{$ column } NOT IN ( " . implode (', ' , $ placeholders ) . ') ' ;
294
303
@@ -312,8 +321,8 @@ public function whereBetween(string $column, array $values): self
312
321
$ placeholder1 = $ this ->getPlaceholder ();
313
322
$ placeholder2 = $ this ->getPlaceholder ();
314
323
$ this ->whereBetween [] = "{$ column } BETWEEN {$ placeholder1 } AND {$ placeholder2 }" ;
315
- $ this ->bindings [] = $ values [0 ];
316
- $ this ->bindings [] = $ values [1 ];
324
+ $ this ->bindings [' whereBetween ' ][ ] = $ values [0 ];
325
+ $ this ->bindings [' whereBetween ' ][ ] = $ values [1 ];
317
326
318
327
return $ this ;
319
328
}
@@ -364,7 +373,7 @@ public function like(string $column, string $value, string $side = 'both'): self
364
373
default => $ value
365
374
};
366
375
367
- $ this ->bindings [] = $ likeValue ;
376
+ $ this ->bindings [' where ' ][ ] = $ likeValue ;
368
377
369
378
return $ this ;
370
379
}
@@ -407,7 +416,7 @@ public function having(string $column, mixed $operator = null, mixed $value = nu
407
416
408
417
$ placeholder = $ this ->getPlaceholder ();
409
418
$ this ->having [] = "{$ column } {$ operator } {$ placeholder }" ;
410
- $ this ->bindings [] = $ value ;
419
+ $ this ->bindings [' having ' ][ ] = $ value ;
411
420
412
421
return $ this ;
413
422
}
@@ -465,7 +474,7 @@ public function get(): PromiseInterface
465
474
{
466
475
$ sql = $ this ->buildSelectQuery ();
467
476
468
- return AsyncPDO::query ($ sql , $ this ->bindings );
477
+ return AsyncPDO::query ($ sql , $ this ->getCompiledBindings () );
469
478
}
470
479
471
480
/**
@@ -480,7 +489,7 @@ public function first(): PromiseInterface
480
489
$ sql = $ this ->buildSelectQuery ();
481
490
$ this ->limit = $ originalLimit ;
482
491
483
- return AsyncPDO::fetchOne ($ sql , $ this ->bindings );
492
+ return AsyncPDO::fetchOne ($ sql , $ this ->getCompiledBindings () );
484
493
}
485
494
486
495
/**
@@ -546,7 +555,7 @@ public function count(string $column = '*'): PromiseInterface
546
555
{
547
556
$ sql = $ this ->buildCountQuery ($ column );
548
557
549
- return AsyncPDO::fetchValue ($ sql , $ this ->bindings );
558
+ return AsyncPDO::fetchValue ($ sql , $ this ->getCompiledBindings () );
550
559
}
551
560
552
561
/**
@@ -620,7 +629,8 @@ public function create(array $data): PromiseInterface
620
629
public function update (array $ data ): PromiseInterface
621
630
{
622
631
$ sql = $ this ->buildUpdateQuery ($ data );
623
- $ bindings = array_merge (array_values ($ data ), $ this ->bindings );
632
+ $ whereBindings = $ this ->getCompiledBindings ();
633
+ $ bindings = array_merge (array_values ($ data ), $ whereBindings );
624
634
625
635
return AsyncPDO::execute ($ sql , $ bindings );
626
636
}
@@ -634,7 +644,7 @@ public function delete(): PromiseInterface
634
644
{
635
645
$ sql = $ this ->buildDeleteQuery ();
636
646
637
- return AsyncPDO::execute ($ sql , $ this ->bindings );
647
+ return AsyncPDO::execute ($ sql , $ this ->getCompiledBindings () );
638
648
}
639
649
640
650
/**
@@ -689,7 +699,10 @@ protected function buildSelectQuery(): string
689
699
}
690
700
691
701
// Add where clauses
692
- $ sql .= $ this ->buildWhereClause ();
702
+ $ whereSql = $ this ->buildWhereClause ();
703
+ if ($ whereSql !== '' ) {
704
+ $ sql .= ' WHERE ' . $ whereSql ;
705
+ }
693
706
694
707
// Add group by
695
708
if (count ($ this ->groupBy ) > 0 ) {
@@ -733,7 +746,10 @@ protected function buildCountQuery(string $column = '*'): string
733
746
}
734
747
735
748
// Add where clauses
736
- $ sql .= $ this ->buildWhereClause ();
749
+ $ whereSql = $ this ->buildWhereClause ();
750
+ if ($ whereSql !== '' ) {
751
+ $ sql .= ' WHERE ' . $ whereSql ;
752
+ }
737
753
738
754
// Add group by
739
755
if (count ($ this ->groupBy ) > 0 ) {
@@ -797,7 +813,10 @@ protected function buildUpdateQuery(array $data): string
797
813
$ setClauses [] = "{$ column } = ? " ;
798
814
}
799
815
$ sql = "UPDATE {$ this ->table } SET " . implode (', ' , $ setClauses );
800
- $ sql .= $ this ->buildWhereClause ();
816
+ $ whereSql = $ this ->buildWhereClause ();
817
+ if ($ whereSql !== '' ) {
818
+ $ sql .= ' WHERE ' . $ whereSql ;
819
+ }
801
820
802
821
return $ sql ;
803
822
}
@@ -810,7 +829,10 @@ protected function buildUpdateQuery(array $data): string
810
829
protected function buildDeleteQuery (): string
811
830
{
812
831
$ sql = "DELETE FROM {$ this ->table }" ;
813
- $ sql .= $ this ->buildWhereClause ();
832
+ $ whereSql = $ this ->buildWhereClause ();
833
+ if ($ whereSql !== '' ) {
834
+ $ sql .= ' WHERE ' . $ whereSql ;
835
+ }
814
836
815
837
return $ sql ;
816
838
}
@@ -828,7 +850,7 @@ protected function buildWhereClause(): string
828
850
return '' ;
829
851
}
830
852
831
- return ' WHERE ' . $ this ->combineConditionParts ($ allParts );
853
+ return $ this ->combineConditionParts ($ allParts );
832
854
}
833
855
834
856
/**
@@ -1032,24 +1054,18 @@ protected function getAllConditions(): array
1032
1054
*/
1033
1055
public function whereGroup (callable $ callback , string $ logicalOperator = 'AND ' ): self
1034
1056
{
1035
- $ subBuilder = new static ();
1057
+ $ subBuilder = new static ($ this -> table );
1036
1058
$ callback ($ subBuilder );
1037
1059
1038
- $ subConditions = $ subBuilder ->getAllConditions ();
1039
- $ hasConditions = !empty ($ subConditions ['AND ' ]) || !empty ($ subConditions ['OR ' ]);
1040
-
1041
- if ($ hasConditions ) {
1042
- $ this ->conditionGroups [] = [
1043
- 'type ' => 'group ' ,
1044
- 'conditions ' => $ subConditions ,
1045
- 'operator ' => strtoupper ($ logicalOperator ),
1046
- 'bindings ' => $ subBuilder ->bindings
1047
- ];
1060
+ $ subSql = $ subBuilder ->buildWhereClause ();
1048
1061
1049
- // Merge bindings
1050
- $ this ->bindings = array_merge ($ this ->bindings , $ subBuilder ->bindings );
1062
+ // If the subquery is empty, do nothing.
1063
+ if ($ subSql === '' ) {
1064
+ return $ this ;
1051
1065
}
1052
1066
1067
+ $ this ->whereRaw ("( {$ subSql }) " , $ subBuilder ->getCompiledBindings (), $ logicalOperator );
1068
+
1053
1069
return $ this ;
1054
1070
}
1055
1071
@@ -1077,12 +1093,12 @@ public function whereRaw(string $condition, array $bindings = [], string $operat
1077
1093
{
1078
1094
if (strtoupper ($ operator ) === 'OR ' ) {
1079
1095
$ this ->orWhereRaw [] = $ condition ;
1096
+ $ this ->bindings ['orWhereRaw ' ] = array_merge ($ this ->bindings ['orWhereRaw ' ], $ bindings );
1080
1097
} else {
1081
1098
$ this ->whereRaw [] = $ condition ;
1099
+ $ this ->bindings ['whereRaw ' ] = array_merge ($ this ->bindings ['whereRaw ' ], $ bindings );
1082
1100
}
1083
1101
1084
- $ this ->bindings = array_merge ($ this ->bindings , $ bindings );
1085
-
1086
1102
return $ this ;
1087
1103
}
1088
1104
@@ -1113,7 +1129,7 @@ public function whereExists(callable $callback, string $operator = 'AND'): self
1113
1129
$ subSql = $ subBuilder ->buildSelectQuery ();
1114
1130
$ condition = "EXISTS ( {$ subSql }) " ;
1115
1131
1116
- return $ this ->whereRaw ($ condition , $ subBuilder ->bindings , $ operator );
1132
+ return $ this ->whereRaw ($ condition , $ subBuilder ->getCompiledBindings () , $ operator );
1117
1133
}
1118
1134
1119
1135
/**
@@ -1131,7 +1147,18 @@ public function whereNotExists(callable $callback, string $operator = 'AND'): se
1131
1147
$ subSql = $ subBuilder ->buildSelectQuery ();
1132
1148
$ condition = "NOT EXISTS ( {$ subSql }) " ;
1133
1149
1134
- return $ this ->whereRaw ($ condition , $ subBuilder ->bindings , $ operator );
1150
+ return $ this ->whereRaw ($ condition , $ subBuilder ->getCompiledBindings (), $ operator );
1151
+ }
1152
+
1153
+ /**
1154
+ * Add a nested OR WHERE condition with custom logic.
1155
+ *
1156
+ * @param callable $callback Callback function for nested conditions.
1157
+ * @return self Returns the query builder instance for method chaining.
1158
+ */
1159
+ public function orWhereNested (callable $ callback ): self
1160
+ {
1161
+ return $ this ->whereGroup ($ callback , 'OR ' );
1135
1162
}
1136
1163
1137
1164
/**
@@ -1151,7 +1178,7 @@ public function toSql(): string
1151
1178
*/
1152
1179
public function getBindings (): array
1153
1180
{
1154
- return $ this ->bindings ;
1181
+ return $ this ->getCompiledBindings () ;
1155
1182
}
1156
1183
1157
1184
/**
@@ -1171,7 +1198,16 @@ public function resetWhere(): self
1171
1198
$ this ->whereRaw = [];
1172
1199
$ this ->orWhereRaw = [];
1173
1200
$ this ->conditionGroups = [];
1174
- $ this ->bindings = [];
1201
+ $ this ->bindings = [
1202
+ 'where ' => [],
1203
+ 'whereIn ' => [],
1204
+ 'whereNotIn ' => [],
1205
+ 'whereBetween ' => [],
1206
+ 'whereRaw ' => [],
1207
+ 'orWhere ' => [],
1208
+ 'orWhereRaw ' => [],
1209
+ 'having ' => [],
1210
+ ];
1175
1211
$ this ->bindingIndex = 0 ;
1176
1212
1177
1213
return $ this ;
@@ -1186,4 +1222,33 @@ protected function getPlaceholder(): string
1186
1222
{
1187
1223
return '? ' ;
1188
1224
}
1225
+
1226
+ /**
1227
+ * Compiles the final bindings array in the correct order for execution.
1228
+ *
1229
+ * @return array<mixed>
1230
+ */
1231
+ protected function getCompiledBindings (): array
1232
+ {
1233
+ // This merge order MUST match the order in `collectAllConditionParts()`
1234
+ // and the order of the final query construction.
1235
+ $ whereBindings = array_merge (
1236
+ $ this ->bindings ['where ' ],
1237
+ $ this ->bindings ['whereIn ' ],
1238
+ $ this ->bindings ['whereNotIn ' ],
1239
+ $ this ->bindings ['whereBetween ' ],
1240
+ $ this ->bindings ['whereRaw ' ],
1241
+ $ this ->bindings ['orWhere ' ],
1242
+ $ this ->bindings ['orWhereRaw ' ]
1243
+ );
1244
+
1245
+ // This handles bindings from complex groups like whereGroup
1246
+ foreach ($ this ->conditionGroups as $ group ) {
1247
+ if (isset ($ group ['bindings ' ])) {
1248
+ $ whereBindings = array_merge ($ whereBindings , $ group ['bindings ' ]);
1249
+ }
1250
+ }
1251
+
1252
+ return array_merge ($ whereBindings , $ this ->bindings ['having ' ]);
1253
+ }
1189
1254
}
0 commit comments