1 | <?php |
||||||
2 | |||||||
3 | namespace Pixie\QueryBuilder; |
||||||
4 | |||||||
5 | use wpdb; |
||||||
6 | use Closure; |
||||||
7 | use Throwable; |
||||||
8 | use Pixie\Binding; |
||||||
9 | use Pixie\Exception; |
||||||
10 | use Pixie\Connection; |
||||||
11 | |||||||
12 | use Pixie\HasConnection; |
||||||
13 | |||||||
14 | use Pixie\JSON\JsonHandler; |
||||||
15 | use Pixie\QueryBuilder\Raw; |
||||||
16 | use Pixie\Hydration\Hydrator; |
||||||
17 | use Pixie\JSON\JsonSelectorHandler; |
||||||
18 | use Pixie\QueryBuilder\JoinBuilder; |
||||||
19 | use Pixie\QueryBuilder\QueryObject; |
||||||
20 | use Pixie\QueryBuilder\Transaction; |
||||||
21 | use Pixie\QueryBuilder\WPDBAdapter; |
||||||
22 | use Pixie\QueryBuilder\TablePrefixer; |
||||||
23 | use function mb_strlen; |
||||||
24 | |||||||
25 | class QueryBuilderHandler implements HasConnection |
||||||
26 | { |
||||||
27 | /** |
||||||
28 | * @method add |
||||||
29 | */ |
||||||
30 | use TablePrefixer; |
||||||
31 | |||||||
32 | /** |
||||||
33 | * @var \Viocon\Container |
||||||
34 | */ |
||||||
35 | protected $container; |
||||||
36 | |||||||
37 | /** |
||||||
38 | * @var Connection |
||||||
39 | */ |
||||||
40 | protected $connection; |
||||||
41 | |||||||
42 | /** |
||||||
43 | * @var array<string, mixed[]|mixed> |
||||||
44 | */ |
||||||
45 | protected $statements = []; |
||||||
46 | |||||||
47 | /** |
||||||
48 | * @var wpdb |
||||||
49 | */ |
||||||
50 | protected $dbInstance; |
||||||
51 | |||||||
52 | /** |
||||||
53 | * @var string|string[]|null |
||||||
54 | */ |
||||||
55 | protected $sqlStatement = null; |
||||||
56 | |||||||
57 | /** |
||||||
58 | * @var string|null |
||||||
59 | */ |
||||||
60 | protected $tablePrefix = null; |
||||||
61 | |||||||
62 | /** |
||||||
63 | * @var WPDBAdapter |
||||||
64 | */ |
||||||
65 | protected $adapterInstance; |
||||||
66 | |||||||
67 | /** |
||||||
68 | * The mode to return results as. |
||||||
69 | * Accepts WPDB constants or class names. |
||||||
70 | * |
||||||
71 | * @var string |
||||||
72 | */ |
||||||
73 | protected $fetchMode; |
||||||
74 | |||||||
75 | /** |
||||||
76 | * Custom args used to construct models for hydrator |
||||||
77 | * |
||||||
78 | * @var array<int, mixed>|null |
||||||
79 | */ |
||||||
80 | protected $hydratorConstructorArgs; |
||||||
81 | |||||||
82 | /** |
||||||
83 | * Handler for Json Selectors |
||||||
84 | * |
||||||
85 | * @var JsonHandler |
||||||
86 | */ |
||||||
87 | protected $jsonHandler; |
||||||
88 | |||||||
89 | /** |
||||||
90 | * @param \Pixie\Connection|null $connection |
||||||
91 | * @param string $fetchMode |
||||||
92 | * @param mixed[] $hydratorConstructorArgs |
||||||
93 | * |
||||||
94 | * @throws Exception if no connection passed and not previously established |
||||||
95 | */ |
||||||
96 | final public function __construct( |
||||||
97 | Connection $connection = null, |
||||||
98 | string $fetchMode = \OBJECT, |
||||||
99 | ?array $hydratorConstructorArgs = null |
||||||
100 | ) { |
||||||
101 | if (is_null($connection)) { |
||||||
102 | // throws if connection not already established. |
||||||
103 | $connection = Connection::getStoredConnection(); |
||||||
104 | } |
||||||
105 | |||||||
106 | // Set all dependencies from connection. |
||||||
107 | $this->connection = $connection; |
||||||
108 | $this->container = $this->connection->getContainer(); |
||||||
109 | $this->dbInstance = $this->connection->getDbInstance(); |
||||||
110 | $this->setAdapterConfig($this->connection->getAdapterConfig()); |
||||||
111 | |||||||
112 | // Set up optional hydration details. |
||||||
113 | $this->setFetchMode($fetchMode); |
||||||
114 | $this->hydratorConstructorArgs = $hydratorConstructorArgs; |
||||||
115 | |||||||
116 | // Query builder adapter instance |
||||||
117 | $this->adapterInstance = $this->container->build( |
||||||
118 | WPDBAdapter::class, |
||||||
119 | [$this->connection] |
||||||
120 | ); |
||||||
121 | |||||||
122 | // Setup JSON Selector handler. |
||||||
123 | $this->jsonHandler = new JsonHandler($connection); |
||||||
124 | } |
||||||
125 | |||||||
126 | /** |
||||||
127 | * Sets the config for WPDB |
||||||
128 | * |
||||||
129 | * @param array<string, mixed> $adapterConfig |
||||||
130 | * |
||||||
131 | * @return void |
||||||
132 | */ |
||||||
133 | protected function setAdapterConfig(array $adapterConfig): void |
||||||
134 | { |
||||||
135 | if (isset($adapterConfig[Connection::PREFIX])) { |
||||||
136 | $this->tablePrefix = $adapterConfig[Connection::PREFIX]; |
||||||
137 | } |
||||||
138 | } |
||||||
139 | |||||||
140 | /** |
||||||
141 | * Fetch query results as object of specified type |
||||||
142 | * |
||||||
143 | * @param string $className |
||||||
144 | * @param array<int, mixed> $constructorArgs |
||||||
145 | * @return static |
||||||
146 | */ |
||||||
147 | public function asObject($className, $constructorArgs = array()): self |
||||||
148 | { |
||||||
149 | return $this->setFetchMode($className, $constructorArgs); |
||||||
150 | } |
||||||
151 | |||||||
152 | /** |
||||||
153 | * Set the fetch mode |
||||||
154 | * |
||||||
155 | * @param string $mode |
||||||
156 | * @param array<int, mixed>|null $constructorArgs |
||||||
157 | * |
||||||
158 | * @return static |
||||||
159 | */ |
||||||
160 | public function setFetchMode(string $mode, ?array $constructorArgs = null): self |
||||||
161 | { |
||||||
162 | $this->fetchMode = $mode; |
||||||
163 | $this->hydratorConstructorArgs = $constructorArgs; |
||||||
164 | |||||||
165 | return $this; |
||||||
166 | } |
||||||
167 | |||||||
168 | /** |
||||||
169 | * @param Connection|null $connection |
||||||
170 | * |
||||||
171 | * @return static |
||||||
172 | * |
||||||
173 | * @throws Exception |
||||||
174 | */ |
||||||
175 | public function newQuery(Connection $connection = null): self |
||||||
176 | { |
||||||
177 | if (is_null($connection)) { |
||||||
178 | $connection = $this->connection; |
||||||
179 | } |
||||||
180 | |||||||
181 | $newQuery = $this->constructCurrentBuilderClass($connection); |
||||||
182 | $newQuery->setFetchMode($this->getFetchMode(), $this->hydratorConstructorArgs); |
||||||
183 | |||||||
184 | return $newQuery; |
||||||
185 | } |
||||||
186 | |||||||
187 | /** |
||||||
188 | * Returns a new instance of the current, with the passed connection. |
||||||
189 | * |
||||||
190 | * @param \Pixie\Connection $connection |
||||||
191 | * |
||||||
192 | * @return static |
||||||
193 | */ |
||||||
194 | protected function constructCurrentBuilderClass(Connection $connection): self |
||||||
195 | { |
||||||
196 | return new static($connection); |
||||||
197 | } |
||||||
198 | |||||||
199 | /** |
||||||
200 | * Interpolates a query |
||||||
201 | * |
||||||
202 | * @param string $query |
||||||
203 | * @param array<mixed> $bindings |
||||||
204 | * @return string |
||||||
205 | */ |
||||||
206 | public function interpolateQuery(string $query, array $bindings = []): string |
||||||
207 | { |
||||||
208 | return $this->adapterInstance->interpolateQuery($query, $bindings); |
||||||
209 | } |
||||||
210 | |||||||
211 | /** |
||||||
212 | * @param string $sql |
||||||
213 | * @param array<int,mixed> $bindings |
||||||
214 | * |
||||||
215 | * @return static |
||||||
216 | */ |
||||||
217 | public function query($sql, $bindings = []): self |
||||||
218 | { |
||||||
219 | list($this->sqlStatement) = $this->statement($sql, $bindings); |
||||||
220 | |||||||
221 | return $this; |
||||||
222 | } |
||||||
223 | |||||||
224 | /** |
||||||
225 | * @param string $sql |
||||||
226 | * @param array<int,mixed> $bindings |
||||||
227 | * |
||||||
228 | * @return array{0:string, 1:float} |
||||||
229 | */ |
||||||
230 | public function statement(string $sql, $bindings = []): array |
||||||
231 | { |
||||||
232 | $start = microtime(true); |
||||||
233 | $sqlStatement = empty($bindings) ? $sql : $this->interpolateQuery($sql, $bindings); |
||||||
234 | |||||||
235 | if (!is_string($sqlStatement)) { |
||||||
0 ignored issues
–
show
introduced
by
![]() |
|||||||
236 | throw new Exception('Could not interpolate query', 1); |
||||||
237 | } |
||||||
238 | |||||||
239 | return [$sqlStatement, microtime(true) - $start]; |
||||||
240 | } |
||||||
241 | |||||||
242 | /** |
||||||
243 | * Get all rows |
||||||
244 | * |
||||||
245 | * @return array<mixed,mixed>|null |
||||||
246 | * |
||||||
247 | * @throws Exception |
||||||
248 | */ |
||||||
249 | public function get() |
||||||
250 | { |
||||||
251 | $eventResult = $this->fireEvents('before-select'); |
||||||
252 | if (!is_null($eventResult)) { |
||||||
253 | return $eventResult; |
||||||
254 | } |
||||||
255 | $executionTime = 0; |
||||||
256 | if (is_null($this->sqlStatement)) { |
||||||
257 | $queryObject = $this->getQuery('select'); |
||||||
258 | $statement = $this->statement( |
||||||
259 | $queryObject->getSql(), |
||||||
260 | $queryObject->getBindings() |
||||||
261 | ); |
||||||
262 | |||||||
263 | $this->sqlStatement = $statement[0]; |
||||||
264 | $executionTime = $statement[1]; |
||||||
265 | } |
||||||
266 | |||||||
267 | $start = microtime(true); |
||||||
268 | $result = $this->dbInstance()->get_results( |
||||||
269 | is_array($this->sqlStatement) ? (end($this->sqlStatement) ?: '') : $this->sqlStatement, |
||||||
270 | // If we are using the hydrator, return as OBJECT and let the hydrator map the correct model. |
||||||
271 | $this->useHydrator() ? OBJECT : $this->getFetchMode() |
||||||
272 | ); |
||||||
273 | $executionTime += microtime(true) - $start; |
||||||
274 | $this->sqlStatement = null; |
||||||
275 | |||||||
276 | // Ensure we have an array of results. |
||||||
277 | if (!is_array($result) && null !== $result) { |
||||||
278 | $result = [$result]; |
||||||
279 | } |
||||||
280 | |||||||
281 | // Maybe hydrate the results. |
||||||
282 | if (null !== $result && $this->useHydrator()) { |
||||||
283 | $result = $this->getHydrator()->fromMany($result); |
||||||
284 | } |
||||||
285 | |||||||
286 | $this->fireEvents('after-select', $result, $executionTime); |
||||||
287 | |||||||
288 | return $result; |
||||||
289 | } |
||||||
290 | |||||||
291 | /** |
||||||
292 | * Returns a populated instance of the Hydrator. |
||||||
293 | * |
||||||
294 | * @return Hydrator |
||||||
295 | */ |
||||||
296 | protected function getHydrator(): Hydrator /* @phpstan-ignore-line */ |
||||||
297 | { |
||||||
298 | $hydrator = new Hydrator($this->getFetchMode(), $this->hydratorConstructorArgs ?? []); /* @phpstan-ignore-line */ |
||||||
299 | |||||||
300 | return $hydrator; |
||||||
301 | } |
||||||
302 | |||||||
303 | /** |
||||||
304 | * Checks if the results should be mapped via the hydrator |
||||||
305 | * |
||||||
306 | * @return bool |
||||||
307 | */ |
||||||
308 | protected function useHydrator(): bool |
||||||
309 | { |
||||||
310 | return !in_array($this->getFetchMode(), [\ARRAY_A, \ARRAY_N, \OBJECT, \OBJECT_K]); |
||||||
311 | } |
||||||
312 | |||||||
313 | /** |
||||||
314 | * Find all matching a simple where condition. |
||||||
315 | * |
||||||
316 | * Shortcut of ->where('key','=','value')->limit(1)->get(); |
||||||
317 | * |
||||||
318 | * @return \stdClass\array<mixed,mixed>|object|null Can return any object using hydrator |
||||||
0 ignored issues
–
show
The type
stdClass\array was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||||
319 | */ |
||||||
320 | public function first() |
||||||
321 | { |
||||||
322 | $this->limit(1); |
||||||
323 | $result = $this->get(); |
||||||
324 | |||||||
325 | return empty($result) ? null : $result[0]; |
||||||
326 | } |
||||||
327 | |||||||
328 | /** |
||||||
329 | * Find all matching a simple where condition. |
||||||
330 | * |
||||||
331 | * Shortcut of ->where('key','=','value')->get(); |
||||||
332 | * |
||||||
333 | * @param string $fieldName |
||||||
334 | * @param mixed $value |
||||||
335 | * |
||||||
336 | * @return array<mixed,mixed>|null Can return any object using hydrator |
||||||
337 | */ |
||||||
338 | public function findAll($fieldName, $value) |
||||||
339 | { |
||||||
340 | $this->where($fieldName, '=', $value); |
||||||
341 | |||||||
342 | return $this->get(); |
||||||
343 | } |
||||||
344 | |||||||
345 | /** |
||||||
346 | * @param string $fieldName |
||||||
347 | * @param mixed $value |
||||||
348 | * |
||||||
349 | * @return \stdClass\array<mixed,mixed>|object|null Can return any object using hydrator |
||||||
350 | */ |
||||||
351 | public function find($value, $fieldName = 'id') |
||||||
352 | { |
||||||
353 | $this->where($fieldName, '=', $value); |
||||||
354 | |||||||
355 | return $this->first(); |
||||||
356 | } |
||||||
357 | |||||||
358 | /** |
||||||
359 | * @param string $fieldName |
||||||
360 | * @param mixed $value |
||||||
361 | * |
||||||
362 | * @return \stdClass\array<mixed,mixed>|object Can return any object using hydrator |
||||||
363 | * @throws Exception If fails to find |
||||||
364 | */ |
||||||
365 | public function findOrFail($value, $fieldName = 'id') |
||||||
366 | { |
||||||
367 | $result = $this->find($value, $fieldName); |
||||||
368 | if (null === $result) { |
||||||
369 | throw new Exception("Failed to find {$fieldName}={$value}", 1); |
||||||
370 | } |
||||||
371 | return $result; |
||||||
372 | } |
||||||
373 | |||||||
374 | /** |
||||||
375 | * Used to handle all aggregation method. |
||||||
376 | * |
||||||
377 | * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/ |
||||||
378 | * |
||||||
379 | * @param string $type |
||||||
380 | * @param string|Raw $field |
||||||
381 | * |
||||||
382 | * @return float |
||||||
383 | */ |
||||||
384 | protected function aggregate(string $type, $field = '*'): float |
||||||
385 | { |
||||||
386 | // Parse a raw expression. |
||||||
387 | if ($field instanceof Raw) { |
||||||
388 | $field = $this->adapterInstance->parseRaw($field); |
||||||
389 | } |
||||||
390 | |||||||
391 | // Potentialy cast field from JSON |
||||||
392 | if ($this->jsonHandler->isJsonSelector($field)) { |
||||||
393 | $field = $this->jsonHandler->extractAndUnquoteFromJsonSelector($field); |
||||||
394 | } |
||||||
395 | |||||||
396 | // Verify that field exists |
||||||
397 | if ('*' !== $field && true === isset($this->statements['selects']) && false === \in_array($field, $this->statements['selects'], true)) { |
||||||
398 | throw new \Exception(sprintf('Failed %s query - the column %s hasn\'t been selected in the query.', $type, $field)); |
||||||
399 | } |
||||||
400 | |||||||
401 | |||||||
402 | if (false === isset($this->statements['tables'])) { |
||||||
403 | throw new Exception('No table selected'); |
||||||
404 | } |
||||||
405 | |||||||
406 | $count = $this |
||||||
407 | ->table($this->subQuery($this, 'count')) |
||||||
408 | ->select([$this->raw(sprintf('%s(%s) AS field', strtoupper($type), $field))]) |
||||||
409 | ->first(); |
||||||
410 | |||||||
411 | return true === isset($count->field) ? (float)$count->field : 0; |
||||||
412 | } |
||||||
413 | |||||||
414 | /** |
||||||
415 | * Get count of all the rows for the current query |
||||||
416 | * |
||||||
417 | * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/ |
||||||
418 | * |
||||||
419 | * @param string|Raw $field |
||||||
420 | * |
||||||
421 | * @return int |
||||||
422 | * |
||||||
423 | * @throws Exception |
||||||
424 | */ |
||||||
425 | public function count($field = '*'): int |
||||||
426 | { |
||||||
427 | return (int)$this->aggregate('count', $field); |
||||||
428 | } |
||||||
429 | |||||||
430 | /** |
||||||
431 | * Get the sum for a field in the current query |
||||||
432 | * |
||||||
433 | * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/ |
||||||
434 | * |
||||||
435 | * @param string|Raw $field |
||||||
436 | * |
||||||
437 | * @return float |
||||||
438 | * |
||||||
439 | * @throws Exception |
||||||
440 | */ |
||||||
441 | public function sum($field): float |
||||||
442 | { |
||||||
443 | return $this->aggregate('sum', $field); |
||||||
444 | } |
||||||
445 | |||||||
446 | /** |
||||||
447 | * Get the average for a field in the current query |
||||||
448 | * |
||||||
449 | * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/ |
||||||
450 | * |
||||||
451 | * @param string|Raw $field |
||||||
452 | * |
||||||
453 | * @return float |
||||||
454 | * |
||||||
455 | * @throws Exception |
||||||
456 | */ |
||||||
457 | public function average($field): float |
||||||
458 | { |
||||||
459 | return $this->aggregate('avg', $field); |
||||||
460 | } |
||||||
461 | |||||||
462 | /** |
||||||
463 | * Get the minimum for a field in the current query |
||||||
464 | * |
||||||
465 | * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/ |
||||||
466 | * |
||||||
467 | * @param string|Raw $field |
||||||
468 | * |
||||||
469 | * @return float |
||||||
470 | * |
||||||
471 | * @throws Exception |
||||||
472 | */ |
||||||
473 | public function min($field): float |
||||||
474 | { |
||||||
475 | return $this->aggregate('min', $field); |
||||||
476 | } |
||||||
477 | |||||||
478 | /** |
||||||
479 | * Get the maximum for a field in the current query |
||||||
480 | * |
||||||
481 | * @see Taken from the pecee-pixie library - https://github.com/skipperbent/pecee-pixie/ |
||||||
482 | * |
||||||
483 | * @param string|Raw $field |
||||||
484 | * |
||||||
485 | * @return float |
||||||
486 | * |
||||||
487 | * @throws Exception |
||||||
488 | */ |
||||||
489 | public function max($field): float |
||||||
490 | { |
||||||
491 | return $this->aggregate('max', $field); |
||||||
492 | } |
||||||
493 | |||||||
494 | /** |
||||||
495 | * @param string $type |
||||||
496 | * @param bool|array<mixed, mixed> $dataToBePassed |
||||||
497 | * |
||||||
498 | * @return mixed |
||||||
499 | * |
||||||
500 | * @throws Exception |
||||||
501 | */ |
||||||
502 | public function getQuery(string $type = 'select', $dataToBePassed = []) |
||||||
503 | { |
||||||
504 | $allowedTypes = ['select', 'insert', 'insertignore', 'replace', 'delete', 'update', 'criteriaonly']; |
||||||
505 | if (!in_array(strtolower($type), $allowedTypes)) { |
||||||
506 | throw new Exception($type . ' is not a known type.', 2); |
||||||
507 | } |
||||||
508 | |||||||
509 | $queryArr = $this->adapterInstance->$type($this->statements, $dataToBePassed); |
||||||
510 | |||||||
511 | return $this->container->build( |
||||||
512 | QueryObject::class, |
||||||
513 | [$queryArr['sql'], $queryArr['bindings'], $this->dbInstance] |
||||||
514 | ); |
||||||
515 | } |
||||||
516 | |||||||
517 | /** |
||||||
518 | * @param QueryBuilderHandler $queryBuilder |
||||||
519 | * @param string|null $alias |
||||||
520 | * |
||||||
521 | * @return Raw |
||||||
522 | */ |
||||||
523 | public function subQuery(QueryBuilderHandler $queryBuilder, ?string $alias = null) |
||||||
524 | { |
||||||
525 | $sql = '(' . $queryBuilder->getQuery()->getRawSql() . ')'; |
||||||
526 | if (is_string($alias) && 0 !== mb_strlen($alias)) { |
||||||
527 | $sql = $sql . ' as ' . $alias; |
||||||
528 | } |
||||||
529 | |||||||
530 | return $queryBuilder->raw($sql); |
||||||
531 | } |
||||||
532 | |||||||
533 | /** |
||||||
534 | * Handles the various insert operations based on the type. |
||||||
535 | * |
||||||
536 | * @param array<int|string, mixed|mixed[]> $data |
||||||
537 | * @param string $type |
||||||
538 | * |
||||||
539 | * @return int|int[]|mixed|null can return a single row id, array of row ids, null (for failed) or any other value short circuited from event |
||||||
540 | */ |
||||||
541 | private function doInsert(array $data, string $type) |
||||||
542 | { |
||||||
543 | $eventResult = $this->fireEvents('before-insert'); |
||||||
544 | if (!is_null($eventResult)) { |
||||||
545 | return $eventResult; |
||||||
546 | } |
||||||
547 | |||||||
548 | // If first value is not an array () not a batch insert) |
||||||
549 | if (!is_array(current($data))) { |
||||||
550 | $queryObject = $this->getQuery($type, $data); |
||||||
551 | |||||||
552 | list($preparedQuery, $executionTime) = $this->statement($queryObject->getSql(), $queryObject->getBindings()); |
||||||
553 | $this->dbInstance->get_results($preparedQuery); |
||||||
554 | |||||||
555 | // Check we have a result. |
||||||
556 | $return = 1 === $this->dbInstance->rows_affected ? $this->dbInstance->insert_id : null; |
||||||
557 | } else { |
||||||
558 | // Its a batch insert |
||||||
559 | $return = []; |
||||||
560 | $executionTime = 0; |
||||||
561 | foreach ($data as $subData) { |
||||||
562 | $queryObject = $this->getQuery($type, $subData); |
||||||
563 | |||||||
564 | list($preparedQuery, $time) = $this->statement($queryObject->getSql(), $queryObject->getBindings()); |
||||||
565 | $this->dbInstance->get_results($preparedQuery); |
||||||
566 | $executionTime += $time; |
||||||
567 | |||||||
568 | if (1 === $this->dbInstance->rows_affected) { |
||||||
569 | $return[] = $this->dbInstance->insert_id; |
||||||
570 | } |
||||||
571 | } |
||||||
572 | } |
||||||
573 | |||||||
574 | $this->fireEvents('after-insert', $return, $executionTime); |
||||||
575 | |||||||
576 | return $return; |
||||||
577 | } |
||||||
578 | |||||||
579 | /** |
||||||
580 | * @param array<int|string, mixed|mixed[]> $data either key=>value array for single or array of arrays for bulk |
||||||
581 | * |
||||||
582 | * @return int|int[]|mixed|null can return a single row id, array of row ids, null (for failed) or any other value short circuited from event |
||||||
583 | */ |
||||||
584 | public function insert($data) |
||||||
585 | { |
||||||
586 | return $this->doInsert($data, 'insert'); |
||||||
587 | } |
||||||
588 | |||||||
589 | /** |
||||||
590 | * |
||||||
591 | * @param array<int|string, mixed|mixed[]> $data either key=>value array for single or array of arrays for bulk |
||||||
592 | * |
||||||
593 | * @return int|int[]|mixed|null can return a single row id, array of row ids, null (for failed) or any other value short circuited from event |
||||||
594 | */ |
||||||
595 | public function insertIgnore($data) |
||||||
596 | { |
||||||
597 | return $this->doInsert($data, 'insertignore'); |
||||||
598 | } |
||||||
599 | |||||||
600 | /** |
||||||
601 | * |
||||||
602 | * @param array<int|string, mixed|mixed[]> $data either key=>value array for single or array of arrays for bulk |
||||||
603 | * |
||||||
604 | * @return int|int[]|mixed|null can return a single row id, array of row ids, null (for failed) or any other value short circuited from event |
||||||
605 | */ |
||||||
606 | public function replace($data) |
||||||
607 | { |
||||||
608 | return $this->doInsert($data, 'replace'); |
||||||
609 | } |
||||||
610 | |||||||
611 | /** |
||||||
612 | * @param array<string, mixed> $data |
||||||
613 | * |
||||||
614 | * @return int|null Number of row effected, null for none. |
||||||
615 | */ |
||||||
616 | public function update(array $data): ?int |
||||||
617 | { |
||||||
618 | $eventResult = $this->fireEvents('before-update'); |
||||||
619 | if (!is_null($eventResult)) { |
||||||
620 | return $eventResult; |
||||||
621 | } |
||||||
622 | $queryObject = $this->getQuery('update', $data); |
||||||
623 | $r = $this->statement($queryObject->getSql(), $queryObject->getBindings()); |
||||||
624 | list($preparedQuery, $executionTime) = $r; |
||||||
625 | $this->dbInstance()->get_results($preparedQuery); |
||||||
626 | $this->fireEvents('after-update', $queryObject, $executionTime); |
||||||
627 | |||||||
628 | return 0 !== (int) $this->dbInstance()->rows_affected |
||||||
629 | ? (int) $this->dbInstance()->rows_affected |
||||||
630 | : null; |
||||||
631 | } |
||||||
632 | |||||||
633 | /** |
||||||
634 | * Update or Insert based on the attributes. |
||||||
635 | * |
||||||
636 | * @param array<string, mixed> $attributes Conditions to check |
||||||
637 | * @param array<string, mixed> $values Values to add/update |
||||||
638 | * |
||||||
639 | * @return int|int[]|null will return row id(s) for insert and null for success/fail on update |
||||||
640 | */ |
||||||
641 | public function updateOrInsert(array $attributes, array $values = []) |
||||||
642 | { |
||||||
643 | // Check if existing post exists. |
||||||
644 | $query = clone $this; |
||||||
645 | foreach ($attributes as $column => $value) { |
||||||
646 | $query->where($column, $value); |
||||||
647 | } |
||||||
648 | |||||||
649 | // If we have a result, update it. |
||||||
650 | if (null !== $query->first()) { |
||||||
651 | foreach ($attributes as $column => $value) { |
||||||
652 | $this->where($column, $value); |
||||||
653 | } |
||||||
654 | |||||||
655 | return $this->update($values); |
||||||
656 | } |
||||||
657 | |||||||
658 | // Else insert |
||||||
659 | return $this->insert($values); |
||||||
660 | } |
||||||
661 | |||||||
662 | /** |
||||||
663 | * @param array<string, mixed> $data |
||||||
664 | * |
||||||
665 | * @return static |
||||||
666 | */ |
||||||
667 | public function onDuplicateKeyUpdate($data) |
||||||
668 | { |
||||||
669 | $this->addStatement('onduplicate', $data); |
||||||
670 | |||||||
671 | return $this; |
||||||
672 | } |
||||||
673 | |||||||
674 | /** |
||||||
675 | * @return mixed number of rows effected or shortcircuited response |
||||||
676 | */ |
||||||
677 | public function delete() |
||||||
678 | { |
||||||
679 | $eventResult = $this->fireEvents('before-delete'); |
||||||
680 | if (!is_null($eventResult)) { |
||||||
681 | return $eventResult; |
||||||
682 | } |
||||||
683 | |||||||
684 | $queryObject = $this->getQuery('delete'); |
||||||
685 | |||||||
686 | list($preparedQuery, $executionTime) = $this->statement($queryObject->getSql(), $queryObject->getBindings()); |
||||||
687 | $this->dbInstance()->get_results($preparedQuery); |
||||||
688 | $this->fireEvents('after-delete', $queryObject, $executionTime); |
||||||
689 | |||||||
690 | return $this->dbInstance()->rows_affected; |
||||||
691 | } |
||||||
692 | |||||||
693 | /** |
||||||
694 | * @param string|Raw ...$tables Single table or array of tables |
||||||
695 | * |
||||||
696 | * @return static |
||||||
697 | * |
||||||
698 | * @throws Exception |
||||||
699 | */ |
||||||
700 | public function table(...$tables) |
||||||
701 | { |
||||||
702 | $instance = $this->constructCurrentBuilderClass($this->connection); |
||||||
703 | $this->setFetchMode($this->getFetchMode(), $this->hydratorConstructorArgs); |
||||||
704 | $tables = $this->addTablePrefix($tables, false); |
||||||
705 | $instance->addStatement('tables', $tables); |
||||||
706 | |||||||
707 | return $instance; |
||||||
708 | } |
||||||
709 | |||||||
710 | /** |
||||||
711 | * @param string|Raw ...$tables Single table or array of tables |
||||||
712 | * |
||||||
713 | * @return static |
||||||
714 | */ |
||||||
715 | public function from(...$tables): self |
||||||
716 | { |
||||||
717 | $tables = $this->addTablePrefix($tables, false); |
||||||
718 | $this->addStatement('tables', $tables); |
||||||
719 | |||||||
720 | return $this; |
||||||
721 | } |
||||||
722 | |||||||
723 | /** |
||||||
724 | * @param string|string[]|Raw[]|array<string, string> $fields |
||||||
725 | * |
||||||
726 | * @return static |
||||||
727 | */ |
||||||
728 | public function select($fields): self |
||||||
729 | { |
||||||
730 | if (!is_array($fields)) { |
||||||
731 | $fields = func_get_args(); |
||||||
732 | } |
||||||
733 | |||||||
734 | foreach ($fields as $field => $alias) { |
||||||
735 | // If we have a JSON expression |
||||||
736 | if ($this->jsonHandler->isJsonSelector($field)) { |
||||||
737 | $field = $this->jsonHandler->extractAndUnquoteFromJsonSelector($field); |
||||||
738 | } |
||||||
739 | |||||||
740 | // If no alias passed, but field is for JSON. thrown an exception. |
||||||
741 | if (is_numeric($field) && is_string($alias) && $this->jsonHandler->isJsonSelector($alias)) { |
||||||
742 | throw new Exception("An alias must be used if you wish to select from JSON Object", 1); |
||||||
743 | } |
||||||
744 | |||||||
745 | // Treat each array as a single table, to retain order added |
||||||
746 | $field = is_numeric($field) |
||||||
747 | ? $field = $alias // If single colum |
||||||
0 ignored issues
–
show
|
|||||||
748 | : $field = [$field => $alias]; // Has alias |
||||||
749 | |||||||
750 | $field = $this->addTablePrefix($field); |
||||||
751 | $this->addStatement('selects', $field); |
||||||
752 | } |
||||||
753 | |||||||
754 | return $this; |
||||||
755 | } |
||||||
756 | |||||||
757 | /** |
||||||
758 | * @param string|string[]|Raw[]|array<string, string> $fields |
||||||
759 | * |
||||||
760 | * @return static |
||||||
761 | */ |
||||||
762 | public function selectDistinct($fields) |
||||||
763 | { |
||||||
764 | $this->select($fields); |
||||||
765 | $this->addStatement('distinct', true); |
||||||
766 | |||||||
767 | return $this; |
||||||
768 | } |
||||||
769 | |||||||
770 | /** |
||||||
771 | * @param string|string[] $field either the single field or an array of fields |
||||||
772 | * |
||||||
773 | * @return static |
||||||
774 | */ |
||||||
775 | public function groupBy($field): self |
||||||
776 | { |
||||||
777 | $field = $this->addTablePrefix($field); |
||||||
778 | $this->addStatement('groupBys', $field); |
||||||
779 | |||||||
780 | return $this; |
||||||
781 | } |
||||||
782 | |||||||
783 | /** |
||||||
784 | * @param string|array<string|int, mixed> $fields |
||||||
785 | * @param string $defaultDirection |
||||||
786 | * |
||||||
787 | * @return static |
||||||
788 | */ |
||||||
789 | public function orderBy($fields, string $defaultDirection = 'ASC'): self |
||||||
790 | { |
||||||
791 | if (!is_array($fields)) { |
||||||
792 | $fields = [$fields]; |
||||||
793 | } |
||||||
794 | |||||||
795 | foreach ($fields as $key => $value) { |
||||||
796 | $field = $key; |
||||||
797 | $type = $value; |
||||||
798 | if (is_int($key)) { |
||||||
799 | $field = $value; |
||||||
800 | $type = $defaultDirection; |
||||||
801 | } |
||||||
802 | |||||||
803 | if ($this->jsonHandler->isJsonSelector($field)) { |
||||||
804 | $field = $this->jsonHandler->extractAndUnquoteFromJsonSelector($field); |
||||||
805 | } |
||||||
806 | |||||||
807 | if (!$field instanceof Raw) { |
||||||
808 | $field = $this->addTablePrefix($field); |
||||||
809 | } |
||||||
810 | $this->statements['orderBys'][] = compact('field', 'type'); |
||||||
811 | } |
||||||
812 | |||||||
813 | return $this; |
||||||
814 | } |
||||||
815 | |||||||
816 | /** |
||||||
817 | * @param string|Raw $key The database column which holds the JSON value |
||||||
818 | * @param string|Raw|string[] $jsonKey The json key/index to search |
||||||
819 | * @param string $defaultDirection |
||||||
820 | * @return static |
||||||
821 | */ |
||||||
822 | public function orderByJson($key, $jsonKey, string $defaultDirection = 'ASC'): self |
||||||
823 | { |
||||||
824 | $key = $this->jsonHandler->jsonExpressionFactory()->extractAndUnquote($key, $jsonKey); |
||||||
825 | return $this->orderBy($key, $defaultDirection); |
||||||
826 | } |
||||||
827 | |||||||
828 | /** |
||||||
829 | * @param int $limit |
||||||
830 | * |
||||||
831 | * @return static |
||||||
832 | */ |
||||||
833 | public function limit(int $limit): self |
||||||
834 | { |
||||||
835 | $this->statements['limit'] = $limit; |
||||||
836 | |||||||
837 | return $this; |
||||||
838 | } |
||||||
839 | |||||||
840 | /** |
||||||
841 | * @param int $offset |
||||||
842 | * |
||||||
843 | * @return static |
||||||
844 | */ |
||||||
845 | public function offset(int $offset): self |
||||||
846 | { |
||||||
847 | $this->statements['offset'] = $offset; |
||||||
848 | |||||||
849 | return $this; |
||||||
850 | } |
||||||
851 | |||||||
852 | /** |
||||||
853 | * @param string|string[]|Raw|Raw[] $key |
||||||
854 | * @param string $operator |
||||||
855 | * @param mixed $value |
||||||
856 | * @param string $joiner |
||||||
857 | * |
||||||
858 | * @return static |
||||||
859 | */ |
||||||
860 | public function having($key, string $operator, $value, string $joiner = 'AND') |
||||||
861 | { |
||||||
862 | $key = $this->addTablePrefix($key); |
||||||
863 | $this->statements['havings'][] = compact('key', 'operator', 'value', 'joiner'); |
||||||
864 | |||||||
865 | return $this; |
||||||
866 | } |
||||||
867 | |||||||
868 | /** |
||||||
869 | * @param string|string[]|Raw|Raw[] $key |
||||||
870 | * @param string $operator |
||||||
871 | * @param mixed $value |
||||||
872 | * |
||||||
873 | * @return static |
||||||
874 | */ |
||||||
875 | public function orHaving($key, $operator, $value) |
||||||
876 | { |
||||||
877 | return $this->having($key, $operator, $value, 'OR'); |
||||||
878 | } |
||||||
879 | |||||||
880 | /** |
||||||
881 | * @param string|Raw $key |
||||||
882 | * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed |
||||||
883 | * @param mixed|null $value |
||||||
884 | * |
||||||
885 | * @return static |
||||||
886 | */ |
||||||
887 | public function where($key, $operator = null, $value = null): self |
||||||
888 | { |
||||||
889 | // If two params are given then assume operator is = |
||||||
890 | if (2 === func_num_args()) { |
||||||
891 | $value = $operator; |
||||||
892 | $operator = '='; |
||||||
893 | } |
||||||
894 | |||||||
895 | return $this->whereHandler($key, $operator, $value); |
||||||
896 | } |
||||||
897 | |||||||
898 | /** |
||||||
899 | * @param string|Raw|\Closure(QueryBuilderHandler):void $key |
||||||
900 | * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed |
||||||
901 | * @param mixed|null $value |
||||||
902 | * |
||||||
903 | * @return static |
||||||
904 | */ |
||||||
905 | public function orWhere($key, $operator = null, $value = null): self |
||||||
906 | { |
||||||
907 | // If two params are given then assume operator is = |
||||||
908 | if (2 === func_num_args()) { |
||||||
909 | $value = $operator; |
||||||
910 | $operator = '='; |
||||||
911 | } |
||||||
912 | |||||||
913 | return $this->whereHandler($key, $operator, $value, 'OR'); |
||||||
914 | } |
||||||
915 | |||||||
916 | /** |
||||||
917 | * @param string|Raw $key |
||||||
918 | * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed |
||||||
919 | * @param mixed|null $value |
||||||
920 | * |
||||||
921 | * @return static |
||||||
922 | */ |
||||||
923 | public function whereNot($key, $operator = null, $value = null): self |
||||||
924 | { |
||||||
925 | // If two params are given then assume operator is = |
||||||
926 | if (2 === func_num_args()) { |
||||||
927 | $value = $operator; |
||||||
928 | $operator = '='; |
||||||
929 | } |
||||||
930 | |||||||
931 | return $this->whereHandler($key, $operator, $value, 'AND NOT'); |
||||||
932 | } |
||||||
933 | |||||||
934 | /** |
||||||
935 | * @param string|Raw $key |
||||||
936 | * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed |
||||||
937 | * @param mixed|null $value |
||||||
938 | * |
||||||
939 | * @return static |
||||||
940 | */ |
||||||
941 | public function orWhereNot($key, $operator = null, $value = null) |
||||||
942 | { |
||||||
943 | // If two params are given then assume operator is = |
||||||
944 | if (2 === func_num_args()) { |
||||||
945 | $value = $operator; |
||||||
946 | $operator = '='; |
||||||
947 | } |
||||||
948 | |||||||
949 | return $this->whereHandler($key, $operator, $value, 'OR NOT'); |
||||||
950 | } |
||||||
951 | |||||||
952 | /** |
||||||
953 | * @param string|Raw $key |
||||||
954 | * @param mixed[]|string|Raw $values |
||||||
955 | * |
||||||
956 | * @return static |
||||||
957 | */ |
||||||
958 | public function whereIn($key, $values): self |
||||||
959 | { |
||||||
960 | return $this->whereHandler($key, 'IN', $values, 'AND'); |
||||||
961 | } |
||||||
962 | |||||||
963 | /** |
||||||
964 | * @param string|Raw $key |
||||||
965 | * @param mixed[]|string|Raw $values |
||||||
966 | * |
||||||
967 | * @return static |
||||||
968 | */ |
||||||
969 | public function whereNotIn($key, $values): self |
||||||
970 | { |
||||||
971 | return $this->whereHandler($key, 'NOT IN', $values, 'AND'); |
||||||
972 | } |
||||||
973 | |||||||
974 | /** |
||||||
975 | * @param string|Raw $key |
||||||
976 | * @param mixed[]|string|Raw $values |
||||||
977 | * |
||||||
978 | * @return static |
||||||
979 | */ |
||||||
980 | public function orWhereIn($key, $values): self |
||||||
981 | { |
||||||
982 | return $this->whereHandler($key, 'IN', $values, 'OR'); |
||||||
983 | } |
||||||
984 | |||||||
985 | /** |
||||||
986 | * @param string|Raw $key |
||||||
987 | * @param mixed[]|string|Raw $values |
||||||
988 | * |
||||||
989 | * @return static |
||||||
990 | */ |
||||||
991 | public function orWhereNotIn($key, $values): self |
||||||
992 | { |
||||||
993 | return $this->whereHandler($key, 'NOT IN', $values, 'OR'); |
||||||
994 | } |
||||||
995 | |||||||
996 | /** |
||||||
997 | * @param string|Raw $key |
||||||
998 | * @param mixed $valueFrom |
||||||
999 | * @param mixed $valueTo |
||||||
1000 | * |
||||||
1001 | * @return static |
||||||
1002 | */ |
||||||
1003 | public function whereBetween($key, $valueFrom, $valueTo): self |
||||||
1004 | { |
||||||
1005 | return $this->whereHandler($key, 'BETWEEN', [$valueFrom, $valueTo], 'AND'); |
||||||
1006 | } |
||||||
1007 | |||||||
1008 | /** |
||||||
1009 | * @param string|Raw $key |
||||||
1010 | * @param mixed $valueFrom |
||||||
1011 | * @param mixed $valueTo |
||||||
1012 | * |
||||||
1013 | * @return static |
||||||
1014 | */ |
||||||
1015 | public function orWhereBetween($key, $valueFrom, $valueTo): self |
||||||
1016 | { |
||||||
1017 | return $this->whereHandler($key, 'BETWEEN', [$valueFrom, $valueTo], 'OR'); |
||||||
1018 | } |
||||||
1019 | |||||||
1020 | /** |
||||||
1021 | * Handles all function call based where conditions |
||||||
1022 | * |
||||||
1023 | * @param string|Raw $key |
||||||
1024 | * @param string $function |
||||||
1025 | * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed |
||||||
1026 | * @param mixed|null $value |
||||||
1027 | * @return static |
||||||
1028 | */ |
||||||
1029 | protected function whereFunctionCallHandler($key, $function, $operator, $value): self |
||||||
1030 | { |
||||||
1031 | $key = \sprintf('%s(%s)', $function, $this->addTablePrefix($key)); |
||||||
0 ignored issues
–
show
It seems like
$this->addTablePrefix($key) can also be of type array<mixed,mixed> ; however, parameter $values of sprintf() does only seem to accept double|integer|string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1032 | return $this->where($key, $operator, $value); |
||||||
1033 | } |
||||||
1034 | |||||||
1035 | /** |
||||||
1036 | * @param string|Raw $key |
||||||
1037 | * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed |
||||||
1038 | * @param mixed|null $value |
||||||
1039 | * @return self |
||||||
1040 | */ |
||||||
1041 | public function whereMonth($key, $operator = null, $value = null): self |
||||||
1042 | { |
||||||
1043 | // If two params are given then assume operator is = |
||||||
1044 | if (2 === func_num_args()) { |
||||||
1045 | $value = $operator; |
||||||
1046 | $operator = '='; |
||||||
1047 | } |
||||||
1048 | return $this->whereFunctionCallHandler($key, 'MONTH', $operator, $value); |
||||||
1049 | } |
||||||
1050 | |||||||
1051 | /** |
||||||
1052 | * @param string|Raw $key |
||||||
1053 | * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed |
||||||
1054 | * @param mixed|null $value |
||||||
1055 | * @return self |
||||||
1056 | */ |
||||||
1057 | public function whereDay($key, $operator = null, $value = null): self |
||||||
1058 | { |
||||||
1059 | // If two params are given then assume operator is = |
||||||
1060 | if (2 === func_num_args()) { |
||||||
1061 | $value = $operator; |
||||||
1062 | $operator = '='; |
||||||
1063 | } |
||||||
1064 | return $this->whereFunctionCallHandler($key, 'DAY', $operator, $value); |
||||||
1065 | } |
||||||
1066 | |||||||
1067 | /** |
||||||
1068 | * @param string|Raw $key |
||||||
1069 | * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed |
||||||
1070 | * @param mixed|null $value |
||||||
1071 | * @return self |
||||||
1072 | */ |
||||||
1073 | public function whereYear($key, $operator = null, $value = null): self |
||||||
1074 | { |
||||||
1075 | // If two params are given then assume operator is = |
||||||
1076 | if (2 === func_num_args()) { |
||||||
1077 | $value = $operator; |
||||||
1078 | $operator = '='; |
||||||
1079 | } |
||||||
1080 | return $this->whereFunctionCallHandler($key, 'YEAR', $operator, $value); |
||||||
1081 | } |
||||||
1082 | |||||||
1083 | /** |
||||||
1084 | * @param string|Raw $key |
||||||
1085 | * @param string|mixed|null $operator Can be used as value, if 3rd arg not passed |
||||||
1086 | * @param mixed|null $value |
||||||
1087 | * @return self |
||||||
1088 | */ |
||||||
1089 | public function whereDate($key, $operator = null, $value = null): self |
||||||
1090 | { |
||||||
1091 | // If two params are given then assume operator is = |
||||||
1092 | if (2 === func_num_args()) { |
||||||
1093 | $value = $operator; |
||||||
1094 | $operator = '='; |
||||||
1095 | } |
||||||
1096 | return $this->whereFunctionCallHandler($key, 'DATE', $operator, $value); |
||||||
1097 | } |
||||||
1098 | |||||||
1099 | /** |
||||||
1100 | * @param string|Raw $key |
||||||
1101 | * |
||||||
1102 | * @return static |
||||||
1103 | */ |
||||||
1104 | public function whereNull($key): self |
||||||
1105 | { |
||||||
1106 | return $this->whereNullHandler($key); |
||||||
1107 | } |
||||||
1108 | |||||||
1109 | /** |
||||||
1110 | * @param string|Raw $key |
||||||
1111 | * |
||||||
1112 | * @return static |
||||||
1113 | */ |
||||||
1114 | public function whereNotNull($key): self |
||||||
1115 | { |
||||||
1116 | return $this->whereNullHandler($key, 'NOT'); |
||||||
1117 | } |
||||||
1118 | |||||||
1119 | /** |
||||||
1120 | * @param string|Raw $key |
||||||
1121 | * |
||||||
1122 | * @return static |
||||||
1123 | */ |
||||||
1124 | public function orWhereNull($key): self |
||||||
1125 | { |
||||||
1126 | return $this->whereNullHandler($key, '', 'or'); |
||||||
1127 | } |
||||||
1128 | |||||||
1129 | /** |
||||||
1130 | * @param string|Raw $key |
||||||
1131 | * |
||||||
1132 | * @return static |
||||||
1133 | */ |
||||||
1134 | public function orWhereNotNull($key): self |
||||||
1135 | { |
||||||
1136 | return $this->whereNullHandler($key, 'NOT', 'or'); |
||||||
1137 | } |
||||||
1138 | |||||||
1139 | /** |
||||||
1140 | * @param string|Raw $key |
||||||
1141 | * @param string $prefix |
||||||
1142 | * @param string $operator |
||||||
1143 | * |
||||||
1144 | * @return static |
||||||
1145 | */ |
||||||
1146 | protected function whereNullHandler($key, string $prefix = '', $operator = ''): self |
||||||
1147 | { |
||||||
1148 | $prefix = 0 === mb_strlen($prefix) ? '' : " {$prefix}"; |
||||||
1149 | |||||||
1150 | if ($key instanceof Raw) { |
||||||
1151 | $key = $this->adapterInstance->parseRaw($key); |
||||||
1152 | } |
||||||
1153 | |||||||
1154 | $key = $this->addTablePrefix($key); |
||||||
1155 | if ($key instanceof Closure) { |
||||||
1156 | throw new Exception('Key used for whereNull condition must be a string or raw exrpession.', 1); |
||||||
1157 | } |
||||||
1158 | |||||||
1159 | return $this->{$operator . 'Where'}($this->raw("{$key} IS{$prefix} NULL")); |
||||||
1160 | } |
||||||
1161 | |||||||
1162 | |||||||
1163 | /** |
||||||
1164 | * Runs a transaction |
||||||
1165 | * |
||||||
1166 | * @param \Closure(Transaction):void $callback |
||||||
1167 | * |
||||||
1168 | * @return static |
||||||
1169 | */ |
||||||
1170 | public function transaction(Closure $callback): self |
||||||
1171 | { |
||||||
1172 | try { |
||||||
1173 | // Begin the transaction |
||||||
1174 | $this->dbInstance->query('START TRANSACTION'); |
||||||
1175 | |||||||
1176 | // Get the Transaction class |
||||||
1177 | $transaction = $this->container->build(Transaction::class, [$this->connection]); |
||||||
1178 | |||||||
1179 | $this->handleTransactionCall($callback, $transaction); |
||||||
1180 | |||||||
1181 | // If no errors have been thrown or the transaction wasn't completed within |
||||||
1182 | $this->dbInstance->query('COMMIT'); |
||||||
1183 | |||||||
1184 | return $this; |
||||||
1185 | } catch (TransactionHaltException $e) { |
||||||
1186 | // Commit or rollback behavior has been handled in the closure, so exit |
||||||
1187 | return $this; |
||||||
1188 | } catch (\Exception $e) { |
||||||
1189 | // something happened, rollback changes |
||||||
1190 | $this->dbInstance->query('ROLLBACK'); |
||||||
1191 | |||||||
1192 | return $this; |
||||||
1193 | } |
||||||
1194 | } |
||||||
1195 | |||||||
1196 | /** |
||||||
1197 | * Handles the transaction call. |
||||||
1198 | * Catches any WPDB Errors (printed) |
||||||
1199 | * |
||||||
1200 | * @param Closure $callback |
||||||
1201 | * @param Transaction $transaction |
||||||
1202 | * |
||||||
1203 | * @return void |
||||||
1204 | * @throws Exception |
||||||
1205 | */ |
||||||
1206 | protected function handleTransactionCall(Closure $callback, Transaction $transaction): void |
||||||
1207 | { |
||||||
1208 | try { |
||||||
1209 | ob_start(); |
||||||
1210 | $callback($transaction); |
||||||
1211 | $output = ob_get_clean() ?: ''; |
||||||
1212 | } catch (Throwable $th) { |
||||||
1213 | ob_end_clean(); |
||||||
1214 | throw $th; |
||||||
1215 | } |
||||||
1216 | |||||||
1217 | // If we caught an error, throw an exception. |
||||||
1218 | if (0 !== mb_strlen($output)) { |
||||||
1219 | throw new Exception($output); |
||||||
1220 | } |
||||||
1221 | } |
||||||
1222 | |||||||
1223 | /*************************************************************************/ |
||||||
1224 | /*************************************************************************/ |
||||||
1225 | /*************************************************************************/ |
||||||
1226 | /** JOIN JOIN **/ |
||||||
1227 | /** JOIN **/ |
||||||
1228 | /** JOIN JOIN **/ |
||||||
1229 | /*************************************************************************/ |
||||||
1230 | /*************************************************************************/ |
||||||
1231 | /*************************************************************************/ |
||||||
1232 | |||||||
1233 | /** |
||||||
1234 | * @param string|Raw $table |
||||||
1235 | * @param string|Raw|Closure $key |
||||||
1236 | * @param string|null $operator |
||||||
1237 | * @param mixed $value |
||||||
1238 | * @param string $type |
||||||
1239 | * |
||||||
1240 | * @return static |
||||||
1241 | */ |
||||||
1242 | public function join($table, $key, ?string $operator = null, $value = null, $type = 'inner') |
||||||
1243 | { |
||||||
1244 | // Potentially cast key from JSON |
||||||
1245 | if ($this->jsonHandler->isJsonSelector($key)) { |
||||||
1246 | /** @var string $key */ |
||||||
1247 | $key = $this->jsonHandler->extractAndUnquoteFromJsonSelector($key); /** @phpstan-ignore-line */ |
||||||
1248 | } |
||||||
1249 | |||||||
1250 | // Potentially cast value from json |
||||||
1251 | if ($this->jsonHandler->isJsonSelector($value)) { |
||||||
1252 | /** @var string $value */ |
||||||
1253 | $value = $this->jsonHandler->extractAndUnquoteFromJsonSelector($value); |
||||||
1254 | } |
||||||
1255 | |||||||
1256 | if (!$key instanceof Closure) { |
||||||
1257 | $key = function ($joinBuilder) use ($key, $operator, $value) { |
||||||
1258 | $joinBuilder->on($key, $operator, $value); |
||||||
1259 | }; |
||||||
1260 | } |
||||||
1261 | |||||||
1262 | // Build a new JoinBuilder class, keep it by reference so any changes made |
||||||
1263 | // in the closure should reflect here |
||||||
1264 | $joinBuilder = $this->container->build(JoinBuilder::class, [$this->connection]); |
||||||
1265 | $joinBuilder = &$joinBuilder; |
||||||
1266 | // Call the closure with our new joinBuilder object |
||||||
1267 | $key($joinBuilder); |
||||||
1268 | $table = $this->addTablePrefix($table, false); |
||||||
1269 | // Get the criteria only query from the joinBuilder object |
||||||
1270 | $this->statements['joins'][] = compact('type', 'table', 'joinBuilder'); |
||||||
1271 | return $this; |
||||||
1272 | } |
||||||
1273 | |||||||
1274 | /** |
||||||
1275 | * @param string|Raw $table |
||||||
1276 | * @param string|Raw|Closure $key |
||||||
1277 | * @param string|null $operator |
||||||
1278 | * @param mixed $value |
||||||
1279 | * |
||||||
1280 | * @return static |
||||||
1281 | */ |
||||||
1282 | public function leftJoin($table, $key, $operator = null, $value = null) |
||||||
1283 | { |
||||||
1284 | return $this->join($table, $key, $operator, $value, 'left'); |
||||||
1285 | } |
||||||
1286 | |||||||
1287 | /** |
||||||
1288 | * @param string|Raw $table |
||||||
1289 | * @param string|Raw|Closure $key |
||||||
1290 | * @param string|null $operator |
||||||
1291 | * @param mixed $value |
||||||
1292 | * |
||||||
1293 | * @return static |
||||||
1294 | */ |
||||||
1295 | public function rightJoin($table, $key, $operator = null, $value = null) |
||||||
1296 | { |
||||||
1297 | return $this->join($table, $key, $operator, $value, 'right'); |
||||||
1298 | } |
||||||
1299 | |||||||
1300 | /** |
||||||
1301 | * @param string|Raw $table |
||||||
1302 | * @param string|Raw|Closure $key |
||||||
1303 | * @param string|null $operator |
||||||
1304 | * @param mixed $value |
||||||
1305 | * |
||||||
1306 | * @return static |
||||||
1307 | */ |
||||||
1308 | public function innerJoin($table, $key, $operator = null, $value = null) |
||||||
1309 | { |
||||||
1310 | return $this->join($table, $key, $operator, $value, 'inner'); |
||||||
1311 | } |
||||||
1312 | |||||||
1313 | /** |
||||||
1314 | * @param string|Raw $table |
||||||
1315 | * @param string|Raw|Closure $key |
||||||
1316 | * @param string|null $operator |
||||||
1317 | * @param mixed $value |
||||||
1318 | * |
||||||
1319 | * @return static |
||||||
1320 | */ |
||||||
1321 | public function crossJoin($table, $key, $operator = null, $value = null) |
||||||
1322 | { |
||||||
1323 | return $this->join($table, $key, $operator, $value, 'cross'); |
||||||
1324 | } |
||||||
1325 | |||||||
1326 | /** |
||||||
1327 | * @param string|Raw $table |
||||||
1328 | * @param string|Raw|Closure $key |
||||||
1329 | * @param string|null $operator |
||||||
1330 | * @param mixed $value |
||||||
1331 | * |
||||||
1332 | * @return static |
||||||
1333 | */ |
||||||
1334 | public function outerJoin($table, $key, $operator = null, $value = null) |
||||||
1335 | { |
||||||
1336 | return $this->join($table, $key, $operator, $value, 'full outer'); |
||||||
1337 | } |
||||||
1338 | |||||||
1339 | /** |
||||||
1340 | * Shortcut to join 2 tables on the same key name with equals |
||||||
1341 | * |
||||||
1342 | * @param string $table |
||||||
1343 | * @param string $key |
||||||
1344 | * @param string $type |
||||||
1345 | * @return self |
||||||
1346 | * @throws Exception If base table is set as more than 1 or 0 |
||||||
1347 | */ |
||||||
1348 | public function joinUsing(string $table, string $key, string $type = 'INNER'): self |
||||||
1349 | { |
||||||
1350 | if (!array_key_exists('tables', $this->statements) || count($this->statements['tables']) !== 1) { |
||||||
1351 | throw new Exception("JoinUsing can only be used with a single table set as the base of the query", 1); |
||||||
1352 | } |
||||||
1353 | $baseTable = end($this->statements['tables']); |
||||||
1354 | |||||||
1355 | // Potentialy cast key from JSON |
||||||
1356 | if ($this->jsonHandler->isJsonSelector($key)) { |
||||||
1357 | $key = $this->jsonHandler->extractAndUnquoteFromJsonSelector($key); |
||||||
1358 | } |
||||||
1359 | |||||||
1360 | $remoteKey = $table = $this->addTablePrefix("{$table}.{$key}", true); |
||||||
0 ignored issues
–
show
|
|||||||
1361 | $localKey = $table = $this->addTablePrefix("{$baseTable}.{$key}", true); |
||||||
1362 | return $this->join($table, $remoteKey, '=', $localKey, $type); |
||||||
0 ignored issues
–
show
It seems like
$table can also be of type array<mixed,mixed> ; however, parameter $table of Pixie\QueryBuilder\QueryBuilderHandler::join() does only seem to accept Pixie\QueryBuilder\Raw|string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() It seems like
$remoteKey can also be of type array<mixed,mixed> ; however, parameter $key of Pixie\QueryBuilder\QueryBuilderHandler::join() does only seem to accept Closure|Pixie\QueryBuilder\Raw|string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1363 | } |
||||||
1364 | |||||||
1365 | /** |
||||||
1366 | * Add a raw query |
||||||
1367 | * |
||||||
1368 | * @param string|Raw $value |
||||||
1369 | * @param mixed|mixed[] $bindings |
||||||
1370 | * |
||||||
1371 | * @return Raw |
||||||
1372 | */ |
||||||
1373 | public function raw($value, $bindings = []): Raw |
||||||
1374 | { |
||||||
1375 | return new Raw($value, $bindings); |
||||||
1376 | } |
||||||
1377 | |||||||
1378 | /** |
||||||
1379 | * Return wpdb instance |
||||||
1380 | * |
||||||
1381 | * @return wpdb |
||||||
1382 | */ |
||||||
1383 | public function dbInstance(): wpdb |
||||||
1384 | { |
||||||
1385 | return $this->dbInstance; |
||||||
1386 | } |
||||||
1387 | |||||||
1388 | /** |
||||||
1389 | * @param Connection $connection |
||||||
1390 | * |
||||||
1391 | * @return static |
||||||
1392 | */ |
||||||
1393 | public function setConnection(Connection $connection): self |
||||||
1394 | { |
||||||
1395 | $this->connection = $connection; |
||||||
1396 | |||||||
1397 | return $this; |
||||||
1398 | } |
||||||
1399 | |||||||
1400 | /** |
||||||
1401 | * @return Connection |
||||||
1402 | */ |
||||||
1403 | public function getConnection(): Connection |
||||||
1404 | { |
||||||
1405 | return $this->connection; |
||||||
1406 | } |
||||||
1407 | |||||||
1408 | /** |
||||||
1409 | * @param string|Raw|Closure $key |
||||||
1410 | * @param string|null $operator |
||||||
1411 | * @param mixed|null $value |
||||||
1412 | * @param string $joiner |
||||||
1413 | * |
||||||
1414 | * @return static |
||||||
1415 | */ |
||||||
1416 | protected function whereHandler($key, $operator = null, $value = null, $joiner = 'AND') |
||||||
1417 | { |
||||||
1418 | $key = $this->addTablePrefix($key); |
||||||
1419 | if ($key instanceof Raw) { |
||||||
1420 | $key = $this->adapterInstance->parseRaw($key); |
||||||
1421 | } |
||||||
1422 | |||||||
1423 | if ($this->jsonHandler->isJsonSelector($key)) { |
||||||
1424 | $key = $this->jsonHandler->extractAndUnquoteFromJsonSelector($key); |
||||||
0 ignored issues
–
show
It seems like
$key can also be of type array<mixed,mixed> ; however, parameter $selector of Pixie\JSON\JsonHandler::...quoteFromJsonSelector() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1425 | } |
||||||
1426 | |||||||
1427 | $this->statements['wheres'][] = compact('key', 'operator', 'value', 'joiner'); |
||||||
1428 | return $this; |
||||||
1429 | } |
||||||
1430 | |||||||
1431 | |||||||
1432 | |||||||
1433 | /** |
||||||
1434 | * @param string $key |
||||||
1435 | * @param mixed|mixed[]|bool $value |
||||||
1436 | * |
||||||
1437 | * @return void |
||||||
1438 | */ |
||||||
1439 | protected function addStatement($key, $value) |
||||||
1440 | { |
||||||
1441 | if (!is_array($value)) { |
||||||
1442 | $value = [$value]; |
||||||
1443 | } |
||||||
1444 | |||||||
1445 | if (!array_key_exists($key, $this->statements)) { |
||||||
1446 | $this->statements[$key] = $value; |
||||||
1447 | } else { |
||||||
1448 | $this->statements[$key] = array_merge($this->statements[$key], $value); |
||||||
1449 | } |
||||||
1450 | } |
||||||
1451 | |||||||
1452 | /** |
||||||
1453 | * @param string $event |
||||||
1454 | * @param string|Raw $table |
||||||
1455 | * |
||||||
1456 | * @return callable|null |
||||||
1457 | */ |
||||||
1458 | public function getEvent(string $event, $table = ':any'): ?callable |
||||||
1459 | { |
||||||
1460 | return $this->connection->getEventHandler()->getEvent($event, $table); |
||||||
1461 | } |
||||||
1462 | |||||||
1463 | /** |
||||||
1464 | * @param string $event |
||||||
1465 | * @param string|Raw $table |
||||||
1466 | * @param Closure $action |
||||||
1467 | * |
||||||
1468 | * @return void |
||||||
1469 | */ |
||||||
1470 | public function registerEvent($event, $table, Closure $action): void |
||||||
1471 | { |
||||||
1472 | $table = $table ?: ':any'; |
||||||
1473 | |||||||
1474 | if (':any' != $table) { |
||||||
1475 | $table = $this->addTablePrefix($table, false); |
||||||
1476 | } |
||||||
1477 | |||||||
1478 | $this->connection->getEventHandler()->registerEvent($event, $table, $action); |
||||||
0 ignored issues
–
show
It seems like
$table can also be of type array<mixed,mixed> ; however, parameter $table of Pixie\EventHandler::registerEvent() does only seem to accept null|string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1479 | } |
||||||
1480 | |||||||
1481 | /** |
||||||
1482 | * @param string $event |
||||||
1483 | * @param string|Raw $table |
||||||
1484 | * |
||||||
1485 | * @return void |
||||||
1486 | */ |
||||||
1487 | public function removeEvent(string $event, $table = ':any') |
||||||
1488 | { |
||||||
1489 | if (':any' != $table) { |
||||||
1490 | $table = $this->addTablePrefix($table, false); |
||||||
1491 | } |
||||||
1492 | |||||||
1493 | $this->connection->getEventHandler()->removeEvent($event, $table); |
||||||
0 ignored issues
–
show
It seems like
$table can also be of type array<mixed,mixed> ; however, parameter $table of Pixie\EventHandler::removeEvent() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
1494 | } |
||||||
1495 | |||||||
1496 | /** |
||||||
1497 | * @param string $event |
||||||
1498 | * |
||||||
1499 | * @return mixed |
||||||
1500 | */ |
||||||
1501 | public function fireEvents(string $event) |
||||||
1502 | { |
||||||
1503 | $params = func_get_args(); // @todo Replace this with an easier to read alteratnive |
||||||
1504 | array_unshift($params, $this); |
||||||
1505 | |||||||
1506 | return call_user_func_array([$this->connection->getEventHandler(), 'fireEvents'], $params); |
||||||
1507 | } |
||||||
1508 | |||||||
1509 | /** |
||||||
1510 | * @return array<string, mixed[]> |
||||||
1511 | */ |
||||||
1512 | public function getStatements() |
||||||
1513 | { |
||||||
1514 | return $this->statements; |
||||||
1515 | } |
||||||
1516 | |||||||
1517 | /** |
||||||
1518 | * @return string will return WPDB Fetch mode |
||||||
1519 | */ |
||||||
1520 | public function getFetchMode() |
||||||
1521 | { |
||||||
1522 | return null !== $this->fetchMode |
||||||
1523 | ? $this->fetchMode |
||||||
1524 | : \OBJECT; |
||||||
1525 | } |
||||||
1526 | |||||||
1527 | /** |
||||||
1528 | * Returns an NEW instance of the JSON builder populated with the same connection and hydrator details. |
||||||
1529 | * |
||||||
1530 | * @return JsonQueryBuilder |
||||||
1531 | */ |
||||||
1532 | public function jsonBuilder(): JsonQueryBuilder |
||||||
1533 | { |
||||||
1534 | return new JsonQueryBuilder($this->getConnection(), $this->getFetchMode(), $this->hydratorConstructorArgs); |
||||||
1535 | } |
||||||
1536 | } |
||||||
1537 |