WPDBAdapter   F
last analyzed

Complexity

Total Complexity 95

Size/Duplication

Total Lines 712
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
eloc 265
c 3
b 0
f 1
dl 0
loc 712
rs 2
wmc 95

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A insert() 0 3 1
A interpolateQuery() 0 10 3
A criteriaOnly() 0 10 2
A getValue() 0 3 2
A update() 0 32 4
A delete() 0 24 4
A parseRaw() 0 6 2
A stringifyValues() 0 3 1
A stringifyValue() 0 13 4
B doInsert() 0 51 9
A insertIgnore() 0 3 1
A getUpdateStatement() 0 26 6
A arrayStr() 0 12 3
A replace() 0 3 1
A getType() 0 4 3
C select() 0 71 13
A concatenateQuery() 0 8 2
A inferType() 0 12 5
B buildJoin() 0 34 6
A buildCriteriaWithType() 0 18 3
C buildCriteria() 0 102 14
A wrapSanitizer() 0 20 5

How to fix   Complexity   

Complex Class

Complex classes like WPDBAdapter 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 WPDBAdapter, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Pixie\QueryBuilder;
4
5
use Closure;
6
use Pixie\Binding;
7
use Pixie\Exception;
8
9
use Pixie\Connection;
10
11
use Pixie\QueryBuilder\Raw;
12
13
use Pixie\QueryBuilder\NestedCriteria;
14
15
use function is_bool;
16
use function is_float;
17
18
class WPDBAdapter
19
{
20
    /**
21
     * @var string
22
     */
23
    protected $sanitizer = '';
24
25
    /**
26
     * @var \Pixie\Connection
27
     */
28
    protected $connection;
29
30
    /**
31
     * @var \Viocon\Container
32
     */
33
    protected $container;
34
35
    public function __construct(Connection $connection)
36
    {
37
        $this->connection = $connection;
38
        $this->container  = $this->connection->getContainer();
39
    }
40
41
    /**
42
     * Build select query string and bindings
43
     *
44
     * @param array<string|Closure, mixed|mixed[]> $statements
45
     *
46
     * @throws Exception
47
     *
48
     * @return array{sql:string,bindings:mixed[]}
49
     */
50
    public function select(array $statements): array
51
    {
52
        if (!array_key_exists('tables', $statements)) {
53
            throw new Exception('No table specified.', 3);
54
        } elseif (!array_key_exists('selects', $statements)) {
55
            $statements['selects'][] = '*';
56
        }
57
58
        // From
59
        $tables = $this->arrayStr($statements['tables'], ', ');
60
        // Select
61
        $selects = $this->arrayStr($statements['selects'], ', ');
62
63
        // Wheres
64
        list($whereCriteria, $whereBindings) = $this->buildCriteriaWithType($statements, 'wheres', 'WHERE');
65
66
        // Group bys
67
        $groupBys = '';
68
        if (isset($statements['groupBys']) && $groupBys = $this->arrayStr($statements['groupBys'], ', ')) {
69
            $groupBys = 'GROUP BY ' . $groupBys;
70
        }
71
72
        // Order bys
73
        $orderBys = '';
74
        if (isset($statements['orderBys']) && is_array($statements['orderBys'])) {
75
            foreach ($statements['orderBys'] as $orderBy) {
76
                $field = $this->wrapSanitizer($orderBy['field']);
77
                if ($field instanceof Closure) {
78
                    continue;
79
                }
80
                $orderBys .= $field . ' ' . $orderBy['type'] . ', ';
81
            }
82
83
            if ($orderBys = trim($orderBys, ', ')) {
84
                $orderBys = 'ORDER BY ' . $orderBys;
85
            }
86
        }
87
88
        // Limit and offset
89
        $limit  = isset($statements['limit']) ? 'LIMIT ' . (int) $statements['limit'] : '';
90
        $offset = isset($statements['offset']) ? 'OFFSET ' . (int) $statements['offset'] : '';
91
92
        // Having
93
        list($havingCriteria, $havingBindings) = $this->buildCriteriaWithType($statements, 'havings', 'HAVING');
94
95
        // Joins
96
        $joinString = $this->buildJoin($statements);
97
98
        /** @var string[] */
99
        $sqlArray = [
100
            'SELECT' . (isset($statements['distinct']) ? ' DISTINCT' : ''),
101
            $selects,
102
            'FROM',
103
            $tables,
104
            $joinString,
105
            $whereCriteria,
106
            $groupBys,
107
            $havingCriteria,
108
            $orderBys,
109
            $limit,
110
            $offset,
111
        ];
112
113
        $sql = $this->concatenateQuery($sqlArray);
114
115
        $bindings = array_merge(
116
            $whereBindings,
117
            $havingBindings
118
        );
119
120
        return compact('sql', 'bindings');
121
    }
122
123
    /**
124
     * Build just criteria part of the query
125
     *
126
     * @param array<string|Closure, mixed|mixed[]> $statements
127
     * @param bool $bindValues
128
     *
129
     * @return array{sql:string[]|string, bindings:array<mixed>}
130
     */
131
    public function criteriaOnly(array $statements, bool $bindValues = true): array
132
    {
133
        $sql = $bindings = [];
134
        if (!isset($statements['criteria'])) {
135
            return compact('sql', 'bindings');
136
        }
137
138
        list($sql, $bindings) = $this->buildCriteria($statements['criteria'], $bindValues);
139
140
        return compact('sql', 'bindings');
141
    }
142
143
    /**
144
     * Build a generic insert/ignore/replace query
145
     *
146
     * @param array<string|Closure, mixed|mixed[]> $statements
147
     * @param array<string, mixed> $data
148
     * @param string $type
149
     *
150
     * @return array{sql:string, bindings:mixed[]}
151
     *
152
     * @throws Exception
153
     */
154
    private function doInsert(array $statements, array $data, string $type): array
155
    {
156
        if (!isset($statements['tables'])) {
157
            throw new Exception('No table specified', 3);
158
        }
159
160
        $table = end($statements['tables']);
161
162
        $bindings = $keys = $values = [];
163
164
        foreach ($data as $key => $value) {
165
            $keys[] = $key;
166
167
            // Handle value as bindings
168
            $isBindings = $value instanceof Binding;
169
            // If this is a raw binding, extract the Raw and replace value.
170
            if ($isBindings && $value->isRaw()) {
171
                $value = $value->getValue();
172
            }
173
174
            if ($value instanceof Raw) {
175
                $values[] = $this->parseRaw($value);
176
            } elseif ($isBindings) {
177
                $values[]   =  $value->getType();
178
                $bindings[] = $value->getValue();
179
            } else {
180
                $values[]   =  $this->inferType($value);
181
                $bindings[] = $value;
182
            }
183
        }
184
185
        $sqlArray = [
186
        $type . ' INTO',
187
        $this->wrapSanitizer($table),
188
        '(' . $this->arrayStr($keys, ',') . ')',
189
        'VALUES',
190
        '(' . $this->arrayStr($values, ',') . ')',
191
        ];
192
193
        if (isset($statements['onduplicate'])) {
194
            if (count($statements['onduplicate']) < 1) {
195
                throw new Exception('No data given.', 4);
196
            }
197
            list($updateStatement, $updateBindings) = $this->getUpdateStatement($statements['onduplicate']);
198
            $sqlArray[]                             = 'ON DUPLICATE KEY UPDATE ' . $updateStatement;
199
            $bindings                               = array_merge($bindings, $updateBindings);
200
        }
201
202
        $sql = $this->concatenateQuery($this->stringifyValues($sqlArray));
203
204
        return compact('sql', 'bindings');
205
    }
206
207
    /**
208
     * Attempts to stringify an array of values.
209
     *
210
     * @param array<string|int, string|Closure> $values
211
     *
212
     * @return string[]
213
     */
214
    protected function stringifyValues(array $values): array
215
    {
216
        return array_filter(array_map([$this, 'stringifyValue'], $values));
217
    }
218
219
    /**
220
     * Attempts to stringify a single of values.
221
     *
222
     * @param string|Closure|Raw $value
223
     *
224
     * @return string|null
225
     */
226
    protected function stringifyValue($value): ?string
227
    {
228
        if ($value instanceof Closure) {
229
            $value = $value();
230
231
            return is_string($value) ? $value : null;
232
        }
233
234
        if ($value instanceof Raw) {
235
            return $this->parseRaw($value);
236
        }
237
238
        return $value;
239
    }
240
241
    /**
242
     * Build Insert query
243
     *
244
     * @param array<string|Closure, mixed|mixed[]> $statements
245
     * @param array<string, mixed> $data $data
246
     *
247
     * @return array{sql:string, bindings:mixed[]}
248
     *
249
     * @throws Exception
250
     */
251
    public function insert($statements, array $data)
252
    {
253
        return $this->doInsert($statements, $data, 'INSERT');
254
    }
255
256
    /**
257
     * Build Insert Ignore query
258
     *
259
     * @param array<string|Closure, mixed|mixed[]> $statements
260
     * @param array<string, mixed> $data $data
261
     *
262
     * @return array{sql:string, bindings:mixed[]}
263
     *
264
     * @throws Exception
265
     */
266
    public function insertIgnore($statements, array $data)
267
    {
268
        return $this->doInsert($statements, $data, 'INSERT IGNORE');
269
    }
270
271
    /**
272
     * Build Insert Ignore query
273
     *
274
     * @param array<string|Closure, mixed|mixed[]> $statements
275
     * @param array<string, mixed> $data $data
276
     *
277
     * @return array{sql:string, bindings:mixed[]}
278
     *
279
     * @throws Exception
280
     */
281
    public function replace($statements, array $data)
282
    {
283
        return $this->doInsert($statements, $data, 'REPLACE');
284
    }
285
286
    /**
287
     * Build fields assignment part of SET ... or ON DUBLICATE KEY UPDATE ... statements
288
     *
289
     * @param array<string, mixed> $data
290
     *
291
     * @return array{0:string,1:mixed[]}
292
     */
293
    private function getUpdateStatement(array $data): array
294
    {
295
        $bindings  = [];
296
        $statement = '';
297
298
        foreach ($data as $key => $value) {
299
            $isBindings = $value instanceof Binding;
300
            // If this is a raw binding, extract the Raw and replace value.
301
            if ($isBindings && $value->isRaw()) {
302
                $value = $value->getValue();
303
            }
304
305
            if ($value instanceof Raw) {
306
                $statement .= $this->stringifyValue($this->wrapSanitizer($key)) . '=' . $value . ',';
307
            } elseif ($isBindings) {
308
                $statement .= $this->stringifyValue($this->wrapSanitizer($key)) . sprintf('=%s,', $value->getType());
309
                $bindings[] = $value->getValue();
310
            } else {
311
                $statement .= $this->stringifyValue($this->wrapSanitizer($key)) . sprintf('=%s,', $this->inferType($value));
312
                $bindings[] = $value;
313
            }
314
        }
315
316
        $statement = trim($statement, ',');
317
318
        return [$statement, $bindings];
319
    }
320
321
    /**
322
     * Build update query
323
     *
324
     * @param array<string|Closure, mixed|mixed[]> $statements
325
     * @param array<string, mixed> $data
326
     *
327
     * @return array{sql:string, bindings:mixed[]}
328
     *
329
     * @throws Exception
330
     */
331
    public function update($statements, array $data)
332
    {
333
        if (!isset($statements['tables'])) {
334
            throw new Exception('No table specified', 3);
335
        } elseif (count($data) < 1) {
336
            throw new Exception('No data given.', 4);
337
        }
338
339
        $table = end($statements['tables']);
340
341
        // Update statement
342
        list($updateStatement, $bindings) = $this->getUpdateStatement($data);
343
344
        // Wheres
345
        list($whereCriteria, $whereBindings) = $this->buildCriteriaWithType($statements, 'wheres', 'WHERE');
346
347
        // Limit
348
        $limit = isset($statements['limit']) ? 'LIMIT ' . $statements['limit'] : '';
349
350
        $sqlArray = [
351
            'UPDATE',
352
            $this->wrapSanitizer($table),
353
            'SET ' . $updateStatement,
354
            $whereCriteria,
355
            $limit,
356
        ];
357
358
        $sql = $this->concatenateQuery($this->stringifyValues($sqlArray));
359
360
        $bindings = array_merge($bindings, $whereBindings);
361
362
        return compact('sql', 'bindings');
363
    }
364
365
    /**
366
     * Build delete query
367
     *
368
     * @param array<string|Closure, mixed|mixed[]> $statements
369
     *
370
     * @return array{sql:string, bindings:mixed[]}
371
     *
372
     * @throws Exception
373
     */
374
    public function delete($statements)
375
    {
376
        if (!isset($statements['tables'])) {
377
            throw new Exception('No table specified', 3);
378
        }
379
380
        $table = end($statements['tables']);
381
        // Ensure table name is a string
382
        $table = $this->stringifyValue($this->wrapSanitizer($table));
383
        if (null === $table) {
384
            throw new Exception('Table must be a valid string.', 5);
385
        }
386
387
        // Wheres
388
        list($whereCriteria, $whereBindings) = $this->buildCriteriaWithType($statements, 'wheres', 'WHERE');
389
390
        // Limit
391
        $limit = isset($statements['limit']) ? 'LIMIT ' . $statements['limit'] : '';
0 ignored issues
show
Unused Code introduced by
The assignment to $limit is dead and can be removed.
Loading history...
392
393
        $sqlArray = ['DELETE FROM', $table, $whereCriteria];
394
        $sql      = $this->concatenateQuery($sqlArray);
395
        $bindings = $whereBindings;
396
397
        return compact('sql', 'bindings');
398
    }
399
400
    /**
401
     * Array concatenating method, like implode.
402
     * But it does wrap sanitizer and trims last glue
403
     *
404
     * @param array<string|int, string> $pieces
405
     * @param string $glue
406
     *
407
     * @return string
408
     */
409
    protected function arrayStr(array $pieces, string $glue): string
410
    {
411
        $str = '';
412
        foreach ($pieces as $key => $piece) {
413
            if (!is_int($key)) {
414
                $piece = $key . ' AS ' . $piece;
415
            }
416
417
            $str .= $piece . $glue;
418
        }
419
420
        return trim($str, $glue);
421
    }
422
423
    /**
424
     * Join different part of queries with a space.
425
     *
426
     * @param array<string|int, string> $pieces
427
     *
428
     * @return string
429
     */
430
    protected function concatenateQuery(array $pieces): string
431
    {
432
        $str = '';
433
        foreach ($pieces as $piece) {
434
            $str = trim($str) . ' ' . trim($piece);
435
        }
436
437
        return trim($str);
438
    }
439
440
    /**
441
     * Gets the type of a value, either from a binding or infered
442
     *
443
     * @param mixed $value
444
     * @return string
445
     */
446
    public function getType($value): string
447
    {
448
        return $value instanceof Binding && $value->getType() !== null
449
            ? $value->getType() : $this->inferType($value) ;
450
    }
451
452
    /**
453
     * Get the value from a possible Bindings object.
454
     *
455
     * @param mixed $value
456
     * @return mixed
457
     */
458
    public function getValue($value)
459
    {
460
        return $value instanceof Binding ? $value->getValue() : $value;
461
    }
462
463
    /**
464
     * Attempts to parse a raw query, if bindings are defined then they will be bound first.
465
     *
466
     * @param Raw $raw
467
     * @requires string
468
     */
469
    public function parseRaw(Raw $raw): string
470
    {
471
        $bindings = $raw->getBindings();
472
        return 0 === count($bindings)
473
            ? (string) $raw
474
            : $this->interpolateQuery($raw->getValue(), $bindings);
475
    }
476
477
    /**
478
     * Interpolates a query
479
     *
480
     * @param string $query
481
     * @param array<mixed> $bindings
482
     * @return string
483
     */
484
    public function interpolateQuery(string $query, array $bindings = []): string
485
    {
486
        if (0 === count($bindings)) {
487
            return $query;
488
        }
489
490
491
        $bindings = array_map([$this, 'getValue'], $bindings);
492
        $query = $this->connection->getDbInstance()->prepare($query, $bindings) ;
493
        return is_string($query) ? $query : '';
0 ignored issues
show
introduced by
The condition is_string($query) is always true.
Loading history...
494
    }
495
496
    /**
497
     * Build generic criteria string and bindings from statements, like "a = b and c = ?"
498
     *
499
     * @param array<string|Closure, mixed|mixed[]> $statements
500
     * @param bool $bindValues
501
     *
502
     * @return array{0:string,1:string[]}
503
     */
504
    protected function buildCriteria(array $statements, bool $bindValues = true): array
505
    {
506
        $criteria = '';
507
        $bindings = [];
508
        foreach ($statements as $statement) {
509
            $key   = $statement['key'];
510
            $value = $statement['value'];
511
512
            // If the value is a Raw Binding, cast to raw
513
            if ($value instanceof Binding && Binding::RAW === $value->getType()) {
514
                /** @var Raw */
515
                $value = $value->getValue();
516
            }
517
518
            if (is_null($value) && $key instanceof Closure) {
519
                // We have a closure, a nested criteria
520
521
                // Build a new NestedCriteria class, keep it by reference so any changes made
522
                // in the closure should reflect here
523
                $nestedCriteria = $this->container->build(NestedCriteria::class, [$this->connection]);
524
525
                $nestedCriteria = &$nestedCriteria;
526
                // Call the closure with our new nestedCriteria object
527
                $key($nestedCriteria);
528
                // Get the criteria only query from the nestedCriteria object
529
                $queryObject = $nestedCriteria->getQuery('criteriaOnly', true);
530
                // Merge the bindings we get from nestedCriteria object
531
                $bindings = array_merge($bindings, $queryObject->getBindings());
532
                // Append the sql we get from the nestedCriteria object
533
                $criteria .= $statement['joiner'] . ' (' . $queryObject->getSql() . ') ';
534
            } elseif (is_array($value)) {
535
                // where_in or between like query
536
                $criteria .= $statement['joiner'] . ' ' . $key . ' ' . $statement['operator'];
537
538
                switch ($statement['operator']) {
539
                    case 'BETWEEN':
540
                        $bindings = array_merge($bindings, $statement['value']);
541
                        $criteria .= sprintf(
542
                            ' %s AND %s ',
543
                            $this->getType($value[0]),
544
                            $this->getType($value[1])
545
                        );
546
547
                        // Maybe cast the values bindings.
548
                        $value[0] = $this->getValue($value[0]);
549
                        $value[1] = $this->getValue($value[1]);
550
551
                        // Parse any raws.
552
                        $value = array_map(function ($value) {
0 ignored issues
show
Unused Code introduced by
The assignment to $value is dead and can be removed.
Loading history...
553
                            return $value instanceof Raw
554
                                ? $this->parseRaw($value)
555
                                : $value;
556
                        }, $value);
557
                        break;
558
                    default:
559
                        $valuePlaceholder = '';
560
                        foreach ($statement['value'] as $subValue) {
561
                            // Get its value.
562
                            if ($this->getValue($subValue) instanceof Raw) {
563
                                /** @var Raw $subValue */
564
                                $subValue = $this->getValue($subValue);
565
                                $valuePlaceholder .= sprintf('%s, ', $this->parseRaw($subValue));
566
                                continue;
567
                            }
568
569
570
                            // Add in format placeholders.
571
                            $valuePlaceholder .= sprintf('%s, ', $this->getType($subValue)); // glynn
572
                            $bindings[] = $this->getValue($subValue);
573
                        }
574
575
                        $valuePlaceholder = trim($valuePlaceholder, ', ');
576
                        $criteria .= ' (' . $valuePlaceholder . ') ';
577
                        break;
578
                }
579
            } elseif ($value instanceof Raw) {
580
                $value = $this->parseRaw($value);
581
                $criteria .= "{$statement['joiner']} {$key} {$statement['operator']} $value ";
582
            } else {
583
                // Usual where like criteria
584
                if (!$bindValues) {
585
                    // Specially for joins
586
                    // We are not binding values, lets sanitize then
587
                    $value = $this->stringifyValue($this->wrapSanitizer($value)) ?? '';
588
                    $criteria .= $statement['joiner'] . ' ' . $key . ' ' . $statement['operator'] . ' ' . $value . ' ';
589
                } elseif ($statement['key'] instanceof Raw) {
590
                    $criteria .= $statement['joiner'] . ' ' . $key . ' ';
591
                    $bindings = array_merge($bindings, $statement['key']->getBindings());
592
                } else {
593
                    // For wheres
594
                    $bindings[] = $this->getValue($value);
595
596
                    $criteria .= $statement['joiner'] . ' ' . $key . ' ' . $statement['operator'] . ' '
597
                    . $this->getType($value) . ' ';
598
                }
599
            }
600
        }
601
602
        // Clear all white spaces, and, or from beginning and white spaces from ending
603
        $criteria = preg_replace('/^(\s?AND ?|\s?OR ?)|\s$/i', '', $criteria);
604
605
        return [$criteria ?? '', $bindings];
606
    }
607
608
    /**
609
     * Asserts the types place holder based on its value
610
     *
611
     * @param mixed $value
612
     *
613
     * @return string
614
     */
615
    public function inferType($value): string
616
    {
617
        switch (true) {
618
            case is_string($value):
619
                return '%s';
620
            case \is_int($value):
621
            case is_bool($value):
622
                return '%d';
623
            case is_float($value):
624
                return '%f';
625
            default:
626
                return '';
627
        }
628
    }
629
630
    /**
631
     * Wrap values with adapter's sanitizer like, '`'
632
     *
633
     * @param string|Raw|Closure $value
634
     *
635
     * @return string|Closure
636
     */
637
    public function wrapSanitizer($value)
638
    {
639
        // Its a raw query, just cast as string, object has __toString()
640
        if ($value instanceof Raw) {
641
            return $this->parseRaw($value);
642
        } elseif ($value instanceof Closure) {
643
            return $value;
644
        }
645
646
        // Separate our table and fields which are joined with a ".",
647
        // like my_table.id
648
        $valueArr = explode('.', $value, 2);
649
650
        foreach ($valueArr as $key => $subValue) {
651
            // Don't wrap if we have *, which is not a usual field
652
            $valueArr[$key] = '*' == trim($subValue) ? $subValue : $this->sanitizer . $subValue . $this->sanitizer;
653
        }
654
655
        // Join these back with "." and return
656
        return implode('.', $valueArr);
657
    }
658
659
    /**
660
     * Build criteria string and binding with various types added, like WHERE and Having
661
     *
662
     * @param array<string|Closure, mixed|mixed[]> $statements
663
     * @param string $key
664
     * @param string $type
665
     * @param bool $bindValues
666
     *
667
     * @return array{0:string, 1:string[]}
668
     */
669
    protected function buildCriteriaWithType(array $statements, string $key, string $type, bool $bindValues = true)
670
    {
671
        $criteria = '';
672
        $bindings = [];
673
674
        if (isset($statements[$key])) {
675
            // Get the generic/adapter agnostic criteria string from parent
676
            list($criteria, $bindings) = $this->buildCriteria($statements[$key], $bindValues);
677
678
            if ($criteria) {
679
                $criteria = $type . ' ' . $criteria;
680
            }
681
        }
682
683
        // Remove any multiple whitespace.
684
        $criteria = (string) preg_replace('!\s+!', ' ', $criteria);
685
686
        return [$criteria, $bindings];
687
    }
688
689
    /**
690
     * Build join string
691
     *
692
     * @param array<string|Closure, mixed|mixed[]> $statements
693
     *
694
     * @return string
695
     */
696
    protected function buildJoin(array $statements): string
697
    {
698
        $sql = '';
699
700
        if (!array_key_exists('joins', $statements) || !is_array($statements['joins'])) {
701
            return $sql;
702
        }
703
704
        foreach ($statements['joins'] as $joinArr) {
705
            if (is_array($joinArr['table'])) {
706
                $mainTable  = $this->stringifyValue($this->wrapSanitizer($joinArr['table'][0]));
707
                $aliasTable = $this->stringifyValue($this->wrapSanitizer($joinArr['table'][1]));
708
                $table      = $mainTable . ' AS ' . $aliasTable;
709
            } else {
710
                $table = $joinArr['table'] instanceof Raw
711
                    ? $this->parseRaw($joinArr['table'])
712
                    : $this->wrapSanitizer($joinArr['table']);
713
            }
714
            $joinBuilder = $joinArr['joinBuilder'];
715
716
            /** @var string[] */
717
            $sqlArr = [
718
                $sql,
719
                strtoupper($joinArr['type']),
720
                'JOIN',
721
                $table,
722
                'ON',
723
                $joinBuilder->getQuery('criteriaOnly', false)->getSql(),
724
            ];
725
726
            $sql = $this->concatenateQuery($sqlArr);
727
        }
728
729
        return $sql;
730
    }
731
}
732