Completed
Push — PSR-11-2 ( a5ad88...7f5041 )
by Nikolaos
03:59
created

Select::join()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 31
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
c 0
b 0
f 0
dl 0
loc 31
rs 9.2222
cc 6
nc 8
nop 5
1
<?php
2
3
/**
4
 * This file is part of the Phalcon Framework.
5
 *
6
 * (c) Phalcon Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 *
11
 * Implementation of this file has been influenced by AtlasPHP
12
 *
13
 * @link    https://github.com/atlasphp/Atlas.Query
14
 * @license https://github.com/atlasphp/Atlas.Query/blob/1.x/LICENSE.md
15
 */
16
17
declare(strict_types=1);
18
19
namespace Phalcon\DataMapper\Query;
20
21
use BadMethodCallException;
22
use PDO;
23
use Phalcon\Helper\Arr;
24
25
use function array_merge;
26
use function array_shift;
27
use function call_user_func_array;
28
use function func_get_args;
29
use function implode;
30
use function ltrim;
31
use function strtoupper;
32
use function substr;
33
use function trim;
34
35
/**
36
 * Class Select
37
 *
38
 * @property string $asAlias
39
 * @property bool   $forUpdate
40
 *
41
 * @method int    fetchAffected()
42
 * @method array  fetchAll()
43
 * @method array  fetchAssoc()
44
 * @method array  fetchColumn(int $column = 0)
45
 * @method array  fetchGroup(int $flags = PDO::FETCH_ASSOC)
46
 * @method object fetchObject(string $class = 'stdClass', array $arguments = [])
47
 * @method array  fetchObjects(string $class = 'stdClass', array $arguments = [])
48
 * @method array  fetchOne()
49
 * @method array  fetchPairs()
50
 * @method mixed  fetchValue()
51
 */
52
class Select extends AbstractConditions
53
{
54
    public const JOIN_INNER   = "INNER";
55
    public const JOIN_LEFT    = "LEFT";
56
    public const JOIN_NATURAL = "NATURAL";
57
    public const JOIN_RIGHT   = "RIGHT";
58
59
    /**
60
     * @var string
61
     */
62
    protected string $asAlias = "";
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_STRING, expecting T_FUNCTION or T_CONST
Loading history...
63
64
    /**
65
     * @var bool
66
     */
67
    protected bool $forUpdate = false;
68
69
    /**
70
     * Proxied methods to the connection
71
     *
72
     * @param string $method
73
     * @param array  $params
74
     *
75
     * @return mixed
76
     */
77 4
    public function __call(string $method, array $params)
78
    {
79
        $proxied = [
80 4
            "fetchAffected" => true,
81
            "fetchAll"      => true,
82
            "fetchAssoc"    => true,
83
            "fetchCol"      => true,
84
            "fetchGroup"    => true,
85
            "fetchObject"   => true,
86
            "fetchObjects"  => true,
87
            "fetchOne"      => true,
88
            "fetchPairs"    => true,
89
            "fetchValue"    => true,
90
        ];
91
92 4
        if (isset($proxied[$method])) {
93
            return call_user_func_array(
94
                [
95 2
                    $this->connection,
96 2
                    $method,
97
                ],
98 2
                array_merge(
99
                    [
100 2
                        $this->getStatement(),
101 2
                        $this->getBindValues(),
102
                    ],
103 2
                    $params
104
                )
105
            );
106
        }
107
108 2
        throw new BadMethodCallException(
109 2
            "Unknown method: [" . $method . "]"
110
        );
111
    }
112
113
    /**
114
     * Sets a `AND` for a `HAVING` condition
115
     *
116
     * @param string     $condition
117
     * @param mixed|null $value
118
     * @param int        $type
119
     *
120
     * @return Select
121
     */
122 2
    public function andHaving(
123
        string $condition,
124
        $value = null,
125
        int $type = -1
126
    ): Select {
127 2
        $this->having($condition, $value, $type);
128
129 2
        return $this;
130
    }
131
132
    /**
133
     * The `AS` statement for the query - useful in sub-queries
134
     *
135
     * @param string $asAlias
136
     *
137
     * @return Select
138
     */
139 4
    public function asAlias(string $asAlias): Select
140
    {
141 4
        $this->asAlias = $asAlias;
142
143 4
        return $this;
144
    }
145
146
    /**
147
     * Concatenates to the most recent `HAVING` clause
148
     *
149
     * @param string     $condition
150
     * @param mixed|null $value
151
     * @param int        $type
152
     *
153
     * @return Select
154
     */
155 2
    public function appendHaving(
156
        string $condition,
157
        $value = null,
158
        int $type = -1
159
    ): Select {
160 2
        $this->appendCondition("HAVING", $condition, $value, $type);
161
162 2
        return $this;
163
    }
164
165
    /**
166
     * Concatenates to the most recent `JOIN` clause
167
     *
168
     * @param string     $condition
169
     * @param mixed|null $value
170
     * @param int        $type
171
     *
172
     * @return Select
173
     */
174 2
    public function appendJoin(
175
        string $condition,
176
        $value = null,
177
        int $type = -1
178
    ): Select {
179 2
        if (!empty($value)) {
180 2
            $condition .= $this->bind->bindInline($value, $type);
181
        }
182
183 2
        $end = Arr::lastKey($this->store["FROM"]);
184 2
        $key = Arr::lastKey($this->store["FROM"][$end]);
185
186 2
        $this->store["FROM"][$end][$key] = $this->store["FROM"][$end][$key]
187 2
            . $condition;
188
189 2
        return $this;
190
    }
191
192
    /**
193
     * The columns to select from. If a key is set in an array element, the
194
     * key will be used as the alias
195
     *
196
     * @param string ...$column
197
     *
198
     * @return Select
199
     */
200 10
    public function columns(): Select
201
    {
202 10
        $this->store["COLUMNS"] = array_merge(
203 10
            $this->store["COLUMNS"],
204 10
            func_get_args()
205
        );
206
207 10
        return $this;
208
    }
209
210
    /**
211
     * @param bool $enable
212
     *
213
     * @return Select
214
     */
215 6
    public function distinct(bool $enable = true): Select
216
    {
217 6
        $this->setFlag("DISTINCT", $enable);
218
219 6
        return $this;
220
    }
221
222
    /**
223
     * Adds table(s) in the query
224
     *
225
     * @param string $table
226
     *
227
     * @return Select
228
     */
229 60
    public function from(string $table): Select
230
    {
231 60
        $this->store["FROM"][] = [$table];
232
233 60
        return $this;
234
    }
235
236
    /**
237
     * Enable the `FOR UPDATE` for the query
238
     *
239
     * @param bool $enable
240
     *
241
     * @return Select
242
     */
243 4
    public function forUpdate(bool $enable = true): Select
244
    {
245 4
        $this->forUpdate = $enable;
246
247 4
        return $this;
248
    }
249
250
    /**
251
     * Returns the compiled SQL statement
252
     *
253
     * @return string
254
     */
255 60
    public function getStatement(): string
256
    {
257 60
        return implode("", $this->store["UNION"])
258 60
            . $this->getCurrentStatement();
259
    }
260
261
    /**
262
     * Sets the `GROUP BY`
263
     *
264
     * @param array|string $groupBy
265
     *
266
     * @return Select
267
     */
268 2
    public function groupBy($groupBy): Select
269
    {
270 2
        $this->processValue("GROUP", $groupBy);
271
272 2
        return $this;
273
    }
274
275
    /**
276
     * Whether the query has columns or not
277
     *
278
     * @return bool
279
     */
280 60
    public function hasColumns(): bool
281
    {
282 60
        return count($this->store["COLUMNS"]) > 0;
283
    }
284
285
    /**
286
     * Sets a `HAVING` condition
287
     *
288
     * @param string     $condition
289
     * @param mixed|null $value
290
     * @param int        $type
291
     *
292
     * @return Select
293
     */
294 4
    public function having(
295
        string $condition,
296
        $value = null,
297
        int $type = -1
298
    ): Select {
299 4
        $this->addCondition("HAVING", "AND ", $condition, $value, $type);
300
301 4
        return $this;
302
    }
303
304
    /**
305
     * Sets a 'JOIN' condition
306
     *
307
     * @param string     $join
308
     * @param string     $table
309
     * @param string     $condition
310
     * @param mixed|null $value
311
     * @param int        $type
312
     *
313
     * @return Select
314
     */
315 10
    public function join(
316
        string $join,
317
        string $table,
318
        string $condition,
319
        $value = null,
320
        int $type = -1
321
    ): Select {
322 10
        $join = strtoupper(trim($join));
323 10
        if (substr($join, -4) !== "JOIN") {
324 10
            $join .= " JOIN";
325
        }
326
327 10
        $condition = ltrim($condition);
328
329
        if (
330 10
            "" !== $condition
331 10
            && strtoupper(substr($condition, 0, 3)) !== "ON "
332 10
            && strtoupper(substr($condition, 0, 6)) !== "USING "
333
        ) {
334 10
            $condition = "ON " . $condition;
335
        }
336
337 10
        if (!empty($value)) {
338 2
            $condition .= $this->bind->bindInline($value, $type);
339
        }
340
341 10
        $key = Arr::lastKey($this->store["FROM"]);
342
343 10
        $this->store["FROM"][$key][] = $join . " " . $table . " " . $condition;
344
345 10
        return $this;
346
    }
347
348
    /**
349
     * Sets a `OR` for a `HAVING` condition
350
     *
351
     * @param string     $condition
352
     * @param mixed|null $value
353
     * @param int        $type
354
     *
355
     * @return Select
356
     */
357 2
    public function orHaving(
358
        string $condition,
359
        $value = null,
360
        int $type = -1
361
    ): Select {
362 2
        $this->addCondition("HAVING", "OR ", $condition, $value, $type);
363
364 2
        return $this;
365
    }
366
367
    /**
368
     * Resets the internal collections
369
     *
370
     * @return Select
371
     */
372 70
    public function reset(): Select
373
    {
374 70
        parent::reset();
375
376 70
        $this->asAlias   = "";
377 70
        $this->forUpdate = false;
378
379 70
        return $this;
380
    }
381
382
    /**
383
     * Start a sub-select
384
     *
385
     * @return Select
386
     */
387 2
    public function subSelect(): Select
388
    {
389 2
        return new Select($this->connection, $this->bind);
390
    }
391
392
    /**
393
     * Start a `UNION`
394
     *
395
     * @return Select
396
     */
397 2
    public function union(): Select
398
    {
399 2
        $this->store["UNION"][] = $this->getCurrentStatement(" UNION ");
400
401 2
        $this->reset();
402
403 2
        return $this;
404
    }
405
406
    /**
407
     * Start a `UNION ALL`
408
     *
409
     * @return Select
410
     */
411 2
    public function unionAll(): Select
412
    {
413 2
        $this->store["UNION"][] = $this->getCurrentStatement(" UNION ALL ");
414
415 2
        $this->reset();
416
417 2
        return $this;
418
    }
419
420
    /**
421
     * Statement builder
422
     *
423
     * @param string $suffix
424
     *
425
     * @return string
426
     */
427 60
    protected function getCurrentStatement(string $suffix = ""): string
428
    {
429 60
        $forUpdate = "";
430
431 60
        if ($this->forUpdate) {
432 2
            $forUpdate = " FOR UPDATE";
433
        }
434
435
        $statement = "SELECT"
436 60
            . $this->buildFlags()
437 60
            . $this->buildLimitEarly()
438 60
            . $this->buildColumns()
439 60
            . $this->buildFrom()
440 60
            . $this->buildCondition("WHERE")
441 60
            . $this->buildBy("GROUP")
442 60
            . $this->buildCondition("HAVING")
443 60
            . $this->buildBy("ORDER")
444 60
            . $this->buildLimit()
445 60
            . $forUpdate;
446
447 60
        if ("" !== $this->asAlias) {
448 4
            $statement = "(" . $statement . ") AS " . $this->asAlias;
449
        }
450
451 60
        return $statement . $suffix;
452
    }
453
454
    /**
455
     * Builds the columns list
456
     *
457
     * @return string
458
     */
459 60
    private function buildColumns(): string
460
    {
461 60
        if (!$this->hasColumns()) {
462 52
            $columns = ["*"];
463
        } else {
464 10
            $columns = $this->store["COLUMNS"];
465
        }
466
467 60
        return $this->indent($columns, ",");
468
    }
469
470
    /**
471
     * Builds the from list
472
     *
473
     * @return string
474
     */
475 60
    private function buildFrom(): string
476
    {
477 60
        $from = [];
478
479 60
        if (empty($this->store["FROM"])) {
480 2
            return "";
481
        }
482
483 58
        foreach ($this->store["FROM"] as $table) {
484 58
            $from[] = array_shift($table) . $this->indent($table);
485
        }
486
487 58
        return " FROM" . $this->indent($from, ",");
488
    }
489
}
490