1 <?php
2 3 4 5 6 7 8 9 10
11
12
13 14 15 16 17 18 19 20 21 22 23 24
25 abstract class NeevoBaseStmt implements INeevoObservable {
26
27
28
29 protected $source;
30
31
32 protected $type;
33
34
35 protected $limit;
36
37
38 protected $offset;
39
40
41 protected $conditions = array();
42
43
44 protected $sorting = array();
45
46
47 protected $time;
48
49
50 protected $performed;
51
52
53 protected $connection;
54
55
56 protected static $eventTable = array(
57 Neevo::STMT_SELECT => INeevoObserver::SELECT,
58 Neevo::STMT_INSERT => INeevoObserver::INSERT,
59 Neevo::STMT_UPDATE => INeevoObserver::UPDATE,
60 Neevo::STMT_DELETE => INeevoObserver::DELETE
61 );
62
63
64 protected $subqueries = array();
65
66
67 protected $observers;
68
69
70 private $stmtConditions = array();
71
72
73 74 75 76 77
78 public function __construct(NeevoConnection $connection){
79 $this->connection = $connection;
80 $this->observers = new NeevoObserverMap;
81 }
82
83
84 85 86 87
88 public function __toString(){
89 return (string) $this->parse();
90 }
91
92
93 94 95 96
97 public function __clone(){
98 $this->resetState();
99 }
100
101
102 103 104 105 106 107
108 public function __call($name, $args){
109 $name = strtolower($name);
110
111
112 if(in_array($name, array('and', 'or'))){
113 if($this->validateConditions())
114 return $this;
115
116 $this->resetState();
117 if(($count = count($this->conditions)) !== 0)
118 $this->conditions[$count-1]['glue'] = strtoupper($name);
119 if(count($args) >= 1)
120 call_user_func_array(array($this, 'where'), $args);
121 return $this;
122 }
123
124
125 elseif(in_array($name, array('if', 'else', 'end'))){
126
127
128 if(count($args) < 1 && $name == 'if')
129 throw new InvalidArgumentException('Missing argument 1 for '.__CLASS__."::$name().");
130
131 $conds = & $this->stmtConditions;
132 if($name == 'if')
133 $conds[] = (bool) $args[0];
134 elseif($name == 'else')
135 $conds[count($conds)-1] = !end($conds);
136 elseif($name == 'end')
137 array_pop($conds);
138
139 return $this;
140
141 }
142 throw new BadMethodCallException('Call to undefined method '.__CLASS__."::$name()");
143 }
144
145
146
147
148
149 150 151 152 153 154 155 156 157 158 159 160
161 public function where($expr, $value = true){
162 if((is_array($expr) || $expr instanceof Traversable) && $value === true){
163 foreach($expr as $key => $val)
164 $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 === Neevo::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 268 269
270 public function dump($return = false){
271 $sql = PHP_SAPI === 'cli' ? $this->parse() . "\n" : Neevo::highlightSql($this->parse());
272 if(!$return)
273 echo $sql;
274 return $return ? $sql : $this;
275 }
276
277
278 279 280 281
282 public function run(){
283 if(!$this->performed)
284 $start = microtime(true);
285
286 $query = $this->performed
287 ? $this->resultSet : $this->connection->getDriver()->runQuery($this->parse());
288
289 if(!$this->performed)
290 $this->time = microtime(true) - $start;
291
292 $this->performed = true;
293 $this->resultSet = $query;
294
295 $this->notifyObservers(isset($this->type)
296 ? self::$eventTable[$this->type] : INeevoObserver::QUERY);
297
298 return $query;
299 }
300
301
302 303 304 305
306 public function exec(){
307 return $this->run();
308 }
309
310
311 312 313 314 315
316 public function parse(){
317 if($this->hasCircularReferences($this))
318 throw new RuntimeException('Circular reference found, aborting.');
319
320 $this->connection->connect();
321
322 $parser = $this->connection->getParser();
323 $instance = new $parser($this);
324 return $instance->parse();
325 }
326
327
328
329
330
331 332 333 334 335 336
337 public function attachObserver(INeevoObserver $observer, $event){
338 $this->observers->attach($observer, $event);
339 }
340
341
342 343 344 345 346
347 public function detachObserver(INeevoObserver $observer){
348 $this->observers->detach($observer);
349 }
350
351
352 353 354 355 356
357 public function notifyObservers($event){
358 foreach($this->observers as $observer){
359 if($event & $this->observers->getEvent())
360 $observer->updateStatus($this, $event);
361 }
362 }
363
364
365
366
367
368 369 370 371
372 public function getTime(){
373 return $this->time;
374 }
375
376
377 378 379 380
381 public function isPerformed(){
382 return $this->performed;
383 }
384
385
386 387 388 389
390 public function getTable(){
391 $table = str_replace(':', '', $this->source);
392 $prefix = $this->connection->getPrefix();
393 return $prefix . $table;
394 }
395
396
397 398 399 400
401 public function getType(){
402 return $this->type;
403 }
404
405
406 407 408 409
410 public function getLimit(){
411 return $this->limit;
412 }
413
414
415 416 417 418
419 public function getConditions(){
420 return $this->conditions;
421 }
422
423
424 425 426 427
428 public function getSorting(){
429 return $this->sorting;
430 }
431
432
433 434 435 436
437 public function getPrimaryKey(){
438 $table = $this->getTable();
439 if(!$table)
440 return null;
441 $key = null;
442 $cached = $this->connection->getCache()->fetch($table . '_primaryKey');
443
444 if($cached === null){
445 try{
446 $key = $this->connection->getDriver()->getPrimaryKey($table);
447 } catch(NeevoException $e){
448 return null;
449 }
450 $this->connection->getCache()->store($table . '_primaryKey', $key);
451 return $key === '' ? null : $key;
452 }
453 return $cached === '' ? null : $cached;
454 }
455
456
457
458
459
460 461 462 463
464 public function getConnection(){
465 return $this->connection;
466 }
467
468
469 470 471 472
473 protected function resetState(){
474 $this->performed = false;
475 $this->resultSet = null;
476 $this->time = null;
477 }
478
479
480 481 482 483
484 protected function validateConditions(){
485 if(empty($this->stmtConditions))
486 return false;
487 foreach($this->stmtConditions as $cond){
488 if($cond) continue;
489 else return true;
490 }
491 return false;
492 }
493
494
495 496 497 498 499 500
501 protected function hasCircularReferences($parent, $visited = array()){
502 foreach($parent->subqueries as $child){
503 if(isset($visited[spl_object_hash($child)]))
504 return true;
505 $visited[spl_object_hash($child)] = true;
506 if($this->hasCircularReferences($child, $visited))
507 return true;
508 array_pop($visited);
509 }
510 return false;
511 }
512
513
514 }
515