Issues (265)

src/Query/Traits/JoinTrait.php (1 issue)

Severity
1
<?php
2
3
/**
4
 * This file is part of Cycle ORM package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\Database\Query\Traits;
13
14
use Closure;
15
use Cycle\Database\Exception\BuilderException;
16
use Cycle\Database\Injection\Expression;
17
use Cycle\Database\Injection\FragmentInterface;
18
use Cycle\Database\Injection\Parameter;
19
use Cycle\Database\Injection\ParameterInterface;
20
use Cycle\Database\Query\ActiveQuery;
21
22
/**
23
 * Provides ability to generate QueryCompiler JOIN tokens including ON conditions and table/column
24
 * aliases.
25
 *
26
 * Simple joins (ON userID = users.id):
27
 * $select->join('LEFT', 'info', 'userID', 'users.id');
28
 * $select->leftJoin('info', 'userID', '=', 'users.id');
29
 * $select->rightJoin('info', ['userID' => 'users.id']);
30
 *
31
 * More complex ON conditions:
32
 * $select->leftJoin('info', function($select) {
33
 *      $select->on('userID', 'users.id')->orOn('userID', 'users.masterID');
34
 * });
35
 *
36
 * To specify on conditions outside join method use "on" methods.
37
 * $select->leftJoin('info')->on('userID', '=', 'users.id');
38
 *
39
 * On methods will only support conditions based on outer table columns. You can not use parametric
40
 * values here, use "on where" conditions instead.
41
 * $select->leftJoin('info')->on('userID', '=', 'users.id')->onWhere('value', 100);
42
 *
43
 * Arguments and syntax in "on" and "onWhere" conditions is identical to "where" method defined in
44
 * AbstractWhere.
45
 * Attention, "on" and "onWhere" conditions will be applied to last registered join only!
46
 *
47
 * You can also use table aliases and use them in conditions after:
48
 * $select->join('LEFT', 'info as i')->on('i.userID', 'users.id');
49
 * $select->join('LEFT', 'info as i', function($select) {
50
 *      $select->on('i.userID', 'users.id')->orOn('i.userID', 'users.masterID');
51
 * });
52
 *
53
 * @see AbstractWhere
54
 */
55
trait JoinTrait
56
{
57
    /**
58
     * Set of join tokens with on and on where conditions associated, must be supported by
59
     * QueryCompilers.
60
     */
61
    protected array $joinTokens = [];
62
63
    /**
64
     * Name/id of last join, every ON and ON WHERE call will be associated with this join.
65
     */
66
    private int|string|null $lastJoin = null;
67
68
    /**
69
     * Register new JOIN with specified type with set of on conditions (linking one table to
70
     * another, no parametric on conditions allowed here).
71
     *
72
     * @param ActiveQuery|string $type    Join type. Allowed values, LEFT, RIGHT, INNER and etc.
73
     * @param ActiveQuery|string $outer   Joined table name (without prefix), may include AS statement.
74
     * @param string             $alias   Joined table or query alias.
75
     * @param mixed              $on      Simplified on definition linking table names (no
76
     *                                    parameters allowed) or closure.
77
     *
78
     * @throws BuilderException
79 104
     */
80
    public function join(
81
        ActiveQuery|string $type,
82
        ActiveQuery|string $outer,
83
        ?string $alias = null,
84
        mixed $on = null,
85 104
    ): self {
86 104
        $this->joinTokens[++$this->lastJoin] = [
87 104
            'outer' => $outer,
88 104
            'alias' => $alias,
89
            'type' => \strtoupper($type),
90
            'on' => [],
91
        ];
92 104
93 48
        if ($on === null) {
94
            return $this;
95
        }
96 56
97 24
        if (\is_array($on) && \array_is_list($on)) {
98
            return $this->on(...$on);
99
        }
100 32
101
        return $this->on($on);
102
    }
103
104
    /**
105
     * Register new INNER JOIN with set of on conditions (linking one table to another, no
106
     * parametric on conditions allowed here).
107
     *
108
     * @link http://www.w3schools.com/sql/sql_join_inner.asp
109
     * @see  join()
110
     *
111
     * @param ActiveQuery|string $outer Joined table name (without prefix), may include AS statement.
112
     * @param string             $alias Joined table or query alias.
113
     *
114
     * @throws BuilderException
115 16
     */
116
    public function innerJoin(ActiveQuery|string $outer, ?string $alias = null): self
117 16
    {
118 16
        $this->joinTokens[++$this->lastJoin] = [
119 16
            'outer' => $outer,
120 16
            'alias' => $alias,
121
            'type' => 'INNER',
122
            'on' => [],
123
        ];
124 16
125
        return $this;
126
    }
127
128
    /**
129
     * Register new RIGHT JOIN with set of on conditions (linking one table to another, no
130
     * parametric on conditions allowed here).
131
     *
132
     * @link http://www.w3schools.com/sql/sql_join_right.asp
133
     * @see  join()
134
     *
135
     * @param ActiveQuery|string $outer   Joined table name (without prefix), may include AS statement.
136
     * @param string             $alias   Joined table or query alias.
137
     * @param mixed              $on      Simplified on definition linking table names (no
138
     *                                    parameters allowed) or closure.
139
     *
140
     * @throws BuilderException
141 24
     */
142
    public function rightJoin(ActiveQuery|string $outer, ?string $alias = null, mixed $on = null): self
0 ignored issues
show
The parameter $on is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

142
    public function rightJoin(ActiveQuery|string $outer, ?string $alias = null, /** @scrutinizer ignore-unused */ mixed $on = null): self

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
143 24
    {
144 24
        $this->joinTokens[++$this->lastJoin] = [
145 24
            'outer' => $outer,
146 24
            'alias' => $alias,
147
            'type' => 'RIGHT',
148
            'on' => [],
149
        ];
150 24
151
        return $this;
152
    }
153
154
    /**
155
     * Register new LEFT JOIN with set of on conditions (linking one table to another, no
156
     * parametric
157
     * on conditions allowed here).
158
     *
159
     * @link http://www.w3schools.com/sql/sql_join_left.asp
160
     * @see  join()
161
     *
162
     * @param ActiveQuery|string $outer Joined table name (without prefix), may include AS statement.
163
     * @param string             $alias Joined table or query alias.
164
     *
165
     * @throws BuilderException
166 144
     */
167
    public function leftJoin(ActiveQuery|string $outer, ?string $alias = null): self
168 144
    {
169 144
        $this->joinTokens[++$this->lastJoin] = [
170 144
            'outer' => $outer,
171 144
            'alias' => $alias,
172
            'type' => 'LEFT',
173
            'on' => [],
174
        ];
175 144
176
        return $this;
177
    }
178
179
    /**
180
     * Register new FULL JOIN with set of on conditions (linking one table to another, no
181
     * parametric
182
     * on conditions allowed here).
183
     *
184
     * @link http://www.w3schools.com/sql/sql_join_full.asp
185
     * @see  join()
186
     *
187
     * @param ActiveQuery|string $outer Joined table name (without prefix), may include AS statement.
188
     * @param string             $alias Joined table or query alias.
189
     *
190
     * @return $this
191
     * @throws BuilderException
192
     *
193
     */
194
    public function fullJoin(ActiveQuery|string $outer, ?string $alias = null): self
195
    {
196
        $this->joinTokens[++$this->lastJoin] = [
197
            'outer' => $outer,
198
            'alias' => $alias,
199
            'type' => 'FULL',
200
            'on' => [],
201
        ];
202
203
        return $this;
204
    }
205
206
    /**
207
     * Simple ON condition with various set of arguments. Can only be used to link column values
208
     * together, no parametric values allowed.
209
     *
210
     * @param mixed ...$args [(column, outer column), (column, operator, outer column)]
211
     *
212
     * @throws BuilderException
213 256
     */
214
    public function on(mixed ...$args): self
215 256
    {
216 256
        $this->registerToken(
217
            'AND',
218 256
            $args,
219 256
            $this->joinTokens[$this->lastJoin]['on'],
220
            $this->onWrapper(),
221
        );
222 256
223
        return $this;
224
    }
225
226
    /**
227
     * Simple AND ON condition with various set of arguments. Can only be used to link column values
228
     * together, no parametric values allowed.
229
     *
230
     * @param mixed ...$args [(column, outer column), (column, operator, outer column)]
231
     *
232
     * @throws BuilderException
233 8
     */
234
    public function andOn(mixed ...$args): self
235 8
    {
236 8
        $this->registerToken(
237
            'AND',
238 8
            $args,
239 8
            $this->joinTokens[$this->lastJoin]['on'],
240
            $this->onWrapper(),
241
        );
242 8
243
        return $this;
244
    }
245
246
    /**
247
     * Simple OR ON condition with various set of arguments. Can only be used to link column values
248
     * together, no parametric values allowed.
249
     *
250
     * @param mixed ...$args [(column, outer column), (column, operator, outer column)]
251
     *
252
     * @throws BuilderException
253 8
     */
254
    public function orOn(mixed ...$args): self
255 8
    {
256 8
        $this->registerToken(
257
            'OR',
258 8
            $args,
259 8
            $this->joinTokens[$this->lastJoin]['on'],
260
            $this->onWrapper(),
261
        );
262 8
263
        return $this;
264
    }
265
266
    /**
267
     * Simple ON WHERE condition with various set of arguments. You can use parametric values in
268
     * such methods.
269
     *
270
     * @param mixed ...$args [(column, value), (column, operator, value)]
271
     *
272
     * @throws BuilderException
273
     *
274
     * @see AbstractWhere
275 80
     */
276
    public function onWhere(mixed ...$args): self
277 80
    {
278 80
        $this->registerToken(
279
            'AND',
280 80
            $args,
281 80
            $this->joinTokens[$this->lastJoin]['on'],
282
            $this->onWhereWrapper(),
283
        );
284 80
285
        return $this;
286
    }
287
288
    /**
289
     * Simple AND ON WHERE condition with various set of arguments. You can use parametric values in
290
     * such methods.
291
     *
292
     * @param mixed ...$args [(column, value), (column, operator, value)]
293
     *
294
     * @throws BuilderException
295
     *
296
     * @see AbstractWhere
297 8
     */
298
    public function andOnWhere(mixed ...$args): self
299 8
    {
300 8
        $this->registerToken(
301
            'AND',
302 8
            $args,
303 8
            $this->joinTokens[$this->lastJoin]['on'],
304
            $this->onWhereWrapper(),
305
        );
306 8
307
        return $this;
308
    }
309
310
    /**
311
     * Simple OR ON WHERE condition with various set of arguments. You can use parametric values in
312
     * such methods.
313
     *
314
     * @param mixed ...$args [(column, value), (column, operator, value)]
315
     *
316
     * @throws BuilderException
317
     *
318
     * @see AbstractWhere
319 8
     */
320
    public function orOnWhere(mixed ...$args): self
321 8
    {
322 8
        $this->registerToken(
323
            'OR',
324 8
            $args,
325 8
            $this->joinTokens[$this->lastJoin]['on'],
326
            $this->onWhereWrapper(),
327
        );
328 8
329
        return $this;
330
    }
331
332
    /**
333
     * Convert various amount of where function arguments into valid where token.
334
     *
335
     * @psalm-param non-empty-string $boolean Boolean joiner (AND | OR).
336
     *
337
     * @param array $params Set of parameters collected from where functions.
338
     * @param array $tokens Array to aggregate compiled tokens. Reference.
339
     * @param callable $wrapper Callback or closure used to wrap/collect every potential parameter.
340
     *
341
     * @throws BuilderException
342
     */
343
    abstract protected function registerToken(
344
        string $boolean,
345
        array $params,
346
        array &$tokens,
347
        callable $wrapper,
348
    );
349
350
    /**
351
     * Convert parameters used in JOIN ON statements into sql expressions.
352 256
     */
353
    private function onWrapper(): \Closure
354 256
    {
355 256
        return static fn($parameter) =>
356 48
            $parameter instanceof FragmentInterface || $parameter instanceof ParameterInterface
357 256
                ? $parameter
358
                : new Expression($parameter);
359
    }
360
361
    /**
362
     * Applied to every potential parameter while ON WHERE tokens generation.
363 80
     */
364
    private function onWhereWrapper(): \Closure
365 80
    {
366 80
        return static function ($parameter) {
367
            \is_array($parameter) and throw new BuilderException('Arrays must be wrapped with Parameter instance');
368
369 80
            //Wrapping all values with ParameterInterface
370 80
            return !$parameter instanceof ParameterInterface && !$parameter instanceof FragmentInterface
371 80
                ? new Parameter($parameter)
372 80
                : $parameter;
373
        };
374
    }
375
}
376