1
|
|
|
<?php |
2
|
|
|
namespace samsonframework\orm; |
3
|
|
|
|
4
|
|
|
/** |
5
|
|
|
* Database query builder and executer. |
6
|
|
|
* @author Vitaly Iegorov <[email protected]> |
7
|
|
|
* @version 2.0 |
8
|
|
|
*/ |
9
|
|
|
class Query extends QueryHandler implements QueryInterface |
10
|
|
|
{ |
11
|
|
|
/** @var string Class name for interacting with database */ |
12
|
|
|
protected $class_name; |
13
|
|
|
|
14
|
|
|
/** @var self[] Collection of query parameters objects */ |
15
|
|
|
protected $parameters = array(); |
16
|
|
|
|
17
|
|
|
/** @var array Collection of entity field names for sorting order */ |
18
|
|
|
protected $sorting = array(); |
19
|
|
|
|
20
|
|
|
/** @var array Collection of entity field names for grouping query results */ |
21
|
|
|
protected $grouping = array(); |
22
|
|
|
|
23
|
|
|
/** @var array Collection of query results limitations */ |
24
|
|
|
protected $limitation = array(); |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Reset all query parameters |
28
|
|
|
* @return self Chaining |
29
|
|
|
*/ |
30
|
|
|
public function flush() |
31
|
|
|
{ |
32
|
|
|
// TODO: Do we need it? |
33
|
|
|
foreach ($this->parameters as $param) { |
34
|
|
|
$param->flush(); |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
return $this; |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Perform database request and get collection of database record objects |
42
|
|
|
* @see \samson\activerecord\Query::execute() |
43
|
|
|
* @param mixed $return External variable to store query results |
44
|
|
|
* @return mixed If no arguments passed returns query results collection, otherwise query success status |
45
|
|
|
*/ |
46
|
|
|
public function exec(& $return = null) |
47
|
|
|
{ |
48
|
|
|
$args = func_num_args(); |
49
|
|
|
return $this->execute($return, $args); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Perform database request and get first record from results collection |
54
|
|
|
* @see \samson\activerecord\Query::execute() |
55
|
|
|
* @param mixed $return External variable to store query results |
56
|
|
|
* @return mixed If no arguments passed returns query results first database record object, |
57
|
|
|
* otherwise query success status |
58
|
|
|
*/ |
59
|
|
|
public function first(& $return = null) |
60
|
|
|
{ |
61
|
|
|
$args = func_num_args(); |
62
|
|
|
return $this->execute($return, $args, 1); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Perform database request and get array of record field values |
67
|
|
|
* @see \samson\activerecord\Query::execute() |
68
|
|
|
* @param string $fieldName Record field name to get value from |
69
|
|
|
* @param string $return External variable to store query results |
70
|
|
|
* @return Ambigous <boolean, NULL, mixed> |
71
|
|
|
*/ |
72
|
|
|
public function fields($fieldName, & $return = null) |
73
|
|
|
{ |
74
|
|
|
// Call handlers stack |
75
|
|
|
$this->_callHandlers(); |
76
|
|
|
|
77
|
|
|
// Perform DB request |
78
|
|
|
$return = db()->fetchColumn($this->class_name, $this, $fieldName); |
79
|
|
|
|
80
|
|
|
$success = is_array($return) && sizeof($return); |
81
|
|
|
|
82
|
|
|
// If parent function has arguments - consider them as return value and return request status |
83
|
|
|
if (func_num_args() - 1 > 0) { |
84
|
|
|
return $success; |
|
|
|
|
85
|
|
|
} else { // Parent function has no arguments, return request result |
86
|
|
|
return $return; |
87
|
|
|
} |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* Perform database request and return different results depending on function arguments. |
93
|
|
|
* @see \samson\activerecord\Record |
94
|
|
|
* @param array $result External variable to store dabatase request results collection |
95
|
|
|
* @param integer|bool $rType Amount of arguments passed to parent function |
96
|
|
|
* @param integer $limit Quantity of records to return |
97
|
|
|
* @param callable $handler External callable handler for results modification |
98
|
|
|
* @param array $handlerArgs External callable handler arguments |
99
|
|
|
* @return boolean/array Boolean if $r_type > 0, otherwise array of request results |
|
|
|
|
100
|
|
|
*/ |
101
|
|
|
protected function &execute( |
102
|
|
|
& $result = null, |
103
|
|
|
$rType = false, |
104
|
|
|
$limit = null, |
105
|
|
|
$handler = null, |
106
|
|
|
$handlerArgs = array() |
107
|
|
|
) |
|
|
|
|
108
|
|
|
{ |
109
|
|
|
// Call handlers stack |
110
|
|
|
$this->_callHandlers(); |
111
|
|
|
|
112
|
|
|
// Perform DB request |
113
|
|
|
$result = db()->find($this->class_name, $this); |
114
|
|
|
|
115
|
|
|
// If external result handler is passed - use it |
116
|
|
|
if (isset($handler)) { |
117
|
|
|
// Add results collection to array |
118
|
|
|
array_unshift($handlerArgs, $result); |
119
|
|
|
|
120
|
|
|
// Call external handler with parameters |
121
|
|
|
$result = call_user_func_array($handler, $handlerArgs); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
// Clear this query |
125
|
|
|
$this->flush(); |
126
|
|
|
|
127
|
|
|
// Count records |
128
|
|
|
$count = sizeof($result); |
129
|
|
|
|
130
|
|
|
// Define is request was successful |
131
|
|
|
$success = is_array($result) && $count; |
132
|
|
|
|
133
|
|
|
// Is amount of records is specified |
134
|
|
|
if (isset($limit)) { |
135
|
|
|
// If we have not enought records - return null |
136
|
|
|
if ($count < $limit) { |
137
|
|
|
$result = null; |
138
|
|
|
} elseif ($limit === 1) { // If we need first record |
139
|
|
|
$result = array_shift($result); |
140
|
|
|
} elseif ($limit > 1) { // Slice array for nessesar amount |
141
|
|
|
$result = array_slice($result, 0, $limit); |
142
|
|
|
} |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
// If parent function has arguments - consider them as return value and return request status |
146
|
|
|
if ($rType > 0) { |
147
|
|
|
return $success; |
148
|
|
|
} else { // Parent function has no arguments, return request result |
149
|
|
|
return $result; |
150
|
|
|
} |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* Set query entity to work with. |
155
|
|
|
* |
156
|
|
|
* @param string $entity Entity identifier |
157
|
|
|
* @return self|string Chaining or current entity identifier if nothing is passed |
158
|
|
|
*/ |
159
|
|
|
public function entity($entity = null) |
160
|
|
|
{ |
161
|
|
|
$this->class_name = isset($entity) ? $entity : $this->class_name; |
162
|
|
|
|
163
|
|
|
return func_num_args() > 0 ? $this->class_name : $this; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Add condition to current query. |
168
|
|
|
* |
169
|
|
|
* @param string $fieldName Entity field name |
170
|
|
|
* @param string $fieldValue Value |
171
|
|
|
* @param string $relation Relation between field name and its value |
172
|
|
|
* @return self Chaining |
173
|
|
|
*/ |
174
|
|
|
public function cond($fieldName, $fieldValue, $relation = '=') |
175
|
|
|
{ |
176
|
|
|
// If empty array is passed |
177
|
|
|
if (is_array($fieldValue) && !sizeof($fieldValue)) { |
178
|
|
|
$this->empty = true; |
|
|
|
|
179
|
|
|
return $this; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$fieldName = new Argument($fieldName, $fieldValue, $relation); |
183
|
|
|
|
184
|
|
|
// If this field condition relates to base query entity |
185
|
|
|
$destination = &$this->cConditionGroup; |
|
|
|
|
186
|
|
|
if (property_exists($this->class_name, $fieldName)) { |
187
|
|
|
// Add this condition to base entity condition group |
188
|
|
|
$destination = &$this->own_condition; |
|
|
|
|
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
// Добавим аргумент условия в выбранную группу условий |
192
|
|
|
$destination->arguments[] = $fieldName; |
193
|
|
|
|
194
|
|
|
// Вернем себя для цепирования |
195
|
|
|
return $this; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Join entity to query. |
200
|
|
|
* |
201
|
|
|
* @param string $entityName Entity identifier |
202
|
|
|
* @return self Chaining |
203
|
|
|
*/ |
204
|
|
|
public function join($entityName) |
205
|
|
|
{ |
206
|
|
|
// TODO: We need to implement this logic |
207
|
|
|
|
208
|
|
|
// Chaining |
209
|
|
|
return $this; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Add query result grouping. |
214
|
|
|
* |
215
|
|
|
* @param string $fieldName Entity field identifier for grouping |
216
|
|
|
* @return self Chaining |
217
|
|
|
*/ |
218
|
|
|
public function groupBy($fieldName) |
219
|
|
|
{ |
220
|
|
|
$this->grouping[] = $fieldName; |
221
|
|
|
|
222
|
|
|
// Chaining |
223
|
|
|
return $this; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Add query result quantity limitation. |
228
|
|
|
* |
229
|
|
|
* @param int $offset Resulting offset |
230
|
|
|
* @param null|int $quantity Amount of RecordInterface object to return |
231
|
|
|
* @return self Chaining |
232
|
|
|
*/ |
233
|
|
|
public function limit($offset, $quantity = null) |
234
|
|
|
{ |
235
|
|
|
$this->limitation = array($offset, $quantity); |
236
|
|
|
|
237
|
|
|
// Chaining |
238
|
|
|
return $this; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* Add query result sorting. |
243
|
|
|
* |
244
|
|
|
* @param string $fieldName Entity field identifier for worting |
245
|
|
|
* @param string $order Sorting order |
246
|
|
|
* @return self Chaining |
247
|
|
|
*/ |
248
|
|
|
public function orderBy($fieldName, $order = 'ASC') |
249
|
|
|
{ |
250
|
|
|
$this->sorting[] = array($fieldName, $order); |
251
|
|
|
|
252
|
|
|
// Chaining |
253
|
|
|
return $this; |
254
|
|
|
} |
255
|
|
|
} |
256
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.