PageRenderTime 6ms CodeModel.GetById 2ms app.highlight 51ms RepoModel.GetById 1ms app.codeStats 1ms

/trunk/Classes/PHPLinq/LinqToZendDb.php

#
PHP | 1201 lines | 552 code | 170 blank | 479 comment | 153 complexity | bb68ac38ac5cf9610140d55c4a10b870 MD5 | raw file
   1<?php
   2/**
   3 * PHPLinq
   4 *
   5 * Copyright (c) 2008 - 2011 PHPLinq
   6 *
   7 * This library is free software; you can redistribute it and/or
   8 * modify it under the terms of the GNU Lesser General Public
   9 * License as published by the Free Software Foundation; either
  10 * version 2.1 of the License, or (at your option) any later version.
  11 * 
  12 * This library is distributed in the hope that it will be useful,
  13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15 * Lesser General Public License for more details.
  16 * 
  17 * You should have received a copy of the GNU Lesser General Public
  18 * License along with this library; if not, write to the Free Software
  19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  20 *
  21 * @category   PHPLinq
  22 * @package    PHPLinq
  23 * @copyright  Copyright (c) 2008 - 2011 PHPLinq (http://www.codeplex.com/PHPLinq)
  24 * @license    http://www.gnu.org/licenses/lgpl.txt	LGPL
  25 * @version    ##VERSION##, ##DATE##
  26 */
  27
  28
  29/** PHPLinq */
  30require_once('PHPLinq.php');
  31
  32/** PHPLinq_ILinqProvider */
  33require_once('PHPLinq/ILinqProvider.php');
  34
  35/** PHPLinq_LinqToObjects */
  36require_once('PHPLinq/LinqToObjects.php');
  37
  38/** PHPLinq_Expression */
  39require_once('PHPLinq/Expression.php');
  40
  41/** PHPLinq_OrderByExpression */
  42require_once('PHPLinq/OrderByExpression.php');
  43
  44/** PHPLinq_Initiator */
  45require_once('PHPLinq/Initiator.php');
  46
  47/** PHPLinq_Adapter_Abstract */
  48require_once('PHPLinq/Adapter/Abstract.php');
  49
  50/** Zend_Db_Table */
  51require_once('Zend/Db/Table.php');
  52
  53/** Register ILinqProvider */
  54PHPLinq_Initiator::registerProvider('PHPLinq_LinqToZendDb');
  55
  56
  57/**
  58 * PHPLinq_LinqToZendDb
  59 *
  60 * @category   PHPLinq
  61 * @package    PHPLinq
  62 * @copyright  Copyright (c) 2008 - 2011 PHPLinq (http://www.codeplex.com/PHPLinq)
  63 */
  64class PHPLinq_LinqToZendDb implements PHPLinq_ILinqProvider {
  65	/** Constants */
  66	const T_FUNCTION 		= 1001001;
  67	const T_PROPERTY 		= 1001002;
  68	const T_CONSTANT 		= 1001003;
  69	const T_VARIABLE 		= 1001004;
  70	const T_OBJECT_OPERATOR = 1001005;
  71	const T_OPERATOR		= 1001006;
  72	const T_OPERAND 		= 1001007;
  73	const T_DEFAULT 		= 1001008;
  74	const T_START_STOP 		= 1001009;
  75	const T_SIMPLE			= 1001010;
  76	const T_ARGUMENT		= 1001011;
  77	const T_ARITHMETIC		= 1001012;
  78	
  79	/**
  80	 * Default variable name
  81	 *
  82	 * @var string
  83	 */
  84	private $_from = '';
  85	
  86	/**
  87	 * Data source
  88	 *
  89	 * @var mixed
  90	 */
  91	private $_data = null;
  92	
  93	/**
  94	 * Where expression
  95	 *
  96	 * @var PHPLinq_Expression
  97	 */
  98	private $_where = null;
  99	
 100	/**
 101	 * Take n elements
 102	 *
 103	 * @var int?
 104	 */
 105	private $_take = null;
 106	
 107	/**
 108	 * Skip n elements
 109	 *
 110	 * @var int?
 111	 */
 112	private $_skip = null;
 113	
 114	/**
 115	 * Take while expression is true
 116	 *
 117	 * @var PHPLinq_Expression
 118	 */
 119	private $_takeWhile = null;
 120	
 121	/**
 122	 * Skip while expression is true
 123	 *
 124	 * @var PHPLinq_Expression
 125	 */
 126	private $_skipWhile = null;
 127	
 128	/**
 129	 * OrderBy expressions
 130	 *
 131	 * @var PHPLinq_Expression[]
 132	 */
 133	private $_orderBy = array();
 134	
 135	/**
 136	 * Distinct expression
 137	 *
 138	 * @var PHPLinq_Expression
 139	 */
 140	private $_distinct = null;
 141	
 142	/**
 143	 * OfType expression
 144	 *
 145	 * @var PHPLinq_Expression
 146	 */
 147	private $_ofType = null;
 148	
 149	/**
 150	 * Parent PHPLinq_ILinqProvider instance, used with join conditions
 151	 *
 152	 * @var PHPLinq_ILinqProvider
 153	 */
 154	private $_parentProvider = null;
 155	
 156	/**
 157	 * Child PHPLinq_ILinqProvider instances, used with join conditions
 158	 *
 159	 * @var PHPLinq_ILinqProvider[]
 160	 */
 161	private $_childProviders = array();
 162	
 163	/**
 164	 * Join condition
 165	 *
 166	 * @var PHPLinq_Expression
 167	 */
 168	private $_joinCondition = null;
 169	
 170	/**
 171	 * Is object destructing?
 172	 *
 173	 * @var bool
 174	 */
 175	private $_isDestructing;
 176	
 177	/**
 178	 * PHPLinq_Adapter_Abstract instance
 179	 *
 180	 * @var PHPLinq_Adapter_Abstract
 181	 */
 182	private $_adapter = null;
 183	
 184	/**
 185	 * Columns to select
 186	 *
 187	 * @var string
 188	 */
 189	private $_columns = '*';
 190	
 191	/**
 192	 * Query callback (static for all PHPLinq_LinqToZendDb !)
 193	 * 
 194	 * Function accepting a string to which query strings can be logged.
 195	 *
 196	 * @var mixed
 197	 */
 198	private static $_queryCallback = null;
 199	
 200	/**
 201	 * Static list of PHP internal functions used for generating queries
 202	 *
 203	 * @var array
 204	 */
 205	private static $_internalFunctions = null;
 206	
 207	/**
 208	 * Can this provider type handle data in $source?
 209	 *
 210	 * @param mixed $source
 211	 * @return bool
 212	 */
 213	public static function handles($source) {
 214		return $source instanceof Zend_Db_Table;
 215	}
 216	
 217	/**
 218	 * Create a new class instance
 219	 *
 220	 * @param string $name
 221	 * @param PHPLinq_ILinqProvider $parentProvider Optional parent PHPLinq_ILinqProvider instance, used with join conditions
 222	 * @return PHPLinq_ILinqProvider
 223	 */
 224	public function __construct($name, PHPLinq_ILinqProvider $parentProvider = null) {
 225		if (is_null(self::$_internalFunctions)) {
 226			$internalFunctions = get_defined_functions();
 227			$internalFunctions['internal'][] = 'print'; // Add as PHP function
 228			self::$_internalFunctions = $internalFunctions['internal'];
 229		}
 230		
 231		$this->_from = $name;
 232		
 233		if (!is_null($parentProvider)) {
 234			$this->_parentProvider = $parentProvider;
 235			$parentProvider->addChildProvider($this);
 236		}
 237		
 238		return $this;
 239	}
 240	
 241	/**
 242	 * Class destructor
 243	 */
 244	public function __destruct() {
 245		$this->_isDestructing = true;
 246
 247		if (isset($this->_parentProvider) && !is_null($this->_parentProvider)) {
 248			if (!$this->_parentProvider->__isDestructing()) {
 249				$this->_parentProvider->__destruct();
 250			}
 251			$this->_parentProvider = null;
 252			unset($this->_parentProvider);
 253		}
 254		
 255		if (!is_null($this->_childProviders)) {
 256			foreach ($this->_childProviders as $provider) {
 257				$provider->__destruct();
 258				$provider = null;
 259				unset($provider);
 260			}
 261		}
 262	}
 263
 264	/**
 265	 * Is object destructing?
 266	 *
 267	 * @return bool
 268	 */
 269	public function __isDestructing() {
 270		return $this->_isDestructing;
 271	}
 272	
 273	/**
 274	 * Get join condition
 275	 *
 276	 * @return PHPLinq_Expression
 277	 */
 278	public function getJoinCondition() {
 279		return $this->_joinCondition;
 280	}
 281	
 282	/**
 283	 * Add child provider, used with joins
 284	 *
 285	 * @param PHPLinq_ILinqProvider $provider
 286	 */
 287	public function addChildProvider(PHPLinq_ILinqProvider $provider) {
 288		$this->_childProviders[] = $provider;
 289	}
 290	
 291	/**
 292	 * Retrieve "from" name
 293	 *
 294	 * @return string
 295	 */
 296	public function getFromName() {
 297		return $this->_from;
 298	}
 299	
 300	/**
 301	 * Retrieve data in data source
 302	 *
 303	 * @return mixed
 304	 */
 305	public function getSource() {
 306		return $this->_data;
 307	}
 308	
 309	/**
 310	 * Set source of data
 311	 *
 312	 * @param mixed $source
 313	 * @return PHPLinq_ILinqProvider
 314	 */
 315	public function in($source) {
 316		$this->_data = $source;
 317		
 318		// Configure PHPLinq_Adapter_Abstract
 319		$zendDbAdapter 	= get_class($source->getAdapter());
 320		$phpLinqAdapter = str_replace('Zend_Db', 'PHPLinq', $zendDbAdapter);
 321		require_once(str_replace('_', '/', $phpLinqAdapter) . '.php');
 322		$this->_adapter = new $phpLinqAdapter($source->getAdapter());
 323		
 324		return $this;
 325	}
 326	
 327	/**
 328	 * Select
 329	 *
 330	 * @param  string	$expression	Expression which creates a resulting element
 331	 * @return mixed
 332	 */
 333	public function select($expression = null) {
 334		// Return value
 335		$returnValue = array();
 336		
 337		// Zend_Db_Select expression
 338		$zendDbSelect = null;
 339		
 340		// Expression set?
 341		if (is_null($expression) || $expression == '') {
 342			$expression = $this->_from . ' => ' . $this->_from;
 343		}
 344		
 345		// Data source
 346		$dataSourceInfo = $this->_data->info();
 347		$dataSource = $this->_data->select()->from( array($this->_from => $dataSourceInfo['name']), $this->_columns );
 348
 349		// Create selector expression
 350		$selector = new PHPLinq_Expression($expression, $this->_from);
 351		
 352		// Build Zend_Db_Select chain
 353		$zendDbSelect = $dataSource;
 354		
 355		// Distinct set?
 356		if (!is_null($this->_distinct)) {
 357			$zendDbSelect = $zendDbSelect->distinct();
 358		}
 359		
 360		// Join set?
 361		if (count($this->_childProviders) > 0) {
 362			// Check if the child providers can be supported
 363			foreach ($this->_childProviders as $provider) {
 364				if (get_class($provider) != __CLASS__) {
 365					throw new PHPLinq_Exception('Joins of ' . get_class($provider) . ' on ' . __CLASS__ . ' are not supported.');
 366				} else {
 367					// Supported! Add join
 368					$joinInfo = $provider->getSource()->info();
 369					$dataSource = $dataSource->join(
 370						array($provider->getFromName() => $joinInfo['name']),
 371						$this->_convertToSql($provider->getJoinCondition()->getFunction()->getFunctionCode())
 372					);
 373				}
 374			}
 375		}
 376		
 377		// Where expresion set? Evaluate it!
 378		if (!is_null($this->_where)) {
 379			$functionCode = $this->_convertToSql(
 380			    $this->_where->getFunction()->getFunctionCode(),
 381			    $dataSource->getAdapter()->getQuoteIdentifierSymbol()
 382			);
 383			
 384			$zendDbSelect = $zendDbSelect->where($functionCode);
 385		}
 386		
 387		// OrderBy set?
 388		if (is_array($this->_orderBy) && count($this->_orderBy) > 0) {
 389			$orderBy = array();
 390			foreach ($this->_orderBy as $orderByExpression) {
 391				$orderBy[] = new Zend_Db_Expr(
 392					$this->_convertToSql(
 393						$orderByExpression->getExpression()->getFunction()->getFunctionCode()
 394					) . ($orderByExpression->isDescending() ? ' DESC' : ' ASC')
 395				);
 396			}
 397			$zendDbSelect = $zendDbSelect->order($orderBy);
 398		}
 399		
 400		// Take / skip
 401		$zendDbSelect->limit($this->_take, $this->_skip);
 402		
 403		// No integrity check needed, joins can be free format
 404		$zendDbSelect->setIntegrityCheck(false);
 405
 406		// Log query string
 407		if (!is_null(self::$_queryCallback)) {
 408			if (!is_array(self::$_queryCallback) && in_array(self::$_queryCallback, self::$_internalFunctions)) {
 409				self::$_queryCallback = create_function('$query', self::$_queryCallback . '($query);');
 410			}
 411			call_user_func(self::$_queryCallback, $zendDbSelect->__toString());
 412		}
 413
 414		// Query
 415		$results = null;
 416		if (count($this->_childProviders) == 0) {
 417			// No join, easy!
 418			$results = $zendDbSelect->query(Zend_Db::FETCH_OBJ)->fetchAll();
 419			
 420			// Row class set? If so, convert all data!
 421			if ($this->_data->getRowClass() != 'Zend_Db_Table_Row') {
 422				$rowClass = $this->_data->getRowClass();
 423				$tempResults = array();
 424				
 425				foreach ($results as $result) {
 426					$tempResult = call_user_func( array($rowClass, 'fromArray'), (array)$result );
 427					$tempResults[] = $tempResult;
 428				}
 429				
 430				$results = $tempResults;
 431			}
 432		} else {
 433			// Get data in mode FETCH_NAMED
 434			$tempResults = $zendDbSelect->query(Zend_Db::FETCH_NAMED)->fetchAll();
 435			
 436			// Fetch info
 437			$info = array( $this->_data->info() );
 438			foreach ($this->_childProviders as $provider) {
 439				$info[] = $provider->getSource()->info();
 440			}
 441			
 442			// Build proper resultset
 443			$results 		= array();
 444			$currentResult	= null;
 445			$currentObject	= null;
 446			foreach ($tempResults as $tempResult) {
 447				$currentResult = array();
 448				
 449				// For each table, add a new object with values
 450				for ($i = 0; $i < count($info); $i++) {
 451					// Fill object data
 452					$currentObject = array();
 453					
 454					foreach($info[$i]['cols'] as $column) {
 455						if (!is_array($tempResult[$column])) {
 456							// Regular column
 457							$currentObject[$column] = $tempResult[$column];
 458						} else {
 459							// Some searching to do...
 460							for ($j = $i; $j >= 0; $j--) {
 461								if (isset($tempResult[$column][$j])) {
 462									$currentObject[$column] = $tempResult[$column][$j];
 463									$j = -1;
 464								}
 465							}
 466						}
 467					}
 468					
 469					// Add object to result table.
 470					// Row class set? If so, convert all data!
 471					if ( ($i == 0 && $this->_data->getRowClass() != 'Zend_Db_Table_Row') ||
 472							$this->_childProviders[$i - 1]->getSource()->getRowClass() != 'Zend_Db_Table_Row') {
 473						$rowClass = $i == 0 ? $this->_data->getRowClass() : $this->_childProviders[$i - 1]->getSource()->getRowClass();
 474						$currentResult[] = call_user_func( array($rowClass, 'fromArray'), (array)$currentObject );
 475					} else {
 476						$currentResult[] = (object)$currentObject;
 477					}
 478				}
 479				
 480				$results[] = $currentResult;
 481			}
 482		}
 483		
 484		// Loop trough data source
 485		foreach ($results as $value) {
 486			// Is it a valid element?
 487			$isValid = true;
 488			
 489			// OfType expresion set? Evaluate it!
 490			if ($isValid && !is_null($this->_ofType)) {
 491				$isValid = $this->_ofType->execute($value);
 492			}
 493					
 494			// The element is valid, check if it is our selection range
 495			if ($isValid) {
 496				// Skip element?
 497				if (!is_null($this->_skipWhile) && $this->_skipWhile->execute($value)) {
 498					$isValid = false;
 499				}
 500				
 501				// Take element?
 502				if (!is_null($this->_takeWhile) && !$this->_takeWhile->execute($value)) {
 503					$isValid = false;
 504					break;
 505				}
 506
 507				// Add the element to the return value if it is a valid element
 508				if ($isValid) {
 509					$returnValue[] = $selector->execute($value);
 510				}
 511			}
 512		}
 513		
 514		// TODO:
 515		// - join
 516		// - aggregates 
 517		// - translate more SQL!
 518		
 519		// Return value
 520		return $returnValue;
 521	}
 522	
 523	/**
 524	 * Converts PHP code into SQL code
 525	 *
 526	 * @param string $phpCode	Code to convert
 527	 * @return string
 528	 */
 529    private function _convertToSql($phpCode) {
 530    	// Some fast cleaning
 531		$phpCode = str_replace('return ', '', $phpCode);
 532		$phpCode = str_replace(';', '', $phpCode);
 533		
 534		// Adapter functions
 535		$adapterFunctions 	= $this->_adapter->getFunctions();
 536
 537		// Go parse!
 538        $tokens 		= token_get_all('<?php ' . $phpCode . '?>');
 539        $stack 			= array();
 540        $tokenId		= 0;
 541        $depth			= 0;
 542
 543        for ($i = 0; $i < count($tokens); $i++) {
 544        	// Ignore token?
 545        	$ignoreToken	= false;
 546        	
 547        	// Token details
 548        	$previousToken 	= $i > 0 ? $tokens[$i - 1] : null;
 549            $token 			= $tokens[$i];
 550            $nextToken	 	= $i < count($tokens) - 1 ? $tokens[$i + 1] : null;
 551            
 552            // Parse token
 553            if (is_array($token)) {
 554                switch ($token[0]) {
 555                    case T_OPEN_TAG:
 556                    case T_CLOSE_TAG:
 557                    	$ignoreToken = true;
 558                    	
 559                        break;
 560
 561                    case T_STRING:
 562                    	if (in_array($token[1], $adapterFunctions)) {
 563                    		// It is an adapter function!
 564                    		$stack[$tokenId]['token'] = '$this->_adapter->' . $token[1];
 565                    		$stack[$tokenId]['type']  = self::T_FUNCTION;
 566                    	} else if (in_array($token[1], self::$_internalFunctions)) {
 567                    		// If it is a PHP function, let's take a lucky
 568                    		// shot and expect the syntax to be the same...
 569                    		$stack[$tokenId]['token'] = $token[1];
 570                    		$stack[$tokenId]['type']  = self::T_FUNCTION;
 571                    	} else {
 572                    		// Probably some sort of constant / property name
 573                    		if (!is_null($previousToken) && is_array($previousToken) && $previousToken[0] == T_OBJECT_OPERATOR) {
 574                    			$stack[$tokenId]['token'] = $this->_adapter->quoteIdentifier($token[1]) . '\'';
 575                    			$stack[$tokenId]['type']  = self::T_PROPERTY;
 576                    		} else {
 577                    			$stack[$tokenId]['token'] = '\'' . $token[1] . '\'';
 578                    			$stack[$tokenId]['type']  = self::T_CONSTANT;
 579                    		}
 580                    	}
 581                        
 582                        break;
 583                        
 584                    case T_VARIABLE:
 585                        $stack[$tokenId]['token'] = $this->_adapter->quote($token[1]);
 586                        $stack[$tokenId]['type']  = self::T_VARIABLE;
 587                        
 588                        break;
 589                        
 590                    case T_OBJECT_OPERATOR: 
 591                    	if (!is_null($previousToken) && is_array($previousToken) && $previousToken[0] == T_VARIABLE) {
 592                    		$stack[$tokenId - 1]['token'] = '\'' . addslashes($stack[$tokenId - 1]['token']) . '.';
 593                    		
 594                    		$ignoreToken = true;
 595                    	} else {
 596                        	$stack[$tokenId]['token'] = '\'.\'';
 597                    	}
 598                        $stack[$tokenId]['type']  = self::T_OBJECT_OPERATOR;
 599                        
 600                        break;
 601                    
 602                    case T_IS_IDENTICAL:
 603                    case T_IS_EQUAL:
 604                        $stack[$tokenId]['token'] = '\' ' . $this->_adapter->operator('=') . ' \'';
 605                        $stack[$tokenId]['type']  = self::T_OPERATOR;
 606                        
 607                        break;
 608                        
 609                    case T_IS_NOT_EQUAL:
 610                    case T_IS_NOT_IDENTICAL:
 611                        $stack[$tokenId]['token'] = '\' ' . $this->_adapter->operator('!=') . ' \'';
 612                        $stack[$tokenId]['type']  = self::T_OPERATOR;
 613                        
 614                        break;
 615                        
 616                    case T_IS_GREATER_OR_EQUAL:
 617                        $stack[$tokenId]['token'] = '\' ' . $this->_adapter->operator('>=') . ' \'';
 618                        $stack[$tokenId]['type']  = self::T_OPERATOR;
 619
 620                        break;
 621
 622                    case T_IS_SMALLER_OR_EQUAL:
 623                        $stack[$tokenId]['token'] = '\' ' . $this->_adapter->operator('<=') . ' \'';
 624                        $stack[$tokenId]['type']  = self::T_OPERATOR;
 625
 626                        break;
 627                    
 628                    case T_BOOLEAN_AND:
 629                    case T_LOGICAL_AND:
 630                        $stack[$tokenId]['token'] = '\' ' . $this->_adapter->operand('AND') . ' \''; 
 631                        $stack[$tokenId]['type']  = self::T_OPERAND;
 632                        
 633                        break;
 634                        
 635                    case T_BOOLEAN_OR:    
 636                    case T_LOGICAL_OR:
 637                        $stack[$tokenId]['token'] = '\' ' . $this->_adapter->operand('OR') . ' \'';
 638                        $stack[$tokenId]['type']  = self::T_OPERAND;
 639
 640                        break;
 641
 642                    default: 
 643                        $stack[$tokenId]['token'] = '\'' . $token[1] . '\'';
 644                        $stack[$tokenId]['type']  = self::T_DEFAULT;
 645
 646                        break;
 647                }
 648            } else {
 649            	// Simple token
 650            	if ($token != '(' && $token != ')') {
 651            		$stack[$tokenId]['token'] = $token;
 652            		$stack[$tokenId]['type']  = self::T_SIMPLE;
 653            		
 654            		if ($token == ',') {
 655            			$stack[$tokenId]['type']  = self::T_ARGUMENT;
 656            		} else if ($token == '+' || $token == '-' || $token == '*' || $token == '/' || $token == '%' || $token == '.') {
 657            			$stack[$tokenId]['token'] = '\'' . $this->_adapter->operator($token) . '\'';
 658            			$stack[$tokenId]['type']  = self::T_ARITHMETIC;
 659            		} else if ($token == '>' || $token == '<') {
 660            			$stack[$tokenId]['token'] = '\' ' . $this->_adapter->operator($token) . ' \'';
 661            			$stack[$tokenId]['type']  = self::T_OPERATOR;
 662            		}
 663            	} else {
 664	            	$stack[$tokenId]['token'] = '';
 665	            	$stack[$tokenId]['type']  = self::T_START_STOP;
 666            	}
 667            }
 668
 669            // Token metadata
 670            if (!$ignoreToken) {
 671	            // Depth
 672	            if (!is_array($token) && $token == '(') $depth++;
 673	            if (!is_array($token) && $token == ')') $depth--;	
 674	            $stack[$tokenId]['depth'] = $depth;         
 675	            
 676	            // Identifier
 677	            $tokenId++;
 678            }
 679        }
 680        
 681        // Display tree
 682        /*
 683        foreach ($stack as $node) {
 684        	for ($i = 0; $i <= $node['depth']; $i++) {
 685        		echo "| ";
 686        	}
 687        	
 688        	echo $node['type'] . ' - ' . $node['token'] . "\r\n";
 689        }
 690        die();
 691        */
 692
 693        // Build compilation string
 694        $compileCode 	= '';
 695        $functionDepth	= array();
 696        $depth 			= 0;
 697        for ($i = 0; $i < count($stack); $i++) {
 698        	// Token details
 699        	$previousToken 	= $i > 0 ? $stack[$i - 1] : null;
 700            $token 			= $stack[$i];
 701            $nextToken	 	= $i < count($stack) - 1 ? $stack[$i + 1] : null;  
 702            
 703        	// Regular token
 704        	if ($token['depth'] == $depth && $token['type'] != self::T_START_STOP) {
 705        		$compileCode .= $token['token'];
 706        	}
 707        	
 708        	// Start/stop
 709        	if ($token['depth'] > $depth && $token['type'] == self::T_START_STOP && !is_null($previousToken) && $previousToken['type'] == self::T_FUNCTION) {
 710        		$compileCode .= '(';
 711        		$functionDepth[$depth] = self::T_FUNCTION;
 712        	} else if ($token['depth'] < $depth && $token['type'] == self::T_START_STOP && $functionDepth[ $token['depth'] ] == self::T_FUNCTION) {
 713        		$compileCode .= ')';
 714        	} else if ($token['depth'] > $depth && $token['type'] == self::T_START_STOP) {
 715        		$compileCode .= '\'(\'';
 716        		$functionDepth[$depth] = self::T_START_STOP;
 717        	} else if ($token['depth'] < $depth && $token['type'] == self::T_START_STOP) {
 718        		$compileCode .= '\')\'';
 719        	}
 720        	
 721        	// Next token needs concatenation?
 722        	if (!is_null($nextToken) && $nextToken['type'] != self::T_ARGUMENT) {
 723        		if (
 724        			!($token['type'] == self::T_FUNCTION && $nextToken['type'] == self::T_START_STOP) &&
 725        			!(!is_null($previousToken) && $previousToken['type'] == self::T_FUNCTION && $token['type'] == self::T_START_STOP) &&
 726        			
 727        			!($token['type'] == self::T_ARGUMENT) &&
 728        			
 729        			!(!is_null($nextToken) && $nextToken['type'] == self::T_START_STOP && isset($functionDepth[ $nextToken['depth'] ]) && $functionDepth[ $nextToken['depth'] ] == self::T_FUNCTION) &&
 730        			
 731        			!($token['type'] == self::T_VARIABLE && !is_null($nextToken) && $nextToken['type'] == self::T_PROPERTY)
 732        			) {
 733        			$compileCode .= ' . ';
 734        		}
 735        	}
 736
 737        	// Depth
 738        	if ($token['depth'] < $depth) {
 739        		unset($functionDepth[$token['depth']]);
 740        	}
 741        	$depth = $token['depth'];
 742        }
 743
 744        // Compile
 745		$compileCode = '$compileCode = ' . $compileCode . ';';
 746		eval($compileCode);
 747
 748        return $compileCode;
 749    }
 750	
 751	/**
 752	 * Where
 753	 *
 754	 * @param  string	$expression	Expression checking if an element should be contained
 755	 * @return PHPLinq_ILinqProvider
 756	 */
 757	public function where($expression) {
 758		$this->_where = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
 759		return $this;
 760	}
 761	
 762	/**
 763	 * Take $n elements
 764	 *
 765	 * @param int $n
 766	 * @return PHPLinq_ILinqProvider
 767	 */
 768	public function take($n) {
 769		$this->_take = $n;
 770		return $this;
 771	}
 772	
 773	/**
 774	 * Skip $n elements
 775	 *
 776	 * @param int $n
 777	 * @return PHPLinq_ILinqProvider
 778	 */
 779	public function skip($n) {
 780		$this->_skip = $n;
 781		return $this;
 782	}
 783	
 784	/**
 785	 * Take elements while $expression evaluates to true
 786	 *
 787	 * @param  string	$expression	Expression to evaluate
 788	 * @return PHPLinq_ILinqProvider
 789	 */
 790	public function takeWhile($expression) {
 791		$this->_takeWhile = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
 792		return $this;
 793	}
 794	
 795	/**
 796	 * Skip elements while $expression evaluates to true
 797	 *
 798	 * @param  string	$expression	Expression to evaluate
 799	 * @return PHPLinq_ILinqProvider
 800	 */
 801	public function skipWhile($expression) {
 802		$this->_skipWhile = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
 803		return $this;
 804	}
 805	
 806	/**
 807	 * OrderBy
 808	 *
 809	 * @param  string	$expression	Expression to order elements by
 810	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
 811	 * @return PHPLinq_ILinqProvider
 812	 */
 813	public function orderBy($expression, $comparer = null) {
 814		$this->_orderBy[0] = new PHPLinq_OrderByExpression($expression, $this->_from, false, $comparer);
 815		return $this;
 816	}
 817	
 818	/**
 819	 * OrderByDescending
 820	 *
 821	 * @param  string	$expression	Expression to order elements by
 822	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
 823	 * @return PHPLinq_ILinqProvider
 824	 */
 825	public function orderByDescending($expression, $comparer = null) {
 826		$this->_orderBy[0] = new PHPLinq_OrderByExpression($expression, $this->_from, true, $comparer);
 827		return $this;
 828	}
 829	
 830	/**
 831	 * ThenBy
 832	 *
 833	 * @param  string	$expression	Expression to order elements by
 834	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
 835	 * @return PHPLinq_ILinqProvider
 836	 */
 837	public function thenBy($expression, $comparer = null) {
 838		$this->_orderBy[] = new PHPLinq_OrderByExpression($expression, $this->_from, false, $comparer);
 839		return $this;
 840	}
 841	
 842	/**
 843	 * ThenByDescending
 844	 *
 845	 * @param  string	$expression	Expression to order elements by
 846	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
 847	 * @return PHPLinq_ILinqProvider
 848	 */
 849	public function thenByDescending($expression, $comparer = null) {
 850		$this->_orderBy[] = new PHPLinq_OrderByExpression($expression, $this->_from, true, $comparer);
 851		return $this;
 852	}
 853	
 854	/**
 855	 * Distinct
 856	 *
 857	 * @param  string	$expression	Ignored. 
 858	 * @return PHPLinq_ILinqProvider
 859	 */
 860	public function distinct($expression) {
 861		$this->_distinct = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
 862		return $this;
 863	}
 864	
 865	/**
 866	 * Select the elements of a certain type
 867	 *
 868	 * @param string $type	Type name
 869	 */
 870	public function ofType($type) {
 871		// Create a new expression
 872		$expression = $this->_from . ' => ';
 873		
 874		// Evaluate type
 875		switch (strtolower($type)) {
 876			case 'array':
 877			case 'bool':
 878			case 'double':
 879			case 'float':
 880			case 'int':
 881			case 'integer':
 882			case 'long':
 883			case 'null':
 884			case 'numeric':
 885			case 'object':
 886			case 'real':
 887			case 'scalar':
 888			case 'string':
 889				$expression .= 'is_' . strtolower($type) . '(' . $this->_from . ')';
 890				break;
 891			default:
 892				$expression .= 'is_a(' . $this->_from . ', "' . $type . '")';
 893				break;
 894		}
 895		
 896		// Assign expression
 897		$this->_ofType = new PHPLinq_Expression($expression, $this->_from);
 898		return $this;
 899	}
 900	
 901	/**
 902	 * Any
 903	 *
 904	 * @param  string	$expression	Expression checking if an element is contained
 905	 * @return boolean
 906	 */
 907	public function any($expression) {
 908		$originalWhere = $this->_where;
 909		
 910		$this->_columns = 'COUNT(*) AS cnt';
 911		$countFrom = $this->_from . ' => ' . $this->_from . '->cnt';
 912		
 913		$result = $this->where($expression)->select($countFrom);
 914		
 915		$this->_columns = '*';
 916		
 917		$this->_where = $originalWhere;
 918		
 919		return $result[0] > 0;
 920	}
 921	
 922	/**
 923	 * All
 924	 *
 925	 * @param  string	$expression	Expression checking if an all elements are contained
 926	 * @return boolean
 927	 */
 928	public function all($expression) {
 929		$originalWhere = $this->_where;
 930		
 931		$this->_columns = 'COUNT(*) AS cnt';
 932		$countFrom = $this->_from . ' => ' . $this->_from . '->cnt';
 933		
 934		$result1 = $this->where($expression)->select($countFrom);
 935		$result2 = $this->where(null)->select($countFrom);
 936
 937		$this->_columns = '*';
 938		
 939		$this->_where = $originalWhere;
 940		
 941		return $result1[0] == $result2[0];
 942	}
 943
 944	/**
 945	 * Contains - Not performed as query! (heavy)
 946	 *
 947	 * @param mixed $element Is the $element contained?
 948	 * @return boolean
 949	 */
 950	public function contains($element) {
 951		return in_array($element, $this->select());
 952	}
 953	
 954	/**
 955	 * Reverse elements - Not performed as query! (heavy)
 956	 * 
 957	 * @param bool $preserveKeys Preserve keys?
 958	 * @return PHPLinq_ILinqProvider
 959	 */
 960	public function reverse($preserveKeys = null) {
 961		$data = array_reverse($this->select(), $preserveKeys);
 962		return linqfrom($this->_from)->in($data);
 963	}
 964	
 965	/**
 966	 * Element at index
 967	 *
 968	 * @param mixed $index Index
 969	 * @return mixed Element at $index
 970	 */
 971	public function elementAt($index = null) {
 972		$originalWhere = $this->_where;
 973		
 974		$result = $this->where(null)->take(1)->skip($index)->select();
 975		
 976		$this->_where = $originalWhere;
 977		
 978		if (count($result) > 0) {
 979			return array_shift($result);
 980		}
 981		return null;
 982	}
 983	
 984	/**
 985	 * Element at index or default
 986	 *
 987	 * @param mixed $index Index
 988	 * @param  mixed $defaultValue Default value to return if nothing is found
 989	 * @return mixed Element at $index
 990	 */
 991	public function elementAtOrDefault($index = null, $defaultValue = null) {
 992		$returnValue = $this->elementAt($index);
 993		if (!is_null($returnValue)) {
 994			return $returnValue;
 995		} else {
 996			return $defaultValue;
 997		}
 998	}
 999	
1000	/**
1001	 * Concatenate data
1002	 *
1003	 * @param mixed $source
1004	 * @return PHPLinq_ILinqProvider
1005	 */
1006	public function concat($source) {
1007		$data = array_merge($this->select(), $source);
1008		return linqfrom($this->_from)->in($data);
1009	}
1010	
1011	/**
1012	 * First
1013	 *
1014	 * @param  string	$expression	Expression which creates a resulting element
1015	 * @return mixed
1016	 */
1017	public function first($expression = null) {
1018		$linqCommand = clone $this;
1019		$result = $linqCommand->skip(0)->take(1)->select($expression);
1020		if (count($result) > 0) {
1021			return array_shift($result);
1022		}
1023		return null;
1024	}
1025	
1026	/**
1027	 * FirstOrDefault 
1028	 *
1029	 * @param  string	$expression	Expression which creates a resulting element
1030	 * @param  mixed	$defaultValue Default value to return if nothing is found
1031	 * @return mixed
1032	 */
1033	public function firstOrDefault ($expression = null, $defaultValue = null) {
1034		$returnValue = $this->first($expression);
1035		if (!is_null($returnValue)) {
1036			return $returnValue;
1037		} else {
1038			return $defaultValue;
1039		}
1040	}
1041	
1042	/**
1043	 * Last
1044	 *
1045	 * @param  string	$expression	Expression which creates a resulting element
1046	 * @return mixed
1047	 */
1048	public function last($expression = null) {
1049		$linqCommand = clone $this;
1050		$result = $linqCommand->reverse()->skip(0)->take(1)->select($expression);
1051		if (count($result) > 0) {
1052			return array_shift($result);
1053		}
1054		return null;
1055	}
1056	
1057	/**
1058	 * LastOrDefault 
1059	 *
1060	 * @param  string	$expression	Expression which creates a resulting element
1061	 * @param  mixed	$defaultValue Default value to return if nothing is found
1062	 * @return mixed
1063	 */
1064	public function lastOrDefault ($expression = null, $defaultValue = null) {
1065		$returnValue = $this->last($expression);
1066		if (!is_null($returnValue)) {
1067			return $returnValue;
1068		} else {
1069			return $defaultValue;
1070		}
1071	}
1072	
1073	/**
1074	 * Single
1075	 *
1076	 * @param  string	$expression	Expression which creates a resulting element
1077	 * @return mixed
1078	 */
1079	public function single($expression = null) {
1080		return $this->first($expression);
1081	}
1082	
1083	/**
1084	 * SingleOrDefault 
1085	 *
1086	 * @param  string	$expression	Expression which creates a resulting element
1087	 * @param  mixed	$defaultValue Default value to return if nothing is found
1088	 * @return mixed
1089	 */
1090	public function singleOrDefault ($expression = null, $defaultValue = null) {
1091		return $this->firstOrDefault($expression, $defaultValue);
1092	}
1093	
1094	/**
1095	 * Join
1096	 *
1097	 * @param string $name
1098	 * @return PHPLinq_Initiator
1099	 */
1100	public function join($name) {
1101		return new PHPLinq_Initiator($name, $this);
1102	}
1103	
1104	/**
1105	 * On
1106	 *
1107	 * @param  string	$expression	Expression representing join condition
1108	 * @return PHPLinq_ILinqProvider
1109	 */
1110	public function on($expression) {
1111		$this->_joinCondition = new PHPLinq_Expression($expression, $this->_from);
1112		return $this->_parentProvider;
1113	}
1114	
1115	/**
1116	 * Count elements
1117	 *
1118	 * @return int Element count
1119	 */
1120	public function count() {
1121		return count($this->_data);
1122	}
1123	
1124	/**
1125	 * Sum elements
1126	 *
1127	 * @return mixed Sum of elements
1128	 */
1129	public function sum() {
1130		return array_sum($this->_data); // $this->aggregate(0, '$s, $t => $s + $t');
1131	}
1132	
1133	/**
1134	 * Minimum of elements
1135	 *
1136	 * @return mixed Minimum of elements
1137	 */
1138	public function min(){
1139		return min($this->_data);
1140	}
1141	
1142	/**
1143	 * Maximum of elements
1144	 *
1145	 * @return mixed Maximum of elements
1146	 */
1147	public function max(){
1148		return max($this->_data);
1149	}
1150	
1151	/**
1152	 * Average of elements
1153	 *
1154	 * @return mixed Average of elements
1155	 */
1156	public function average(){
1157		return $this->sum() / $this->count();
1158	}
1159
1160	/**
1161	 * Aggregate
1162	 * 
1163	 * Example: Equivalent of count(): $this->aggregate(0, '$s, $t => $s + 1');
1164	 *
1165	 * @param int $seed	Seed
1166	 * @param string $expression	Expression defining the aggregate
1167	 * @return mixed aggregate
1168	 */
1169	public function aggregate($seed = 0, $expression) {
1170		$codeExpression = new PHPLinq_Expression($expression);
1171		
1172		$runningValue = $seed;
1173		foreach ($this->_data as $value) {
1174			$runningValue = $codeExpression->execute( array($runningValue, $value) );
1175		}
1176		
1177		return $runningValue;
1178	}
1179	
1180	/**
1181	 * Get query callback (static for all PHPLinq_LinqToZendDb !)
1182	 * 
1183	 * Function accepting a string to which query strings can be logged.
1184	 *
1185	 * @return mixed
1186	 */
1187	public static function getQueryCallback() {
1188	    return self::$_queryCallback;
1189	}
1190	
1191	/**
1192	 * Set query callback (static for all PHPLinq_LinqToZendDb !)
1193	 * 
1194	 * Function accepting a string to which query strings can be logged.
1195	 *
1196	 * @param mixed $value
1197	 */
1198	public static function setQueryCallback($value = null) {
1199	    self::$_queryCallback = $value;
1200	}
1201}