PageRenderTime 63ms CodeModel.GetById 3ms app.highlight 52ms RepoModel.GetById 1ms app.codeStats 0ms

/trunk/Classes/PHPLinq/LinqToAzure.php

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