Passed
Pull Request — master (#49)
by Thomas
02:17
created

QueryBuilder   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 315
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 57
eloc 111
dl 0
loc 315
ccs 112
cts 112
cp 1
rs 5.04
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getEntityManager() 0 3 2
A buildExpression() 0 12 4
A rightJoin() 0 5 3
A getDefaultOperator() 0 6 2
A column() 0 11 3
A createJoin() 0 27 5
A limit() 0 5 1
A close() 0 3 1
A convertPlaceholders() 0 24 6
A fullJoin() 0 5 3
A createWhereCondition() 0 19 5
A columns() 0 5 1
A __construct() 0 5 1
A groupBy() 0 5 1
A join() 0 5 3
D getQuery() 0 11 10
A offset() 0 5 1
A orderBy() 0 7 1
A leftJoin() 0 5 3
A modifier() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like QueryBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use QueryBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace ORM\QueryBuilder;
4
5
use ORM\EntityManager;
6
7
/**
8
 * Build a ansi sql query / select statement
9
 *
10
 * If you need more specific queries you write them yourself. If you need just more specific where clause you can pass
11
 * them to the *where() methods.
12
 *
13
 * Supported:
14
 *  - joins with on clause (and alias)
15
 *  - joins with using (and alias)
16
 *  - where conditions
17
 *  - parenthesis
18
 *  - order by one or more columns / expressions
19
 *  - group by one or more columns / expressions
20
 *  - limit and offset
21
 *
22
 * @package ORM
23
 * @author  Thomas Flori <[email protected]>
24
 */
25
class QueryBuilder extends Parenthesis implements QueryBuilderInterface
26
{
27
    /** The table to query
28
     * @var string */
29
    protected $tableName = '';
30
31
    /** The alias of the main table
32
     * @var string */
33
    protected $alias = '';
34
35
    /** Columns to fetch (null is equal to ['*'])
36
     * @var array|null */
37
    protected $columns = null;
38
39
    /** Joins get concatenated with space
40
     * @var string[] */
41
    protected $joins = [];
42
43
    /** Limit amount of rows
44
     * @var int */
45
    protected $limit;
46
47
    /** Offset to start from
48
     * @var int */
49
    protected $offset;
50
51
    /** Group by conditions get concatenated with comma
52
     * @var string[] */
53
    protected $groupBy = [];
54
55
    /** Order by conditions get concatenated with comma
56
     * @var string[] */
57
    protected $orderBy = [];
58
59
    /** Modifiers get concatenated with space
60
     * @var string[] */
61
    protected $modifier = [];
62
63
    /** EntityManager to use for quoting
64
     * @var EntityManager */
65
    protected $entityManager;
66
67
    /** The default EntityManager to use to for quoting
68
     * @var EntityManager */
69
    public static $defaultEntityManager;
70
71
    /** @noinspection PhpMissingParentConstructorInspection */
72
    /**
73
     * Constructor
74
     *
75
     * Create a select statement for $tableName with an object oriented interface.
76
     *
77
     * It uses static::$defaultEntityManager if $entityManager is not given.
78
     *
79
     * @param string        $tableName     The main table to use in FROM clause
80
     * @param string        $alias         An alias for the table
81
     * @param EntityManager $entityManager EntityManager for quoting
82
     */
83
    public function __construct($tableName, $alias = '', EntityManager $entityManager = null)
84 142
    {
85
        $this->tableName     = $tableName;
86 142
        $this->alias         = $alias;
87 142
        $this->entityManager = $entityManager;
88 142
    }
89 142
90
    /**
91
     * Replaces question marks in $expression with $args
92
     *
93
     * @param string      $expression Expression with placeholders
94
     * @param array|mixed $args       Arguments for placeholders
95
     * @return string
96
     */
97
    protected function convertPlaceholders(
98
        $expression,
99
        $args
100 164
    ) {
101
        if (strpos($expression, '?') === false) {
102
            return $expression;
103
        }
104 164
105 88
        if (!is_array($args)) {
106
            $args = [ $args ];
107
        }
108 122
109 88
        $parts      = explode('?', $expression);
110
        $expression = '';
111
        while ($part = array_shift($parts)) {
112 122
            $expression .= $part;
113 122
            if (count($args)) {
114 122
                $expression .= $this->getEntityManager()->escapeValue(array_shift($args));
115 122
            } elseif (count($parts)) {
116 122
                $expression .= '?';
117 122
            }
118 40
        }
119 1
120
        return $expression;
121
    }
122
123 122
    /**
124
     * @return EntityManager
125
     */
126
    public function getEntityManager()
127
    {
128
        return $this->entityManager ?: static::$defaultEntityManager;
129 124
    }
130
131 124
    /**
132
     * Common implementation for creating a where condition
133
     *
134
     * @param string $column   Column or expression with placeholders
135
     * @param string $operator Operator or value if operator is omited
136
     * @param string $value    Value or array of values
137
     * @return string
138
     * @internal
139
     */
140
    public function createWhereCondition($column, $operator = null, $value = null)
141
    {
142
        if (strpos($column, '?') !== false) {
143
            $expression = $column;
144 122
            $value      = $operator;
145
        } elseif ($operator === null && $value === null) {
146 122
            $expression = $column;
147 16
        } else {
148 16
            if ($value === null) {
149 117
                $value    = $operator;
150 52
                $operator = null;
151
            }
152 95
153 73
            $expression = $this->buildExpression($column, $value, $operator);
154 73
        }
155
156
        $whereCondition = $this->convertPlaceholders($expression, $value);
157 95
158
        return $whereCondition;
159
    }
160 122
161
    private function buildExpression($column, $value, $operator = null)
162 121
    {
163
        $operator   = $operator ?: $this->getDefaultOperator($value);
164
        $expression = $column . ' ' . $operator;
165 95
166
        if (in_array(strtoupper($operator), [ 'IN', 'NOT IN' ]) && is_array($value)) {
167 95
            $expression .= ' (?' . str_repeat(',?', count($value) - 1) . ')';
168 95
        } else {
169
            $expression .= ' ?';
170 95
        }
171 22
172
        return $expression;
173 74
    }
174
175
    private function getDefaultOperator($value)
176 95
    {
177
        if (is_array($value)) {
178
            return 'IN';
179 74
        } else {
180
            return '=';
181 74
        }
182 14
    }
183
184 60
    /** {@inheritdoc} */
185
    public function columns(array $columns = null)
186
    {
187
        $this->columns = $columns;
188
189 28
        return $this;
190
    }
191 28
192
    /** {@inheritdoc} */
193 28
    public function column($column, $args = [], $alias = '')
194
    {
195
        if ($this->columns === null) {
196
            $this->columns = [];
197 5
        }
198
199 5
        $expression = $this->convertPlaceholders($column, $args);
200 5
201
        $this->columns[] = $expression . ($alias ? ' AS ' . $alias : '');
202
203 5
        return $this;
204
    }
205 5
206
    /** @internal
207 5
     * @return self */
208
    public function close()
209
    {
210
        return $this;
211
    }
212 1
213
    /**
214 1
     * Common implementation for *Join methods
215
     *
216
     * @param string      $join       The join type (e. g. `LEFT JOIN`)
217
     * @param string      $tableName  Table name to join
218
     * @param string      $expression Expression to use in on clause or single column for USING
219
     * @param string      $alias      Alias for the table
220
     * @param array|mixed $args       Arguments to use in $expression
221
     * @param bool        $empty      Create an empty join (without USING and ON)
222
     * @return ParenthesisInterface|QueryBuilder
223
     * @internal
224
     */
225
    protected function createJoin($join, $tableName, $expression, $alias, $args, $empty)
226
    {
227
        $join = $join . ' ' . $tableName
228
                . ($alias ? ' AS ' . $alias : '');
229
230
        if (preg_match('/^[A-Za-z_]+$/', $expression)) {
231 44
            $join          .= ' USING (' . $expression . ')';
232
            $this->joins[] = $join;
233 44
        } elseif ($expression) {
234 44
            $expression = $this->convertPlaceholders($expression, $args);
235
236 44
            $join          .= ' ON ' . $expression;
237 4
            $this->joins[] = $join;
238 4
        } elseif ($empty) {
239 40
            $this->joins[] = $join;
240 32
        } else {
241
            return new Parenthesis(
242 32
                function (ParenthesisInterface $parenthesis) use ($join) {
243 32
                    $join          .= ' ON ' . $parenthesis->getExpression();
244 8
                    $this->joins[] = $join;
245 4
                    return $this;
246
                },
247 4
                $this
248 4
            );
249 4
        }
250 4
251 4
        return $this;
252 4
    }
253
254
    /** {@inheritdoc} */
255
    public function join($tableName, $expression = '', $alias = '', $args = [])
256
    {
257 40
        $empty      = is_bool($expression) ? $expression : false;
258
        $expression = is_string($expression) ? $expression : '';
259
        return $this->createJoin('JOIN', $tableName, $expression, $alias, $args, $empty);
260
    }
261 19
262
    /** {@inheritdoc} */
263 19
    public function leftJoin($tableName, $expression = '', $alias = '', $args = [])
264 19
    {
265 19
        $empty      = is_bool($expression) ? $expression : false;
266
        $expression = is_string($expression) ? $expression : '';
267
        return $this->createJoin('LEFT JOIN', $tableName, $expression, $alias, $args, $empty);
268
    }
269 11
270
    /** {@inheritdoc} */
271 11
    public function rightJoin($tableName, $expression = '', $alias = '', $args = [])
272 11
    {
273 11
        $empty      = is_bool($expression) ? $expression : false;
274
        $expression = is_string($expression) ? $expression : '';
275
        return $this->createJoin('RIGHT JOIN', $tableName, $expression, $alias, $args, $empty);
276
    }
277 7
278
    /** {@inheritdoc} */
279 7
    public function fullJoin($tableName, $expression = '', $alias = '', $args = [])
280 7
    {
281 7
        $empty      = is_bool($expression) ? $expression : false;
282
        $expression = is_string($expression) ? $expression : '';
283
        return $this->createJoin('FULL JOIN', $tableName, $expression, $alias, $args, $empty);
284
    }
285 7
286
    /** {@inheritdoc} */
287 7
    public function groupBy($column, $args = [])
288 7
    {
289 7
        $this->groupBy[] = $this->convertPlaceholders($column, $args);
290
291
        return $this;
292
    }
293 4
294
    /** {@inheritdoc} */
295 4
    public function orderBy($column, $direction = self::DIRECTION_ASCENDING, $args = [])
296
    {
297 4
        $expression = $this->convertPlaceholders($column, $args);
298
299
        $this->orderBy[] = $expression . ' ' . $direction;
300
301 5
        return $this;
302
    }
303 5
304
    /** {@inheritdoc} */
305 5
    public function limit($limit)
306
    {
307 5
        $this->limit = (int) $limit;
308
309
        return $this;
310
    }
311 2
312
    /** {@inheritdoc} */
313 2
    public function offset($offset)
314
    {
315 2
        $this->offset = (int) $offset;
316
317
        return $this;
318
    }
319 1
320
    /** {@inheritdoc} */
321 1
    public function getQuery()
322
    {
323 1
        return 'SELECT '
324
               . (!empty($this->modifier) ? implode(' ', $this->modifier) . ' ' : '')
325
               . ($this->columns ? implode(',', $this->columns) : '*')
326
               . ' FROM ' . $this->tableName . ($this->alias ? ' AS ' . $this->alias : '')
327 186
               . (!empty($this->joins) ? ' ' . implode(' ', $this->joins) : '')
328
               . (!empty($this->where) ? ' WHERE ' . implode(' ', $this->where) : '')
329
               . (!empty($this->groupBy) ? ' GROUP BY ' . implode(',', $this->groupBy) : '')
330 186
               . (!empty($this->orderBy) ? ' ORDER BY ' . implode(',', $this->orderBy) : '')
331 186
               . ($this->limit ? ' LIMIT ' . $this->limit . ($this->offset ? ' OFFSET ' . $this->offset : '') : '');
332 186
    }
333 186
334 186
    /** {@inheritdoc} */
335 186
    public function modifier($modifier)
336 186
    {
337 186
        $this->modifier[] = $modifier;
338
339
        return $this;
340
    }
341
}
342