1 <?php
2
3 namespace Alo\Db\Query;
4
5 use Alo\Exception\ORMException as Ormex;
6 use Alo\Exception\ORMException;
7 use PDO;
8
9 if (!defined('GEN_START')) {
10 http_response_code(404);
11 } else {
12
13 /**
14 * Abstract ORM
15 * @author Art <a.molcanovas@gmail.com>
16 */
17 abstract class AbstractQuery {
18
19 /**
20 * Bound parameters
21 * @var array
22 */
23 protected $binds;
24
25 /**
26 * The LIMIT clause
27 * @var string
28 */
29 protected $limit;
30
31 /**
32 * JOINs to perform
33 * @var array
34 */
35 protected $joins;
36
37 /**
38 * Columns to select
39 * @var array
40 */
41 protected $select;
42
43 /**
44 * Where clauses
45 * @var array
46 */
47 protected $where;
48
49 /**
50 * Table to select from
51 * @var string
52 */
53 protected $from;
54
55 /**
56 * Resets the query settings
57 * @return AbstractQuery
58 */
59 function reset() {
60 $this->binds = $this->joins = $this->select = $this->where = [];
61 $this->limit = null;
62
63 return $this;
64 }
65
66 /**
67 * Sets the table to select from
68 * @author Art <a.molcanovas@gmail.com>
69 *
70 * @param string $table Table name
71 *
72 * @return AbstractQuery
73 * @throws ORMEx When $table isn't scalar
74 */
75 function from($table) {
76 if (is_string($table)) {
77 $this->from = $table;
78 } else {
79 throw new Ormex('$table must be scalar', ORMex::E_INVALID_DATATYPE);
80 }
81
82 return $this;
83 }
84
85 /**
86 * Returns a string representation of the built query
87 * @author Art <a.molcanovas@gmail.com>
88 * @return string
89 */
90 abstract function getSQL();
91
92 /**
93 * Sets an INNER JOIN
94 * @author Art <a.molcanovas@gmail.com>
95 *
96 * @param string $table Table to join
97 * @param string $on ON condition
98 *
99 * @return AbstractQuery
100 * @throws ORMException When $table or $on isn't a string
101 */
102 function innerJoin($table, $on) {
103 return $this->abstractJoin($table, $on, 'INNER');
104 }
105
106 /**
107 * The abstract joining method
108 * @author Art <a.molcanovas@gmail.com>
109 *
110 * @param string $table Table to join
111 * @param string $on ON condition
112 * @param string $type JOIN type
113 *
114 * @return AbstractQuery
115 * @throws ORMException When $table or $on isn't a string
116 */
117 protected function abstractJoin($table, $on, $type) {
118 if (!is_string($table)) {
119 throw new ORMEx('$table must be a string', ORMEx::E_INVALID_DATATYPE);
120 } elseif (!is_string($on) && $on !== null) {
121 throw new ORMException('$on must be a string', ORMEx::E_INVALID_DATATYPE);
122 } else {
123 $this->joins[] = ['type' => $type,
124 'table' => $table,
125 'on' => $on];
126 }
127
128 return $this;
129 }
130
131 /**
132 * Performs a CROSS JOIN
133 * @author Art <a.molcanovas@gmail.com>
134 *
135 * @param string $table Table to join
136 *
137 * @return AbstractQuery
138 * @throws ORMException When $table isn't a string
139 */
140 function crossJoin($table) {
141 return $this->abstractJoin($table, null, 'CROSS');
142 }
143
144 /**
145 * performs a LEFT JOIN
146 * @author Art <a.molcanovas@gmail.com>
147 *
148 * @param string $table Table to join
149 * @param string $on ON condition
150 *
151 * @return AbstractQuery
152 * @throws ORMException When $table or $on isn't a string
153 */
154 function leftJoin($table, $on) {
155 return $this->abstractJoin($table, $on, 'LEFT');
156 }
157
158 /**
159 * performs a RIGHT JOIN
160 * @author Art <a.molcanovas@gmail.com>
161 *
162 * @param string $table Table to join
163 * @param string $on ON condition
164 *
165 * @return AbstractQuery
166 * @throws ORMException When $table or $on isn't a string
167 */
168 function rightJoin($table, $on) {
169 return $this->abstractJoin($table, $on, 'RIGHT');
170 }
171
172 /**
173 * Adds a WHERE clause. If it's not the first WHERE clause, they will be linked by AND
174 * @author Art <a.molcanovas@gmail.com>
175 *
176 * @param string $column WHERE $column
177 * @param string $modifier WHERE $column $modifier
178 * @param string $value WHERE $column $modifier $value
179 * @param bool $bind Whether to use PDO parameter binding. It is HIGHLY discouraged to set this to false.
180 *
181 * @return AbstractQuery
182 */
183 function andWhere($column, $modifier, $value, $bind = true) {
184 return $this->abstractWhere($column, $modifier, $value, $bind, 'AND');
185 }
186
187 /**
188 * The abstract WHERE builder
189 * @author Art <a.molcanovas@gmail.com>
190 *
191 * @param string $column WHERE $column
192 * @param string $modifier WHERE $column $modifier
193 * @param string $value WHERE $column $modifier $value
194 * @param bool $bind Whether to use PDO parameter binding. It is HIGHLY discouraged to set this to false.
195 * @param string $kind OR/AND (how to link multiple WHEREs)
196 *
197 * @return AbstractQuery
198 */
199 protected function abstractWhere($column, $modifier, $value, $bind, $kind) {
200 $add = ['col' => $column,
201 'mod' => $modifier,
202 'kind' => $kind];
203
204 if ($bind) {
205 $bind = ':w' . md5($column . $modifier . $value);
206 $this->binds[$bind] = ['val' => $value,
207 'type' => is_numeric($value) && substr($value, 0, 1) != '0' ?
208 PDO::PARAM_INT : $value === null ? PDO::PARAM_NULL : PDO::PARAM_STR];
209 $add['val'] = $bind;
210 } else {
211 $add['val'] = '\'' . str_replace('\'', "\\'", $value) . '\'';
212 }
213
214 $this->where[] = $add;
215
216 return $this;
217 }
218
219 /**
220 * Adds a WHERE clause. If it's not the first WHERE clause, they will be linked by OR
221 * @author Art <a.molcanovas@gmail.com>
222 *
223 * @param string $column WHERE $column
224 * @param string $modifier WHERE $column $modifier
225 * @param string $value WHERE $column $modifier $value
226 * @param bool $bind Whether to use PDO parameter binding. It is HIGHLY discouraged to set this to false.
227 *
228 * @return AbstractQuery
229 */
230 function orWhere($column, $modifier, $value, $bind = true) {
231 return $this->abstractWhere($column, $modifier, $value, $bind, 'OR');
232 }
233
234 /**
235 * Opens a bracket in the WHERE clause
236 * @author Art <a.molcanovas@gmail.com>
237 * @return AbstractQuery
238 */
239 function whereBracketOpen() {
240 $this->where[] = '(';
241
242 return $this;
243 }
244
245 /**
246 * Closes the bracket in the WHERE clause
247 * @author Art <a.molcanovas@gmail.com>
248 * @return AbstractQuery
249 */
250 function whereBracketClose() {
251 $this->where[] = ')';
252
253 return $this;
254 }
255
256 /**
257 * Adds a column or array of columns to the SELECT clause
258 * @author Art <a.molcanovas@gmail.com>
259 *
260 * @param string|array $col The column or array of columns
261 *
262 * @return AbstractQuery
263 * @throws ORMException When $col isn't a string or array of strings
264 */
265 function select($col) {
266 if (is_array($col)) {
267 foreach ($col as $c) {
268 $this->select($c);
269 }
270 } elseif (is_string($col)) {
271 $this->select[] = $col;
272 } else {
273 throw new ORMEx('$col must be a string or array of strings', Ormex::E_INVALID_DATATYPE);
274 }
275
276 return $this;
277 }
278
279 /**
280 * Sets the LIMIT clause
281 * @author Art <a.molcanovas@gmail.com>
282 *
283 * @param int $limit1 If $limit2 is not passed, the maximum amount of rows to return, otherwise the
284 * return start index desired
285 * @param int|null $limit2 The max amount of rows to return
286 *
287 * @return AbstractQuery
288 * @throws ORMException When $limit1 or $limit2 are not integers
289 */
290 function limit($limit1, $limit2 = null) {
291 if (!is_numeric($limit1)) {
292 throw new ORMEx('$limit1 must be numeric', ORMEx::E_INVALID_DATATYPE);
293 } else {
294 if ($limit2 === null) {
295 $this->limit = $limit1;
296 } elseif (!is_numeric($limit2)) {
297 throw new ORMEx('$limit2 must be numeric', ORMEx::E_INVALID_DATATYPE);
298 } else {
299 $this->limit = $limit1 . ',' . $limit2;
300 }
301 }
302
303 return $this;
304 }
305
306 /**
307 * Returns the list of bound parameters
308 * @author Art <a.molcanovas@gmail.com>
309 * @return array
310 */
311 function getBinds() {
312 $r = [];
313
314 if ($this->binds) {
315 foreach ($this->binds as $k => $v) {
316 $r[$k] = $v['val'];
317 }
318 }
319
320 return $r;
321 }
322
323 /**
324 * Returns a string representation of the built query
325 * @author Art <a.molcanovas@gmail.com>
326 * @return string
327 */
328 function __toString() {
329 return $this->getSQL();
330 }
331 }
332 }
333