Completed
Push — master ( 8c953e...7fbcf6 )
by Thomas
37s
created

QueryBuilder::orderBy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 4
nc 1
nop 3
crap 1
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 113
    public function __construct($tableName, $alias = '', EntityManager $entityManager = null)
84
    {
85 113
        $this->tableName = $tableName;
86 113
        $this->alias = $alias;
87 113
        $this->entityManager = $entityManager;
88 113
    }
89
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
     * @throws \ORM\Exceptions\NoConnection
97
     * @throws \ORM\Exceptions\NotScalar
98
     */
99 133
    protected function convertPlaceholders(
100
        $expression,
101
        $args
102
    ) {
103 133
        if (strpos($expression, '?') === false) {
104 86
            return $expression;
105
        }
106
107 91
        if (!is_array($args)) {
108 57
            $args = [$args];
109
        }
110
111 91
        $parts = explode('?', $expression);
112 91
        $expression = '';
113 91
        while ($part = array_shift($parts)) {
114 91
            $expression .= $part;
115 91
            if (count($args)) {
116 91
                $expression .= $this->getEntityManager()->escapeValue(array_shift($args));
117 40
            } elseif (count($parts)) {
118 1
                $expression .= '?';
119
            }
120
        }
121
122 91
        return $expression;
123
    }
124
125
    /**
126
     * @return EntityManager
127
     */
128 93
    public function getEntityManager()
129
    {
130 93
        return $this->entityManager ?: static::$defaultEntityManager;
131
    }
132
133
    /**
134
     * Common implementation for creating a where condition
135
     *
136
     * @param string $column   Column or expression with placeholders
137
     * @param string $operator Operator or value if operator is omited
138
     * @param string $value    Value or array of values
139
     * @return string
140
     * @throws \ORM\Exceptions\NoConnection
141
     * @throws \ORM\Exceptions\NotScalar
142
     * @internal
143
     */
144 91
    public function createWhereCondition($column, $operator = '', $value = '')
145
    {
146 91
        if (strpos($column, '?') !== false) {
147 16
            $expression = $column;
148 16
            $value      = $operator;
149 86
        } elseif (!$operator && !$value) {
150 52
            $expression = $column;
151
        } else {
152 64
            if (!$value) {
153 43
                $value = $operator;
154 43
                if (is_array($value)) {
155 14
                    $operator = 'IN';
156
                } else {
157 29
                    $operator = '=';
158
                }
159
            }
160
161 64
            $expression = $column . ' ' . $operator;
162
163 64
            if (in_array(strtoupper($operator), ['IN', 'NOT IN']) && is_array($value)) {
164 22
                $expression .= ' (?' . str_repeat(',?', count($value) - 1) . ')';
165
            } else {
166 43
                $expression .= ' ?';
167
            }
168
        }
169
170 91
        $whereCondition = $this->convertPlaceholders($expression, $value);
171
172 90
        return $whereCondition;
173
    }
174
175
    /** {@inheritdoc} */
176 2
    public function columns(array $columns = null)
177
    {
178 2
        $this->columns = $columns;
179
180 2
        return $this;
181
    }
182
183
    /** {@inheritdoc} */
184 5
    public function column($column, $args = [], $alias = '')
185
    {
186 5
        if ($this->columns === null) {
187 5
            $this->columns = [];
188
        }
189
190 5
        $expression = $this->convertPlaceholders($column, $args);
191
192 5
        $this->columns[] = $expression . ($alias ? ' AS ' . $alias : '');
193
194 5
        return $this;
195
    }
196
197
    /** @internal
198
     * @return self */
199 1
    public function close()
200
    {
201 1
        return $this;
202
    }
203
204
    /**
205
     * Common implementation for *Join methods
206
     *
207
     * @param string      $join       The join type (e. g. `LEFT JOIN`)
208
     * @param string      $tableName  Table name to join
209
     * @param string      $expression Expression to use in on clause or single column for USING
210
     * @param string      $alias      Alias for the table
211
     * @param array|mixed $args       Arguments to use in $expression
212
     * @param bool        $empty      Create an empty join (without USING and ON)
213
     * @return ParenthesisInterface|QueryBuilder
214
     * @throws \ORM\Exceptions\NoConnection
215
     * @throws \ORM\Exceptions\NotScalar
216
     * @internal
217
     */
218 42
    protected function createJoin($join, $tableName, $expression, $alias, $args, $empty)
219
    {
220 42
        $join = $join . ' ' . $tableName
221 42
                . ($alias ? ' AS ' . $alias : '');
222
223 42
        if (preg_match('/^[A-Za-z_]+$/', $expression)) {
224 4
            $join .= ' USING (' . $expression . ')';
225 4
            $this->joins[] = $join;
226 38
        } elseif ($expression) {
227 30
            $expression = $this->convertPlaceholders($expression, $args);
228
229 30
            $join .= ' ON ' . $expression;
230 30
            $this->joins[] = $join;
231 8
        } elseif ($empty) {
232 4
            $this->joins[] = $join;
233
        } else {
234 4
            return new Parenthesis(function (ParenthesisInterface $parenthesis) use ($join) {
235 4
                $join .= ' ON ' . $parenthesis->getExpression();
236 4
                $this->joins[] = $join;
237 4
                return $this;
238 4
            }, $this);
239
        }
240
241 38
        return $this;
242
    }
243
244
    /** {@inheritdoc} */
245 17
    public function join($tableName, $expression = '', $alias = '', $args = [])
246
    {
247 17
        return $this->createJoin(
248 17
            'JOIN',
249
            $tableName,
250 17
            is_string($expression) ? $expression : '',
251
            $alias,
252
            $args,
253 17
            is_bool($expression) ? $expression : false
254
        );
255
    }
256
257
    /** {@inheritdoc} */
258 11
    public function leftJoin($tableName, $expression = '', $alias = '', $args = [])
259
    {
260 11
        return $this->createJoin(
261 11
            'LEFT JOIN',
262
            $tableName,
263 11
            is_string($expression) ? $expression : '',
264
            $alias,
265
            $args,
266 11
            is_bool($expression) ? $expression : false
267
        );
268
    }
269
270
    /** {@inheritdoc} */
271 7
    public function rightJoin($tableName, $expression = '', $alias = '', $args = [])
272
    {
273 7
        return $this->createJoin(
274 7
            'RIGHT JOIN',
275
            $tableName,
276 7
            is_string($expression) ? $expression : '',
277
            $alias,
278
            $args,
279 7
            is_bool($expression) ? $expression : false
280
        );
281
    }
282
283
    /** {@inheritdoc} */
284 7
    public function fullJoin($tableName, $expression = '', $alias = '', $args = [])
285
    {
286 7
        return $this->createJoin(
287 7
            'FULL JOIN',
288
            $tableName,
289 7
            is_string($expression) ? $expression : '',
290
            $alias,
291
            $args,
292 7
            is_bool($expression) ? $expression : true
293
        );
294
    }
295
296
    /** {@inheritdoc} */
297 4
    public function groupBy($column, $args = [])
298
    {
299 4
        $this->groupBy[] = $this->convertPlaceholders($column, $args);
300
301 4
        return $this;
302
    }
303
304
    /** {@inheritdoc} */
305 5
    public function orderBy($column, $direction = self::DIRECTION_ASCENDING, $args = [])
306
    {
307 5
        $expression = $this->convertPlaceholders($column, $args);
308
309 5
        $this->orderBy[] = $expression . ' ' . $direction;
310
311 5
        return $this;
312
    }
313
314
    /** {@inheritdoc} */
315 2
    public function limit($limit)
316
    {
317 2
        $this->limit = (int) $limit;
318
319 2
        return $this;
320
    }
321
322
    /** {@inheritdoc} */
323 1
    public function offset($offset)
324
    {
325 1
        $this->offset = (int) $offset;
326
327 1
        return $this;
328
    }
329
330
    /** {@inheritdoc} */
331 153
    public function getQuery()
332
    {
333
        return 'SELECT '
334 153
               . (!empty($this->modifier) ? implode(' ', $this->modifier) . ' ' : '')
335 153
               . ($this->columns ? implode(',', $this->columns) : '*')
336 153
               . ' FROM ' . $this->tableName . ($this->alias ? ' AS ' . $this->alias : '')
337 153
               . (!empty($this->joins) ? ' ' . implode(' ', $this->joins) : '')
338 153
               . (!empty($this->where) ? ' WHERE ' . implode(' ', $this->where) : '')
339 153
               . (!empty($this->groupBy) ? ' GROUP BY ' . implode(',', $this->groupBy) : '')
340 153
               . (!empty($this->orderBy) ? ' ORDER BY ' . implode(',', $this->orderBy) : '')
341 153
               . ($this->limit ? ' LIMIT ' . $this->limit . ($this->offset ? ' OFFSET ' . $this->offset : '') : '');
342
    }
343
344
    /** {@inheritdoc} */
345 1
    public function modifier($modifier)
346
    {
347 1
        $this->modifier[] = $modifier;
348
349 1
        return $this;
350
    }
351
}
352