1
|
|
|
<?php |
2
|
|
|
namespace samsonframework\orm; |
3
|
|
|
|
4
|
|
|
use samsonframework\orm\exception\EntityNotFound; |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* Database query builder. |
8
|
|
|
* @author Vitaly Iegorov <[email protected]> |
9
|
|
|
* @version 2.0 |
10
|
|
|
*/ |
11
|
|
|
class Query extends QueryHandler implements QueryInterface |
12
|
|
|
{ |
13
|
|
|
/** @var string Class name for interacting with database */ |
14
|
|
|
protected $class_name; |
15
|
|
|
|
16
|
|
|
/** @var array Collection of entity field names for sorting order */ |
17
|
|
|
protected $sorting = array(); |
18
|
|
|
|
19
|
|
|
/** @var array Collection of entity field names for grouping query results */ |
20
|
|
|
protected $grouping = array(); |
21
|
|
|
|
22
|
|
|
/** @var array Collection of query results limitations */ |
23
|
|
|
protected $limitation = array(); |
24
|
|
|
|
25
|
|
|
/** @var Condition Query base entity condition group */ |
26
|
|
|
protected $own_condition; |
27
|
|
|
|
28
|
|
|
/** @var Condition Query entity condition group */ |
29
|
|
|
protected $cConditionGroup; |
30
|
|
|
|
31
|
|
|
/** @var Database Database instance */ |
32
|
|
|
protected $database; |
33
|
|
|
|
34
|
|
|
/** Serialization handler */ |
35
|
|
|
public function __sleep() |
36
|
|
|
{ |
37
|
|
|
// Do not serialize anything |
38
|
|
|
return array(); |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Query constructor. |
43
|
|
|
* |
44
|
|
|
* @param Database Database instance |
45
|
|
|
*/ |
46
|
|
|
public function __construct(Database &$database) |
47
|
|
|
{ |
48
|
|
|
$this->database = &$database; |
49
|
|
|
$this->flush(); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Reset all query parameters |
54
|
|
|
* @return self Chaining |
55
|
|
|
*/ |
56
|
|
|
public function flush() |
57
|
|
|
{ |
58
|
|
|
$this->grouping = array(); |
59
|
|
|
$this->limitation = array(); |
60
|
|
|
$this->sorting = array(); |
61
|
|
|
|
62
|
|
|
$this->cConditionGroup = new Condition(); |
63
|
|
|
$this->own_condition = new Condition(); |
64
|
|
|
|
65
|
|
|
return $this; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Proxy function for performing database request and get collection of database record objects. |
70
|
|
|
* This method encapsulates all logic needed for query to be done before and after actual database |
71
|
|
|
* manager request. |
72
|
|
|
* |
73
|
|
|
* @param string $fetcher Database manager fetching method |
74
|
|
|
* @return mixed Return fetching function result |
75
|
|
|
*/ |
76
|
|
|
protected function innerExecute($fetcher = 'find') |
77
|
|
|
{ |
78
|
|
|
// Call handlers stack |
79
|
|
|
$this->callHandlers(); |
80
|
|
|
|
81
|
|
|
// Remove first argument |
82
|
|
|
$args = func_get_args(); |
83
|
|
|
array_shift($args); |
84
|
|
|
|
85
|
|
|
/** @var RecordInterface[] $return Perform DB request */ |
86
|
|
|
$return = call_user_func_array(array($this->database, $fetcher), $args); |
87
|
|
|
|
88
|
|
|
// Clear this query |
89
|
|
|
$this->flush(); |
90
|
|
|
|
91
|
|
|
// Return bool or collection |
92
|
|
|
return $return; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* Perform database request and get collection of database record objects. |
97
|
|
|
* |
98
|
|
|
* @param mixed $return External variable to store query results |
99
|
|
|
* @return mixed If no arguments passed returns query results collection, otherwise query success status |
100
|
|
|
*/ |
101
|
|
View Code Duplication |
public function exec(&$return = null) |
|
|
|
|
102
|
|
|
{ |
103
|
|
|
/** @var RecordInterface[] $return Perform DB request */ |
104
|
|
|
$return = $this->innerExecute('find', $this->class_name, $this); |
105
|
|
|
|
106
|
|
|
// Return bool or collection |
107
|
|
|
return func_num_args() ? sizeof($return) : $return; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Execute current query and receive amount of resulting rows. |
112
|
|
|
* |
113
|
|
|
* @param null|RecordInterface $return If variable is passed resulting amount of rows would be |
114
|
|
|
* stored in this variable. |
115
|
|
|
* @return bool|RecordInterface If method is called with $return parameter then then bool |
116
|
|
|
* with query result status would be returned, otherwise |
117
|
|
|
* query rows count would be returned. |
118
|
|
|
*/ |
119
|
|
View Code Duplication |
public function count(&$return = null) |
|
|
|
|
120
|
|
|
{ |
121
|
|
|
/** @var RecordInterface[] $return Perform DB request */ |
122
|
|
|
$return = $this->innerExecute('count', $this->class_name, $this); |
123
|
|
|
|
124
|
|
|
// Return bool or collection |
125
|
|
|
return func_num_args() ? sizeof($return) : $return; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Perform database request and get first record from results collection. |
130
|
|
|
* |
131
|
|
|
* @param mixed $return External variable to store query results |
132
|
|
|
* @return mixed If no arguments passed returns query results first database record object, |
133
|
|
|
* otherwise query success status |
134
|
|
|
*/ |
135
|
|
|
public function first(&$return = null) |
136
|
|
|
{ |
137
|
|
|
// Add limitation |
138
|
|
|
$this->limit(1); |
139
|
|
|
|
140
|
|
|
/** @var RecordInterface[] $return Perform DB request */ |
141
|
|
|
$return = $this->innerExecute('find', $this->class_name, $this); |
142
|
|
|
$return = sizeof($return) ? array_shift($return) : null; |
143
|
|
|
|
144
|
|
|
// Return bool or collection |
145
|
|
|
return func_num_args() ? sizeof($return) : $return; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Perform database request and get array of record field values |
150
|
|
|
* @see \samson\activerecord\Query::execute() |
151
|
|
|
* @param string $fieldName Record field name to get value from |
152
|
|
|
* @param string $return External variable to store query results |
153
|
|
|
* @return mixed If no arguments passed returns query results first database record object, |
154
|
|
|
* otherwise query success status |
155
|
|
|
*/ |
156
|
|
View Code Duplication |
public function fields($fieldName, &$return = null) |
|
|
|
|
157
|
|
|
{ |
158
|
|
|
/** @var RecordInterface[] $return Perform DB request */ |
159
|
|
|
$return = $this->innerExecute('fetchColumn', $this->class_name, $this, $fieldName); |
160
|
|
|
|
161
|
|
|
// Return bool or collection |
162
|
|
|
return func_num_args() > 1 ? sizeof($return) : $return; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Set query entity to work with. |
167
|
|
|
* |
168
|
|
|
* @param string $entity Entity identifier |
169
|
|
|
* @return Query Chaining |
170
|
|
|
* @throws EntityNotFound |
171
|
|
|
*/ |
172
|
|
|
public function entity($entity) |
173
|
|
|
{ |
174
|
|
|
if (class_exists($entity)) { |
175
|
|
|
$this->flush(); |
176
|
|
|
$this->class_name = $entity; |
177
|
|
|
} else { |
178
|
|
|
throw new EntityNotFound('['.$entity.'] not found'); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
return $this; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Get correct query condition depending on entity field name. |
186
|
|
|
* If base entity has field with this name - use base entity condition |
187
|
|
|
* group, otherwise default condition group. |
188
|
|
|
* |
189
|
|
|
* @param string $fieldName Entity field name |
190
|
|
|
* @return Condition Correct query condition group |
191
|
|
|
*/ |
192
|
|
|
protected function &conditionGroup($fieldName) |
193
|
|
|
{ |
194
|
|
|
if (property_exists($this->class_name, $fieldName)) { |
195
|
|
|
// Add this condition to base entity condition group |
196
|
|
|
return $this->own_condition; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
return $this->cConditionGroup; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Add query condition as prepared Condition instance. |
204
|
|
|
* |
205
|
|
|
* @param ConditionInterface $condition Condition to be added |
206
|
|
|
* @return self Chaining |
207
|
|
|
*/ |
208
|
|
|
public function whereCondition(ConditionInterface $condition) |
209
|
|
|
{ |
210
|
|
|
// TODO: We cannot define to which group this condition is related |
211
|
|
|
$this->own_condition->addCondition($condition); |
212
|
|
|
|
213
|
|
|
// // Iterate condition arguments |
214
|
|
|
// foreach ($condition as $argument) { |
215
|
|
|
// // If passed condition group has another condition group as argument |
216
|
|
|
// if (is_a($argument, __NAMESPACE__ . '\Condition')) { |
217
|
|
|
// // Go deeper in recursion |
218
|
|
|
// $this->whereCondition($argument); |
219
|
|
|
// } else { // Otherwise add condition argument to correct condition group |
220
|
|
|
// $this->conditionGroup($argument->field)->addArgument($argument); |
221
|
|
|
// } |
222
|
|
|
// } |
223
|
|
|
|
224
|
|
|
return $this; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Add condition to current query. |
229
|
|
|
* |
230
|
|
|
* @param string $fieldName Entity field name |
231
|
|
|
* @param string $fieldValue Value |
232
|
|
|
* @param string $relation Relation between field name and its value |
233
|
|
|
* @return self Chaining |
234
|
|
|
*/ |
235
|
|
|
public function where($fieldName, $fieldValue = null, $relation = '=') |
236
|
|
|
{ |
237
|
|
|
// If empty array is passed |
238
|
|
|
if (is_string($fieldName)) { |
239
|
|
|
// Handle empty field value passing to avoid unexpected behaviour |
240
|
|
|
if (!isset($fieldValue)) { |
241
|
|
|
$relation = ArgumentInterface::ISNULL; |
242
|
|
|
$fieldValue = ''; |
243
|
|
|
} elseif (is_array($fieldValue) && !sizeof($fieldValue)) { |
244
|
|
|
// TODO: We consider empty array passed as condition value as NULL, illegal condition |
245
|
|
|
$relation = ArgumentInterface::EQUAL; |
246
|
|
|
$fieldName = '1'; |
247
|
|
|
$fieldValue = '0'; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
// Add condition argument |
251
|
|
|
$this->conditionGroup($fieldName)->add($fieldName, $fieldValue, $relation); |
252
|
|
|
} else { |
253
|
|
|
throw new \InvalidArgumentException('You can only pass string as first argument'); |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
return $this; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Join entity to query. |
261
|
|
|
* |
262
|
|
|
* @param string $entityName Entity identifier |
263
|
|
|
* @return self Chaining |
264
|
|
|
*/ |
265
|
|
|
public function join($entityName) |
266
|
|
|
{ |
267
|
|
|
// TODO: We need to implement this logic |
268
|
|
|
$entityName .= ''; |
269
|
|
|
|
270
|
|
|
// Chaining |
271
|
|
|
return $this; |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* Add query result grouping. |
276
|
|
|
* |
277
|
|
|
* @param string $fieldName Entity field identifier for grouping |
278
|
|
|
* @return self Chaining |
279
|
|
|
*/ |
280
|
|
|
public function groupBy($fieldName) |
281
|
|
|
{ |
282
|
|
|
$this->grouping[] = $fieldName; |
283
|
|
|
|
284
|
|
|
// Chaining |
285
|
|
|
return $this; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* Add query result quantity limitation. |
290
|
|
|
* |
291
|
|
|
* @param int $offset Resulting offset |
292
|
|
|
* @param null|int $quantity Amount of RecordInterface object to return |
293
|
|
|
* @return self Chaining |
294
|
|
|
*/ |
295
|
|
|
public function limit($offset, $quantity = null) |
296
|
|
|
{ |
297
|
|
|
$this->limitation = array($offset, $quantity); |
298
|
|
|
|
299
|
|
|
// Chaining |
300
|
|
|
return $this; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Add query result sorting. |
305
|
|
|
* |
306
|
|
|
* @param string $fieldName Entity field identifier for worting |
307
|
|
|
* @param string $order Sorting order |
308
|
|
|
* @return self Chaining |
309
|
|
|
*/ |
310
|
|
|
public function orderBy($fieldName, $order = 'ASC') |
311
|
|
|
{ |
312
|
|
|
$this->sorting[] = array($fieldName, $order); |
313
|
|
|
|
314
|
|
|
// Chaining |
315
|
|
|
return $this; |
316
|
|
|
} |
317
|
|
|
} |
318
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.