1 <?php
2 3 4 5 6 7 8 9 10
11
12 namespace Neevo;
13
14 use BadMethodCallException;
15 use InvalidArgumentException;
16 use RuntimeException;
17 use SplObjectStorage;
18 use Traversable;
19
20
21 22 23 24 25 26 27 28 29 30 31
32 abstract class BaseStatement implements ObservableInterface {
33
34
35
36 protected $source;
37
38
39 protected $type;
40
41
42 protected $limit;
43
44
45 protected $offset;
46
47
48 protected $conditions = array();
49
50
51 protected $sorting = array();
52
53
54 protected $time;
55
56
57 protected $performed;
58
59
60 protected $connection;
61
62
63 protected static $eventTable = array(
64 Manager::STMT_SELECT => ObserverInterface::SELECT,
65 Manager::STMT_INSERT => ObserverInterface::INSERT,
66 Manager::STMT_UPDATE => ObserverInterface::UPDATE,
67 Manager::STMT_DELETE => ObserverInterface::DELETE
68 );
69
70
71 protected $subqueries = array();
72
73
74 protected $observers;
75
76
77 private $stmtConditions = array();
78
79
80 81 82 83
84 public function __construct(Connection $connection){
85 $this->connection = $connection;
86 $this->observers = new SplObjectStorage;
87 }
88
89
90 91 92 93
94 public function __toString(){
95 return (string) $this->parse();
96 }
97
98
99 100 101
102 public function __clone(){
103 $this->resetState();
104 }
105
106
107 108 109 110 111 112
113 public function __call($name, $args){
114 $name = strtolower($name);
115
116
117 if(in_array($name, array('and', 'or'))){
118 if($this->validateConditions())
119 return $this;
120
121 $this->resetState();
122 if(($count = count($this->conditions)) !== 0)
123 $this->conditions[$count - 1]['glue'] = strtoupper($name);
124 if(count($args) >= 1)
125 call_user_func_array(array($this, 'where'), $args);
126 return $this;
127 }
128
129
130 elseif(in_array($name, array('if', 'else', 'end'))){
131
132
133 if(count($args) < 1 && $name == 'if')
134 throw new InvalidArgumentException('Missing argument 1 for ' . __CLASS__ . "::$name().");
135
136 $conds = & $this->stmtConditions;
137 if($name == 'if')
138 $conds[] = (bool) $args[0];
139 elseif($name == 'else')
140 $conds[count($conds) - 1] = !end($conds);
141 elseif($name == 'end')
142 array_pop($conds);
143
144 return $this;
145 }
146 throw new BadMethodCallException('Call to undefined method ' . __CLASS__ . "::$name()");
147 }
148
149
150 151 152 153 154 155 156 157 158 159 160 161
162 public function where($expr, $value = true){
163 if((is_array($expr) || $expr instanceof Traversable) && $value === true){
164 foreach($expr as $key => $val) $this->where($key, $val);
165 return $this;
166 }
167
168 if($this->validateConditions())
169 return $this;
170
171 $this->resetState();
172
173
174 if(strpos($expr, '%') === false){
175 $field = trim($expr);
176 $this->conditions[] = array(
177 'simple' => true,
178 'field' => $field,
179 'value' => $value,
180 'glue' => 'AND'
181 );
182 if($value instanceof self)
183 $this->subqueries[] = $value;
184 return $this;
185 }
186
187
188 $args = func_get_args();
189 array_shift($args);
190 preg_match_all('~%(bin|sub|b|i|f|s|d|a|l)?~i', $expr, $matches);
191 $this->conditions[] = array(
192 'simple' => false,
193 'expr' => $expr,
194 'modifiers' => $matches[0],
195 'types' => $matches[1],
196 'values' => $args,
197 'glue' => 'AND'
198 );
199 foreach($args as $arg){
200 if($arg instanceof self)
201 $this->subqueries[] = $arg;
202 }
203 return $this;
204 }
205
206
207 208 209 210 211 212
213 public function order($rule, $order = null){
214 if($this->validateConditions())
215 return $this;
216
217 $this->resetState();
218
219 if(is_array($rule) || $rule instanceof Traversable){
220 foreach($rule as $key => $val){
221 $this->order($key, $val);
222 }
223 return $this;
224 }
225 $this->sorting[] = array($rule, $order);
226
227 return $this;
228 }
229
230
231 232 233 234 235 236
237 public function limit($limit, $offset = null){
238 if($this->validateConditions())
239 return $this;
240
241 $this->resetState();
242 $this->limit = array($limit,
243 ($offset !== null && $this->type === Manager::STMT_SELECT) ? $offset : null);
244 return $this;
245 }
246
247
248 249 250 251
252 public function rand(){
253 if($this->validateConditions())
254 return $this;
255
256 $this->resetState();
257 $this->connection->getDriver()->randomizeOrder($this);
258 return $this;
259 }
260
261
262 263 264 265 266
267 public function dump($return = false){
268 $sql = PHP_SAPI === 'cli'
269 ? preg_replace('/\s+/', ' ', $this->parse()) . "\n"
270 : Manager::highlightSql($this->parse());
271 if(!$return)
272 echo $sql;
273 return $return ? $sql : $this;
274 }
275
276
277 278 279 280
281 public function run(){
282 if(!$this->performed)
283 $start = microtime(true);
284
285 try{
286 $query = $this->performed
287 ? $this->resultSet
288 : $this->connection->getDriver()->runQuery(preg_replace('/\s+/', ' ', $this->parse()));
289 } catch(DriverException $e){
290 throw new NeevoException('Query failed. ' . $e->getMessage(), $e->getCode(), $e->getSql(), $e);
291 }
292
293 if(!$this->performed)
294 $this->time = microtime(true) - $start;
295
296 $this->performed = true;
297 $this->resultSet = $query;
298
299 $this->notifyObservers(isset($this->type)
300 ? self::$eventTable[$this->type] : ObserverInterface::QUERY);
301
302 return $query;
303 }
304
305
306 307 308 309
310 public function exec(){
311 return $this->run();
312 }
313
314
315 316 317 318 319
320 public function parse(){
321 if($this->hasCircularReferences($this))
322 throw new RuntimeException('Circular reference found, aborting.');
323
324 $this->connection->connect();
325
326 $parser = $this->connection->getParser();
327 $instance = new $parser($this);
328 return $instance->parse();
329 }
330
331
332 333 334 335 336
337 public function attachObserver(ObserverInterface $observer, $event){
338 $this->observers->attach($observer, $event);
339 }
340
341
342 343 344 345
346 public function detachObserver(ObserverInterface $observer){
347 $this->observers->detach($observer);
348 }
349
350
351 352 353 354
355 public function notifyObservers($event){
356 foreach($this->observers as $observer){
357 if($event & $this->observers->getInfo())
358 $observer->updateStatus($this, $event);
359 }
360 }
361
362
363 364 365 366
367 public function getTime(){
368 return $this->time;
369 }
370
371
372 373 374 375
376 public function isPerformed(){
377 return $this->performed;
378 }
379
380
381 382 383 384
385 public function getTable(){
386 $table = str_replace(':', '', $this->source);
387 $prefix = $this->connection->getPrefix();
388 return $prefix . $table;
389 }
390
391
392 393 394 395
396 public function getType(){
397 return $this->type;
398 }
399
400
401 402 403 404
405 public function getLimit(){
406 return $this->limit;
407 }
408
409
410 411 412 413
414 public function getConditions(){
415 return $this->conditions;
416 }
417
418
419 420 421 422
423 public function getSorting(){
424 return $this->sorting;
425 }
426
427
428 429 430 431
432 public function getPrimaryKey(){
433 $table = $this->getTable();
434 if(!$table)
435 return null;
436 $key = null;
437 $cached = $this->connection->getCache()->fetch($table . '_primaryKey');
438
439 if($cached === null){
440 try{
441 $key = $this->connection->getDriver()
442 ->getPrimaryKey($table, isset($this->resultSet) ? $this->resultSet : null);
443 } catch(NeevoException $e){
444 return null;
445 }
446 $this->connection->getCache()->store($table . '_primaryKey', $key);
447 return $key === '' ? null : $key;
448 }
449 return $cached === '' ? null : $cached;
450 }
451
452
453 454 455 456
457 public function getConnection(){
458 return $this->connection;
459 }
460
461
462 463 464
465 protected function resetState(){
466 $this->performed = false;
467 $this->resultSet = null;
468 $this->time = null;
469 }
470
471
472 473 474 475
476 protected function validateConditions(){
477 if(empty($this->stmtConditions))
478 return false;
479 foreach($this->stmtConditions as $cond){
480 if($cond) continue;
481 else return true;
482 }
483 return false;
484 }
485
486
487 488 489 490 491 492
493 protected function hasCircularReferences($parent, $visited = array()){
494 foreach($parent->subqueries as $child){
495 if(isset($visited[spl_object_hash($child)]))
496 return true;
497 $visited[spl_object_hash($child)] = true;
498 if($this->hasCircularReferences($child, $visited))
499 return true;
500 array_pop($visited);
501 }
502 return false;
503 }
504
505
506 }
507