Where::jWStack()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
nc 1
nop 2
dl 0
loc 19
rs 9.9332
c 0
b 0
f 0
1
<?php
2
/**
3
 * Where statements
4
 * User: moyo
5
 * Date: 22/12/2017
6
 * Time: 4:22 PM
7
 */
8
9
namespace Carno\Database\SQL\Builders;
10
11
use Carno\Database\SQL\Builder;
12
use Carno\Database\SQL\Exception\InvalidWhereStatementException;
13
use Closure;
14
15
trait Where
16
{
17
    /**
18
     * @var array
19
     */
20
    private $bWheres = [];
21
22
    /**
23
     * @var string
24
     */
25
    private $bWGrouped = null;
26
27
    /**
28
     * @var array
29
     */
30
    private $bWGroupSTA = [];
31
32
    /**
33
     * @var int
34
     */
35
    private $bWGDepth = 0;
36
37
    /**
38
     * @return string
39
     */
40
    protected function gWheres() : string
41
    {
42
        return ($sql = $this->gWStmt()) ? sprintf(' WHERE %s', $sql) : '';
43
    }
44
45
    /**
46
     * @return string
47
     */
48
    private function gWStmt() : string
49
    {
50
        $stmt = '';
51
52
        $stash = $this->bWGrouped[$this->bWGDepth] ?? null
53
            ? $this->bWGroupSTA[$this->bWGDepth]
54
            : $this->bWheres
55
        ;
56
57
        $pack = $cond = false;
58
59
        foreach ($stash as $part) {
60
            if (substr($part, 0, 1) === '#') {
61
                $pack = true;
62
                $stmt .= substr($part, 1);
63
            } else {
64
                $cond = true;
65
                $stmt .= $stmt ? sprintf(' AND %s', $part) : $part;
66
            }
67
        }
68
69
        ($pack && !$cond) && $stmt = sprintf('1%s', $stmt);
70
71
        return $stmt;
72
    }
73
74
    /**
75
     * @param array ...$conditions
76
     * @return Builder
77
     */
78
    public function where(...$conditions) : Builder
79
    {
80
        $expr = false;
81
82
        foreach ($conditions as $condition) {
83
            if (is_array($condition)) {
84
                // expr sta
85
                $expr = true;
86
87
                // where([expr], [expr])
88
                if (isset($condition[0])) {
89
                    $this->where(...$condition);
90
                    continue;
91
                }
92
93
                // where([k1 => v1, k2 => v2], [k3 => v3])
94
                foreach ($condition as $k => $v) {
95
                    $this->jWStmt($k, $v);
96
                }
97
            } elseif ($expr) {
98
                // -> where([expr])
99
                $this->jWStmt(...(is_array($condition) ? $condition : [$condition]));
100
            } else {
101
                // where(expr)
102
                $this->jWStmt(...$conditions);
103
                break;
104
            }
105
        }
106
107
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Carno\Database\SQL\Builders\Where which includes types incompatible with the type-hinted return Carno\Database\SQL\Builder.
Loading history...
108
    }
109
110
    /**
111
     * @param mixed $current
112
     * @param mixed ...$extras
113
     */
114
    private function jWStmt($current, ...$extras) : void
115
    {
116
        if (is_string($current)) {
117
            switch (count($extras)) {
118
                case 0:
119
                    // where('sql')
120
                    $this->addWSta($current);
121
                    break;
122
                case 1:
123
                    // where('key', 'val')
124
                    $this->addWSta($this->genWSQL($current, '=', $extras[0]));
125
                    break;
126
                case 2:
127
                    // where('key', 'exp', 'val')
128
                    $this->addWSta($this->genWSQL($current, ...$extras));
129
                    break;
130
                default:
131
                    throw new InvalidWhereStatementException;
132
            }
133
        } elseif (is_integer($current)) {
134
            // where(123) primary key
135
            $this->addWSta($this->genWSQL('id', '=', $current));
136
        } else {
137
            throw new InvalidWhereStatementException;
138
        }
139
    }
140
141
    /**
142
     * Grouping "AND"
143
     * @param Closure $builder
144
     * @return Builder
145
     */
146
    public function and(Closure $builder) : Builder
147
    {
148
        return $this->jWStack($builder, 'AND');
149
    }
150
151
    /**
152
     * Grouping "OR"
153
     * @param Closure $builder
154
     * @return Builder
155
     */
156
    public function or(Closure $builder) : Builder
157
    {
158
        return $this->jWStack($builder, 'OR');
159
    }
160
161
    /**
162
     * @param Closure $builder
163
     * @param string $group
164
     * @return Builder
165
     */
166
    private function jWStack(Closure $builder, string $group) : Builder
167
    {
168
        $this->bWGDepth ++;
169
170
        $this->bWGrouped[$this->bWGDepth] = $group;
171
        $this->bWGroupSTA[$this->bWGDepth] = [];
172
173
        $builder($this);
174
175
        $stage = $this->gWStmt();
176
177
        unset($this->bWGrouped[$this->bWGDepth]);
178
        unset($this->bWGroupSTA[$this->bWGDepth]);
179
180
        $this->bWGDepth --;
181
182
        $this->addWSta(sprintf('# %s (%s)', $group, $stage));
183
184
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Carno\Database\SQL\Builders\Where which includes types incompatible with the type-hinted return Carno\Database\SQL\Builder.
Loading history...
185
    }
186
187
    /**
188
     * @param string $sql
189
     */
190
    private function addWSta(string $sql) : void
191
    {
192
        if ($this->bWGrouped[$this->bWGDepth] ?? null) {
193
            $this->bWGroupSTA[$this->bWGDepth][] = $sql;
194
        } else {
195
            $this->bWheres[] = $sql;
196
        }
197
    }
198
199
    /**
200
     * @param string $key
201
     * @param string $expr
202
     * @param mixed $val
203
     * @return string
204
     */
205
    private function genWSQL(string $key, string $expr, $val) : string
206
    {
207
        $expr = strtoupper($expr);
208
209
        switch ($expr) {
210
            case 'IN':
211
                is_array($val) && $val = sprintf('(%s)', implode(',', array_map(function ($id) {
212
                    return (int) $id;
213
                }, $val)));
214
                break;
215
            default:
216
                $val = sprintf('?%d', $this->stash($val));
0 ignored issues
show
Bug introduced by
It seems like stash() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

216
                $val = sprintf('?%d', $this->/** @scrutinizer ignore-call */ stash($val));
Loading history...
217
        }
218
219
        return sprintf('`%s` %s %s', $key, $expr, $val);
220
    }
221
}
222