QueryBuilderHandler   F
last analyzed

Complexity

Total Complexity 164

Size/Duplication

Total Lines 1510
Duplicated Lines 0 %

Importance

Changes 10
Bugs 0 Features 2
Metric Value
eloc 343
c 10
b 0
f 2
dl 0
loc 1510
rs 2
wmc 164

85 Methods

Rating   Name   Duplication   Size   Complexity  
A setFetchMode() 0 6 1
A useHydrator() 0 3 1
A statement() 0 10 3
A constructCurrentBuilderClass() 0 3 1
A asObject() 0 3 1
A findAll() 0 5 1
A query() 0 5 1
A first() 0 6 2
A find() 0 5 1
A __construct() 0 28 2
A newQuery() 0 10 2
A getHydrator() 0 5 1
A setAdapterConfig() 0 4 2
B get() 0 40 10
A findOrFail() 0 7 2
A interpolateQuery() 0 3 1
B aggregate() 0 28 8
A insertIgnore() 0 3 1
A max() 0 3 1
A update() 0 15 3
A subQuery() 0 8 3
A average() 0 3 1
A replace() 0 3 1
A count() 0 3 1
A insert() 0 3 1
A min() 0 3 1
B doInsert() 0 36 6
A getQuery() 0 12 2
A sum() 0 3 1
A transaction() 0 23 3
A orWhereNotNull() 0 3 1
A whereYear() 0 8 2
A getConnection() 0 3 1
A whereNull() 0 3 1
A whereMonth() 0 8 2
A getEvent() 0 3 1
A whereNotIn() 0 3 1
A joinUsing() 0 15 4
A removeEvent() 0 7 2
A whereDay() 0 8 2
A whereIn() 0 3 1
A orWhereNot() 0 9 2
A selectDistinct() 0 6 1
A innerJoin() 0 3 1
A orWhere() 0 9 2
A join() 0 30 4
A orWhereNull() 0 3 1
A getStatements() 0 3 1
A outerJoin() 0 3 1
A groupBy() 0 6 1
A onDuplicateKeyUpdate() 0 5 1
A jsonBuilder() 0 3 1
A whereNotNull() 0 3 1
A having() 0 6 1
A orWhereNotIn() 0 3 1
A offset() 0 5 1
A leftJoin() 0 3 1
A getFetchMode() 0 5 2
A whereHandler() 0 13 3
A registerEvent() 0 9 3
A orderByJson() 0 4 1
A handleTransactionCall() 0 14 4
A whereFunctionCallHandler() 0 4 1
A limit() 0 5 1
A addStatement() 0 10 3
A fireEvents() 0 6 1
A delete() 0 14 2
A orHaving() 0 3 1
B select() 0 27 8
A whereNullHandler() 0 14 4
A crossJoin() 0 3 1
A rightJoin() 0 3 1
A orWhereBetween() 0 3 1
A updateOrInsert() 0 19 4
A raw() 0 3 1
A where() 0 9 2
A setConnection() 0 5 1
A dbInstance() 0 3 1
A orWhereIn() 0 3 1
A whereDate() 0 8 2
A table() 0 8 1
A from() 0 6 1
A whereBetween() 0 3 1
A orderBy() 0 25 6
A whereNot() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like QueryBuilderHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use QueryBuilderHandler, and based on these observations, apply Extract Interface, too.

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
The condition is_string($sqlStatement) is always true.
Loading history...
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
Bug introduced by
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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
Unused Code introduced by
The assignment to $field is dead and can be removed.
Loading history...
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
Bug introduced by
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 ignore-type  annotation

1031
        $key = \sprintf('%s(%s)', $function, /** @scrutinizer ignore-type */ $this->addTablePrefix($key));
Loading history...
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
Unused Code introduced by
The assignment to $table is dead and can be removed.
Loading history...
1361
        $localKey = $table = $this->addTablePrefix("{$baseTable}.{$key}", true);
1362
        return $this->join($table, $remoteKey, '=', $localKey, $type);
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

1362
        return $this->join(/** @scrutinizer ignore-type */ $table, $remoteKey, '=', $localKey, $type);
Loading history...
Bug introduced by
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 ignore-type  annotation

1362
        return $this->join($table, /** @scrutinizer ignore-type */ $remoteKey, '=', $localKey, $type);
Loading history...
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
Bug introduced by
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 ignore-type  annotation

1424
            $key = $this->jsonHandler->extractAndUnquoteFromJsonSelector(/** @scrutinizer ignore-type */ $key);
Loading history...
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
Bug introduced by
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 ignore-type  annotation

1478
        $this->connection->getEventHandler()->registerEvent($event, /** @scrutinizer ignore-type */ $table, $action);
Loading history...
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
Bug introduced by
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 ignore-type  annotation

1493
        $this->connection->getEventHandler()->removeEvent($event, /** @scrutinizer ignore-type */ $table);
Loading history...
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