Neevo Public API
  • Namespace
  • Class
  • Tree

Namespaces

  • Neevo
    • Cache
    • Drivers
    • Nette
  • PHP

Classes

  • BaseStatement
  • Connection
  • Literal
  • Manager
  • Parser
  • Result
  • ResultIterator
  • Row
  • Statement

Interfaces

  • DriverInterface
  • ObservableInterface
  • ObserverInterface

Exceptions

  • DriverException
  • ImplementationException
  • NeevoException
  1 <?php
  2 /**
  3  * Neevo - Tiny database layer for PHP. (http://neevo.smasty.net)
  4  *
  5  * This source file is subject to the MIT license that is bundled
  6  * with this package in the file license.txt.
  7  *
  8  * Copyright (c) 2012 Smasty (http://smasty.net)
  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  * Neevo statement abstract base ancestor.
 23  *
 24  * @method BaseStatement and($expr, $value = true)
 25  * @method BaseStatement or($expr, $value = true)
 26  * @method BaseStatement if($condition)
 27  * @method BaseStatement else()
 28  * @method BaseStatement end()
 29  *
 30  * @author Smasty
 31  */
 32 abstract class BaseStatement implements ObservableInterface {
 33 
 34 
 35     /** @var string */
 36     protected $source;
 37 
 38     /** @var string */
 39     protected $type;
 40 
 41     /** @var int */
 42     protected $limit;
 43 
 44     /** @var int */
 45     protected $offset;
 46 
 47     /** @var array */
 48     protected $conditions = array();
 49 
 50     /** @var array */
 51     protected $sorting = array();
 52 
 53     /** @var float */
 54     protected $time;
 55 
 56     /** @var bool */
 57     protected $performed;
 58 
 59     /** @var Connection */
 60     protected $connection;
 61 
 62     /** @var array Event type conversion table */
 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     /** @var array */
 71     protected $subqueries = array();
 72 
 73     /** @var SplObjectStorage */
 74     protected $observers;
 75 
 76     /** @var array */
 77     private $stmtConditions = array();
 78 
 79 
 80     /**
 81      * Create statement.
 82      * @param Connection $connection
 83      */
 84     public function __construct(Connection $connection){
 85         $this->connection = $connection;
 86         $this->observers = new SplObjectStorage;
 87     }
 88 
 89 
 90     /**
 91      * String representation of object.
 92      * @return string
 93      */
 94     public function __toString(){
 95         return (string) $this->parse();
 96     }
 97 
 98 
 99     /**
100      * Create clone of object.
101      */
102     public function __clone(){
103         $this->resetState();
104     }
105 
106 
107     /**
108      * @return BaseStatement fluent interface
109      * @internal
110      * @throws BadMethodCallException
111      * @throws InvalidArgumentException
112      */
113     public function __call($name, $args){
114         $name = strtolower($name);
115 
116         // AND/OR where() glues
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         // Conditional statements
130         elseif(in_array($name, array('if', 'else', 'end'))){
131 
132             // Parameter counts
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      * Sets WHERE condition. Accepts infinite arguments.
152      *
153      * More calls append conditions with 'AND' operator. Conditions can also be specified
154      * by calling and() / or() methods the same way as where().
155      * Corresponding operator will be used.
156      *
157      * Accepts associative array in field => value form.
158      * @param string|array|Traversable $expr
159      * @param mixed $value
160      * @return BaseStatement fluent interface
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         // Simple format
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         // Format with modifiers
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      * Defines order. More calls append rules.
209      * @param string|array|Traversable $rule
210      * @param string $order Use constants - Manager::ASC, Manager::DESC
211      * @return BaseStatement fluent interface
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      * Sets LIMIT and OFFSET clauses.
233      * @param int $limit
234      * @param int $offset
235      * @return BaseStatement fluent interface
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      * Randomizes order. Removes any other order clause.
250      * @return BaseStatement fluent interface
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      * Prints out syntax highlighted statement.
264      * @param bool $return
265      * @return string|BaseStatement fluent interface
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      * Performs the statement.
279      * @return resource|bool
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      * Performs the statement. Alias for run().
308      * @return resource|bool
309      */
310     public function exec(){
311         return $this->run();
312     }
313 
314 
315     /**
316      * Builds the SQL statement from the instance.
317      * @return string The SQL statement
318      * @internal
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      * Attaches given observer to given event.
334      * @param ObserverInterface $observer
335      * @param int $event
336      */
337     public function attachObserver(ObserverInterface $observer, $event){
338         $this->observers->attach($observer, $event);
339     }
340 
341 
342     /**
343      * Detaches given observer.
344      * @param ObserverInterface $observer
345      */
346     public function detachObserver(ObserverInterface $observer){
347         $this->observers->detach($observer);
348     }
349 
350 
351     /**
352      * Notifies all observers attached to given event.
353      * @param int $event
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      * Returns query execution time.
365      * @return int
366      */
367     public function getTime(){
368         return $this->time;
369     }
370 
371 
372     /**
373      * Returns wheter query was performed.
374      * @return bool
375      */
376     public function isPerformed(){
377         return $this->performed;
378     }
379 
380 
381     /**
382      * Returns full table name (with prefix).
383      * @return string
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      * Returns tatement type.
394      * @return string
395      */
396     public function getType(){
397         return $this->type;
398     }
399 
400 
401     /**
402      * Returns LIMIT and OFFSET clauses.
403      * @return array
404      */
405     public function getLimit(){
406         return $this->limit;
407     }
408 
409 
410     /**
411      * Returns statement WHERE clause.
412      * @return array
413      */
414     public function getConditions(){
415         return $this->conditions;
416     }
417 
418 
419     /**
420      * Returns statement ORDER BY clause.
421      * @return array
422      */
423     public function getSorting(){
424         return $this->sorting;
425     }
426 
427 
428     /**
429      * Returns the name of the PRIMARY KEY column.
430      * @return string|null
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      * Returns the connection instance.
455      * @return Connection
456      */
457     public function getConnection(){
458         return $this->connection;
459     }
460 
461 
462     /**
463      * Resets the state of the statement.
464      */
465     protected function resetState(){
466         $this->performed = false;
467         $this->resultSet = null;
468         $this->time = null;
469     }
470 
471 
472     /**
473      * Validates the current statement condition.
474      * @return bool
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      * Checks the query tree for circular references.
489      * @param BaseStatement $parent
490      * @param array $visited
491      * @return bool True if circular reference found.
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 
Neevo Public API API documentation generated by ApiGen 2.8.0