Completed
Push — master ( c19f38...530e54 )
by Jared
02:08
created

WhereStatement::buildClause()   C

Complexity

Conditions 11
Paths 8

Size

Total Lines 41
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 2
Metric Value
c 5
b 0
f 2
dl 0
loc 41
rs 5.2653
cc 11
eloc 21
nc 8
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
        } else {
87
            // handles #4
88
            $condition = [$field];
89
90
            if (func_num_args() >= 2) {
91
                // handles #3
92
                if (is_array($value)) {
93
                    $operator = 'IN';
94
                }
95
96
                // handles #1 and #2
97
                $condition[] = $operator;
98
                $condition[] = $value;
99
            }
100
101
            $this->conditions[] = $condition;
102
        }
103
104
        return $this;
105
    }
106
107
    /**
108
     * Adds a between condition to the query.
109
     *
110
     * @param string $field
111
     * @param mixed  $a     first between value
112
     * @param mixed  $b     second between value
113
     *
114
     * @return self
115
     */
116
    public function addBetweenCondition($field, $a, $b)
117
    {
118
        $this->conditions[] = [$field, 'BETWEEN', $a, $b];
119
120
        return $this;
121
    }
122
123
    /**
124
     * Adds a not between condition to the query.
125
     *
126
     * @param string $field
127
     * @param mixed  $a     first between value
128
     * @param mixed  $b     second between value
129
     *
130
     * @return self
131
     */
132
    public function addNotBetweenCondition($field, $a, $b)
133
    {
134
        $this->conditions[] = [$field, 'NOT BETWEEN', $a, $b];
135
136
        return $this;
137
    }
138
139
    /**
140
     * Gets the conditions for this statement.
141
     *
142
     * @return array
143
     */
144
    public function getConditions()
145
    {
146
        return $this->conditions;
147
    }
148
149
    /**
150
     * Builds a parameterized and escaped SQL fragment
151
     * for a condition that uses our own internal
152
     * representation.
153
     *
154
     * A condition is represented by an array, and can be
155
     * have one of the following forms:
156
     * i)   ['SQL fragment']
157
     * ii)  ['identifier', '=', 'value']
158
     * iii) ['identifier', 'BETWEEN', 'value', 'value']
159
     *
160
     * @param array $cond
161
     *
162
     * @return string generated SQL fragment
163
     */
164
    protected function buildClause(array $cond)
165
    {
166
        // handle SQL fragments
167
        if (count($cond) == 1) {
168
            return $cond[0];
169
        }
170
171
        // escape the identifier
172
        $cond[0] = $this->escapeIdentifier($cond[0]);
173
        if (empty($cond[0])) {
174
            return '';
175
        }
176
177
        // handle between conditions
178
        if ($cond[1] === 'BETWEEN') {
179
            return $cond[0].' BETWEEN '.$this->parameterize($cond[2]).' AND '.$this->parameterize($cond[3]);
180
        } elseif ($cond[1] === 'NOT BETWEEN') {
181
            return $cond[0].' NOT BETWEEN '.$this->parameterize($cond[2]).' AND '.$this->parameterize($cond[3]);
182
        }
183
184
        // handle NULL values
185
        if ($cond[1] === '=' && $cond[2] === null) {
186
            return $cond[0].' IS NULL';
187
        } elseif ($cond[1] === '<>' && $cond[2] === null) {
188
            return $cond[0].' IS NOT NULL';
189
        }
190
191
        // handle array values, i.e. for IN conditions
192
        if (is_array($cond[2])) {
193
            foreach ($cond[2] as &$value) {
194
                $value = $this->parameterize($value);
195
            }
196
            $cond[2] = '('.implode(',', $cond[2]).')';
197
198
        // otherwise parameterize the value
199
        } else {
200
            $cond[2] = $this->parameterize($cond[2]);
201
        }
202
203
        return implode(' ', $cond);
204
    }
205
206
    public function build()
207
    {
208
        // reset the parameterized values
209
        $this->values = [];
210
211
        // build clause from conditions
212
        $clauses = [];
213
        foreach ($this->conditions as $condition) {
214
            $clauses[] = $this->buildClause($condition);
215
        }
216
217
        // remove empty values
218
        $clauses = array_filter($clauses);
219
220
        if (count($clauses) == 0) {
221
            return '';
222
        }
223
224
        $sql = (!$this->having) ? 'WHERE ' : 'HAVING ';
225
226
        return $sql.implode(' AND ', $clauses);
227
    }
228
}
229