Completed
Push — master ( 609ee9...07eb26 )
by Jared
02:17
created

WhereStatement::getConditions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/**
4
 * @author Jared King <[email protected]>
5
 *
6
 * @link http://jaredtking.com
7
 *
8
 * @copyright 2015 Jared King
9
 * @license MIT
10
 */
11
namespace JAQB\Statement;
12
13
class WhereStatement extends Statement
14
{
15
    /**
16
     * @var bool
17
     */
18
    protected $having;
19
20
    /**
21
     * @var array
22
     */
23
    protected $conditions = [];
24
25
    /**
26
     * @param bool $having when true, statement becomes a having statement
27
     */
28
    public function __construct($having = false)
29
    {
30
        $this->having = $having;
31
    }
32
33
    /**
34
     * Tells whether this statement is a HAVING statement.
35
     *
36
     * @return bool true: is HAVING, false: is WHERE
37
     */
38
    public function isHaving()
39
    {
40
        return $this->having;
41
    }
42
43
    /**
44
     * Adds a condition to the statement.
45
     *
46
     * Accepts the following forms:
47
     * 1. Equality comparison:
48
     *      addCondition('username', 'john')
49
     * 2. Comparison with custom operator:
50
     *      addCondition('balance', 100, '>')
51
     * 3. IN statement:
52
     *      addCondition('group', ['admin', 'owner'])
53
     * 4. SQL fragment:
54
     *      addCondition('name LIKE "%john%"')
55
     * 5. List of conditions to add:
56
     *      addCondition([['balance', 100, '>'],
57
     *                    ['user_id', 5]])
58
     * 6. Map of equality comparisons:
59
     *      addCondition(['username' => 'john',
60
     *                    'user_id' => 5])
61
     * 7. List of SQL fragments:
62
     *      addCondition(['first_name LIKE "%john%"',
63
     *                    'last_name LIKE "%doe%"'])
64
     *
65
     * @param array|string $field
66
     * @param string|bool  $value    condition value (optional)
67
     * @param string       $operator operator (optional)
68
     *
69
     * @return self
70
     */
71
    public function addCondition($field, $value = false, $operator = '=')
72
    {
73
        if (is_array($field) && !$value) {
74
            foreach ($field as $key => $value) {
75
                // handles #5
76
                if (is_array($value)) {
77
                    call_user_func_array([$this, 'addCondition'], $value);
78
                // handles #6
79
                } elseif (!is_numeric($key)) {
80
                    $this->addCondition($key, $value);
81
                // handles #7
82
                } else {
83
                    $this->addCondition($value);
84
                }
85
            }
86
87
            return $this;
88
        }
89
90
        // handles #4
91
        $condition = [$field];
92
93
        if (func_num_args() >= 2) {
94
            // handles #3
95
            if (is_array($value) && $operator === '=') {
96
                $operator = 'IN';
97
            } elseif (is_array($value) && $operator === '<>') {
98
                $operator = 'NOT IN';
99
            }
100
101
            // handles #1 and #2
102
            $condition[] = $operator;
103
            $condition[] = $value;
104
        }
105
106
        $this->conditions[] = $condition;
107
108
        return $this;
109
    }
110
111
    public function addConditionOr($field, $value = false, $operator = '=')
0 ignored issues
show
Unused Code introduced by
The parameter $field is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $value is not used and could be removed.

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

Loading history...
Unused Code introduced by
The parameter $operator is not used and could be removed.

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

Loading history...
112
    {
113
        $this->conditions[] = ['OR'];
114
115
        return call_user_func_array([$this, 'addCondition'], func_get_args());
116
    }
117
118
    /**
119
     * Adds a between condition to the query.
120
     *
121
     * @param string $field
122
     * @param mixed  $a     first between value
123
     * @param mixed  $b     second between value
124
     *
125
     * @return self
126
     */
127
    public function addBetweenCondition($field, $a, $b)
128
    {
129
        $this->conditions[] = [$field, 'BETWEEN', $a, $b];
130
131
        return $this;
132
    }
133
134
    /**
135
     * Adds a not between condition to the query.
136
     *
137
     * @param string $field
138
     * @param mixed  $a     first between value
139
     * @param mixed  $b     second between value
140
     *
141
     * @return self
142
     */
143
    public function addNotBetweenCondition($field, $a, $b)
144
    {
145
        $this->conditions[] = [$field, 'NOT BETWEEN', $a, $b];
146
147
        return $this;
148
    }
149
150
    /**
151
     * Gets the conditions for this statement.
152
     *
153
     * @return array
154
     */
155
    public function getConditions()
156
    {
157
        return $this->conditions;
158
    }
159
160
    public function build()
161
    {
162
        // reset the parameterized values
163
        $this->values = [];
164
165
        // build clause from conditions
166
        $clauses = [];
167
        foreach ($this->conditions as $condition) {
168
            $clauses[] = $this->buildClause($condition);
169
        }
170
171
        // remove empty values
172
        $clauses = array_filter($clauses);
173
174
        if (count($clauses) == 0) {
175
            return '';
176
        }
177
178
        $sql = (!$this->having) ? 'WHERE ' : 'HAVING ';
179
180
        return $sql.$this->implodeClauses($clauses);
181
    }
182
183
    /**
184
     * Builds a parameterized and escaped SQL fragment
185
     * for a condition that uses our own internal
186
     * representation.
187
     *
188
     * A condition is represented by an array, and can be
189
     * have one of the following forms:
190
     * i)   ['SQL fragment']
191
     * ii)  ['identifier', '=', 'value']
192
     * iii) ['identifier', 'BETWEEN', 'value', 'value']
193
     *
194
     * @param array $cond
195
     *
196
     * @return string generated SQL fragment
197
     */
198
    protected function buildClause(array $cond)
199
    {
200
        // handle SQL fragments
201
        if (count($cond) == 1) {
202
            return $cond[0];
203
        }
204
205
        // escape the identifier
206
        $cond[0] = $this->escapeIdentifier($cond[0]);
207
        if (empty($cond[0])) {
208
            return '';
209
        }
210
211
        // handle between conditions
212
        if ($cond[1] === 'BETWEEN') {
213
            return $cond[0].' BETWEEN '.$this->parameterize($cond[2]).' AND '.$this->parameterize($cond[3]);
214
        } elseif ($cond[1] === 'NOT BETWEEN') {
215
            return $cond[0].' NOT BETWEEN '.$this->parameterize($cond[2]).' AND '.$this->parameterize($cond[3]);
216
        }
217
218
        // handle NULL values
219
        if ($cond[1] === '=' && $cond[2] === null) {
220
            return $cond[0].' IS NULL';
221
        } elseif ($cond[1] === '<>' && $cond[2] === null) {
222
            return $cond[0].' IS NOT NULL';
223
        }
224
225
        // handle array values, i.e. for IN conditions
226
        if (is_array($cond[2])) {
227
            foreach ($cond[2] as &$value) {
228
                $value = $this->parameterize($value);
229
            }
230
            $cond[2] = '('.implode(',', $cond[2]).')';
231
232
        // otherwise parameterize the value
233
        } else {
234
            $cond[2] = $this->parameterize($cond[2]);
235
        }
236
237
        return implode(' ', $cond);
238
    }
239
240
    protected function implodeClauses(array $clauses)
241
    {
242
        $str = false;
243
        $or = false;
244
        foreach ($clauses as $clause) {
245
            // an 'OR' token will change the operator used
246
            // when concatenating the next clause
247
            if ($clause == 'OR') {
248
                $or = true;
249
                continue;
250
            }
251
252
            if (!$str) { // first clause needs no operator
253
                $str = $clause;
254
            } elseif ($or) {
255
                $str .= " OR $clause";
256
            } else {
257
                $str .= " AND $clause";
258
            }
259
260
            $or = false;
261
        }
262
263
        return $str;
264
    }
265
}
266