Select::from()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1.0156

Importance

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