PageRenderTime 32ms CodeModel.GetById 12ms app.highlight 14ms RepoModel.GetById 1ms app.codeStats 0ms

/branches/v0.4.0/Classes/PHPLinq/LinqToObjects.php

#
PHP | 868 lines | 353 code | 114 blank | 401 comment | 54 complexity | a2b0bb7a48bc0f02dcd34c15835ae65b MD5 | raw file
  1<?php
  2/**
  3 * PHPLinq
  4 *
  5 * Copyright (c) 2008 - 2009 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 - 2009 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_Expression */
 36require_once('PHPLinq/Expression.php');
 37
 38/** PHPLinq_OrderByExpression */
 39require_once('PHPLinq/OrderByExpression.php');
 40
 41/** PHPLinq_Initiator */
 42require_once('PHPLinq/Initiator.php');
 43
 44/** Register ILinqProvider */
 45PHPLinq_Initiator::registerProvider('PHPLinq_LinqToObjects');
 46
 47
 48/**
 49 * PHPLinq_LinqToObjects
 50 *
 51 * @category   PHPLinq
 52 * @package    PHPLinq
 53 * @copyright  Copyright (c) 2008 - 2009 PHPLinq (http://www.codeplex.com/PHPLinq)
 54 */
 55class PHPLinq_LinqToObjects implements PHPLinq_ILinqProvider {
 56	/**
 57	 * Default variable name
 58	 *
 59	 * @var string
 60	 */
 61	private $_from = '';
 62	
 63	/**
 64	 * Data source
 65	 *
 66	 * @var mixed
 67	 */
 68	private $_data = null;
 69	
 70	/**
 71	 * Where expression
 72	 *
 73	 * @var PHPLinq_Expression
 74	 */
 75	private $_where = null;
 76	
 77	/**
 78	 * Take n elements
 79	 *
 80	 * @var int?
 81	 */
 82	private $_take = null;
 83	
 84	/**
 85	 * Skip n elements
 86	 *
 87	 * @var int?
 88	 */
 89	private $_skip = null;
 90	
 91	/**
 92	 * Take while expression is true
 93	 *
 94	 * @var PHPLinq_Expression
 95	 */
 96	private $_takeWhile = null;
 97	
 98	/**
 99	 * Skip while expression is true
100	 *
101	 * @var PHPLinq_Expression
102	 */
103	private $_skipWhile = null;
104	
105	/**
106	 * OrderBy expressions
107	 *
108	 * @var PHPLinq_Expression[]
109	 */
110	private $_orderBy = array();
111	
112	/**
113	 * Distinct expression
114	 *
115	 * @var PHPLinq_Expression
116	 */
117	private $_distinct = null;
118	
119	/**
120	 * OfType expression
121	 *
122	 * @var PHPLinq_Expression
123	 */
124	private $_ofType = null;
125	
126	/**
127	 * Parent PHPLinq_ILinqProvider instance, used with join conditions
128	 *
129	 * @var PHPLinq_ILinqProvider
130	 */
131	private $_parentProvider = null;
132	
133	/**
134	 * Child PHPLinq_ILinqProvider instances, used with join conditions
135	 *
136	 * @var PHPLinq_ILinqProvider[]
137	 */
138	private $_childProviders = array();
139	
140	/**
141	 * Join condition
142	 *
143	 * @var PHPLinq_Expression
144	 */
145	private $_joinCondition = null;
146
147	/**
148	 * Is object destructing?
149	 *
150	 * @var bool
151	 */
152	private $_isDestructing;
153	
154	/**
155	 * Can this provider type handle data in $source?
156	 *
157	 * @param mixed $source
158	 * @return bool
159	 */
160	public static function handles($source) {
161		return is_array($source);
162	}
163	
164	/**
165	 * Create a new class instance
166	 *
167	 * @param string $name
168	 * @param PHPLinq_ILinqProvider $parentProvider Optional parent PHPLinq_ILinqProvider instance, used with join conditions
169	 * @return PHPLinq_ILinqProvider
170	 */
171	public function __construct($name, PHPLinq_ILinqProvider $parentProvider = null) {
172		$this->_from = $name;
173		
174		if (!is_null($parentProvider)) {
175			$this->_parentProvider = $parentProvider;
176			$parentProvider->addChildProvider($this);
177		}
178		
179		return $this;
180	}
181	
182	/**
183	 * Class destructor
184	 */
185	public function __destruct() {
186		$this->_isDestructing = true;
187
188		if (isset($this->_parentProvider) && !is_null($this->_parentProvider)) {
189			if (!$this->_parentProvider->__isDestructing()) {
190				$this->_parentProvider->__destruct();
191			}
192			$this->_parentProvider = null;
193			unset($this->_parentProvider);
194		}
195		
196		if (!is_null($this->_childProviders)) {
197			foreach ($this->_childProviders as $provider) {
198				$provider->__destruct();
199				$provider = null;
200				unset($provider);
201			}
202		}
203	}
204
205	/**
206	 * Is object destructing?
207	 *
208	 * @return bool
209	 */
210	public function __isDestructing() {
211		return $this->_isDestructing;
212	}
213	
214	/**
215	 * Get join condition
216	 *
217	 * @return PHPLinq_Expression
218	 */
219	public function getJoinCondition() {
220		return $this->_joinCondition;
221	}
222	
223	/**
224	 * Add child provider, used with joins
225	 *
226	 * @param PHPLinq_ILinqProvider $provider
227	 */
228	public function addChildProvider(PHPLinq_ILinqProvider $provider) {
229		$this->_childProviders[] = $provider;
230	}
231	
232	/**
233	 * Retrieve "from" name
234	 *
235	 * @return string
236	 */
237	public function getFromName() {
238		return $this->_from;
239	}
240	
241	/**
242	 * Retrieve data in data source
243	 *
244	 * @return mixed
245	 */
246	public function getSource() {
247		return $this->_data;
248	}
249	
250	/**
251	 * Set source of data
252	 *
253	 * @param mixed $source
254	 * @return PHPLinq_ILinqProvider
255	 */
256	public function in($source) {
257		$this->_data = $source;
258		return $this;
259	}
260	
261	/**
262	 * Select
263	 *
264	 * @param  string	$expression	Expression which creates a resulting element
265	 * @return mixed
266	 */
267	public function select($expression = null) {
268		// Returnvalue
269		$returnValue = array();
270			
271		// Expression set?
272		if (is_null($expression) || $expression == '') {
273			$expression = $this->_from . ' => ' . $this->_from;
274		}
275
276		// OrderBy set?
277		if (is_array($this->_orderBy) && count($this->_orderBy) > 0) {
278			// Create sorter
279			$sorter = '';
280			
281			// Is there only one OrderBy expression?
282			if (count($this->_orderBy) == 1) {
283				// First sorter
284				$sorter = $this->_orderBy[0]->getFunctionReference();
285			} else {
286				// Create OrderBy expression
287				$compareCode = '';
288				
289				// Compile comparer function
290				$compareCode = "
291					\$result = null;
292				";
293				for ($i = 0; $i < count($this->_orderBy); $i++) {
294					
295					$f = substr($this->_orderBy[$i]->getFunctionReference(), 1); 
296					$compareCode .= "
297					\$result = call_user_func_array(chr(0).'$f', array({$this->_from}A, {$this->_from}B));
298					if (\$result != 0) {
299						return \$result;
300					}
301					";
302					
303				}
304				$compareCode .= "return \$result;";
305				$sorter = create_function($this->_from . 'A, ' . $this->_from . 'B', $compareCode);
306			}
307			
308			// Sort!
309			usort($this->_data, $sorter);
310		}
311		
312		// Data source
313		$dataSource = array();
314		
315		// Build possible join data
316		foreach ($this->_data as $value) {
317			// Child providers set?
318			if (count($this->_childProviders) > 0) {
319				// Data to be joined
320				$joinedData = array();
321				$joinedData[$this->_from][] = $value;
322						
323				// Join other values
324				foreach ($this->_childProviders as $provider) {
325					// Fetch possible join data
326					foreach ($provider->select() as $record) {
327						$joinValid = $provider->getJoinCondition()->execute( array( $this->_from => $value, $provider->getFromName() => $record ) );
328						if ($joinValid) {
329							$joinedData[$provider->getFromName()][] = $record;
330						}
331					}
332				}
333
334				/** BEGIN CARTESIAN JOIN */
335				
336				// Joinable array
337				$joinable = array();
338						
339				// Join data keys
340				$joinedDataKeys = array_keys($joinedData);
341						
342				// Calculate expected size of $joinable
343				$size = (count($joinedData) > 0 ? 1 : 0);
344				foreach ($joinedData as $values) {
345					$size = $size * count($values);
346				}
347				
348				// Create cartesian array
349				for ($i = 0; $i < $size; $i++) {
350					$joinable[$i] = array();
351					
352					for ($j = 0; $j < count($joinedData); $j++) {
353						array_push($joinable[$i], current($joinedData[$joinedDataKeys[$j]]));
354					}
355					
356					// Set cursor on next element in the $joinedData, beginning with the last array
357					for ($j = (count($joinedData)-1); $j >= 0; $j--) {
358						// If next returns true, then break
359						if (next($joinedData[$joinedDataKeys[$j]])) {
360							break;
361						} else {
362							// If next returns false, then reset and go on with previous $joinedData...
363							reset($joinedData[$joinedDataKeys[$j]]);
364						}
365					}
366				}
367				
368				/** END CARTESIAN JOIN */
369						
370				// Join values using selector expression
371				foreach ($joinable as $values) {
372					$dataSource[] = $values;
373				}
374			}
375		}
376		
377		// Data source filled?
378		if (count($dataSource) == 0) {
379			$dataSource = $this->_data;
380		}
381
382		// Distinct values storage
383		$distinctValues = array();
384		
385		// Create selector expression
386		$selector = new PHPLinq_Expression($expression, $this->_from);
387		
388		// Count elements
389		$elementCount = 0;
390		
391		// Loop trough data source
392		foreach ($dataSource as $value) {
393			// Is it a valid element?
394			$isValid = true;
395			
396			// OfType expresion set? Evaluate it!
397			if ($isValid && !is_null($this->_ofType)) {
398				$isValid = $this->_ofType->execute($value);
399			}
400			
401			// Where expresion set? Evaluate it!
402			if ($isValid && !is_null($this->_where)) {
403				$isValid = $this->_where->execute($value);
404			}
405			
406			// Distinct expression set? Evaluate it!
407			if ($isValid && !is_null($this->_distinct)) {
408				$distinctKey = $this->_distinct->execute($value);
409				if (isset($distinctValues[$distinctKey])) {
410					$isValid = false;
411				} else {
412					$distinctValues[$distinctKey] = 1; 
413				}
414			}
415			
416			// The element is valid, check if it is our selection range
417			if ($isValid) {
418					
419				// Skip element?
420				if (!is_null($this->_skipWhile) && $this->_skipWhile->execute($value)) {
421					$isValid = false;
422				}
423				if (!is_null($this->_skip) && $elementCount < $this->_skip) {
424					$isValid = false;
425				}
426				
427				// Take element?
428				if (!is_null($this->_takeWhile) && !$this->_takeWhile->execute($value)) {
429					$isValid = false;
430					break;
431				}
432				if (!is_null($this->_take) && count($returnValue) >= $this->_take) {
433					$isValid = false;
434					break;
435				}
436
437				// Next element
438				$elementCount++;
439
440				// Add the element to the return value if it is a valid element
441				if ($isValid) {
442					$returnValue[] = $selector->execute($value);
443				}
444			}
445		}
446		
447		// Return value
448		return $returnValue;
449	}
450	
451	/**
452	 * Where
453	 *
454	 * @param  string	$expression	Expression checking if an element should be contained
455	 * @return PHPLinq_ILinqProvider
456	 */
457	public function where($expression) {
458		$this->_where = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
459		return $this;
460	}
461	
462	/**
463	 * Take $n elements
464	 *
465	 * @param int $n
466	 * @return PHPLinq_ILinqProvider
467	 */
468	public function take($n) {
469		$this->_take = $n;
470		return $this;
471	}
472	
473	/**
474	 * Skip $n elements
475	 *
476	 * @param int $n
477	 * @return PHPLinq_ILinqProvider
478	 */
479	public function skip($n) {
480		$this->_skip = $n;
481		return $this;
482	}
483	
484	/**
485	 * Take elements while $expression evaluates to true
486	 *
487	 * @param  string	$expression	Expression to evaluate
488	 * @return PHPLinq_ILinqProvider
489	 */
490	public function takeWhile($expression) {
491		$this->_takeWhile = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
492		return $this;
493	}
494	
495	/**
496	 * Skip elements while $expression evaluates to true
497	 *
498	 * @param  string	$expression	Expression to evaluate
499	 * @return PHPLinq_ILinqProvider
500	 */
501	public function skipWhile($expression) {
502		$this->_skipWhile = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
503		return $this;
504	}
505	
506	/**
507	 * OrderBy
508	 *
509	 * @param  string	$expression	Expression to order elements by
510	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
511	 * @return PHPLinq_ILinqProvider
512	 */
513	public function orderBy($expression, $comparer = null) {
514		$this->_orderBy[0] = new PHPLinq_OrderByExpression($expression, $this->_from, false, $comparer);
515		return $this;
516	}
517	
518	/**
519	 * OrderByDescending
520	 *
521	 * @param  string	$expression	Expression to order elements by
522	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
523	 * @return PHPLinq_ILinqProvider
524	 */
525	public function orderByDescending($expression, $comparer = null) {
526		$this->_orderBy[0] = new PHPLinq_OrderByExpression($expression, $this->_from, true, $comparer);
527		return $this;
528	}
529	
530	/**
531	 * ThenBy
532	 *
533	 * @param  string	$expression	Expression to order elements by
534	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
535	 * @return PHPLinq_ILinqProvider
536	 */
537	public function thenBy($expression, $comparer = null) {
538		$this->_orderBy[] = new PHPLinq_OrderByExpression($expression, $this->_from, false, $comparer);
539		return $this;
540	}
541	
542	/**
543	 * ThenByDescending
544	 *
545	 * @param  string	$expression	Expression to order elements by
546	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
547	 * @return PHPLinq_ILinqProvider
548	 */
549	public function thenByDescending($expression, $comparer = null) {
550		$this->_orderBy[] = new PHPLinq_OrderByExpression($expression, $this->_from, true, $comparer);
551		return $this;
552	}
553	
554	/**
555	 * Distinct
556	 *
557	 * @param  string	$expression	Expression to retrieve the key value. 
558	 * @return PHPLinq_ILinqProvider
559	 */
560	public function distinct($expression) {
561		$this->_distinct = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
562		return $this;
563	}
564	
565	/**
566	 * Select the elements of a certain type
567	 *
568	 * @param string $type	Type name
569	 */
570	public function ofType($type) {
571		// Create a new expression
572		$expression = $this->_from . ' => ';
573		
574		// Evaluate type
575		switch (strtolower($type)) {
576			case 'array':
577			case 'bool':
578			case 'double':
579			case 'float':
580			case 'int':
581			case 'integer':
582			case 'long':
583			case 'null':
584			case 'numeric':
585			case 'object':
586			case 'real':
587			case 'scalar':
588			case 'string':
589				$expression .= 'is_' . strtolower($type) . '(' . $this->_from . ')';
590				break;
591			default:
592				$expression .= 'is_a(' . $this->_from . ', "' . $type . '")';
593				break;
594		}
595		
596		// Assign expression
597		$this->_ofType = new PHPLinq_Expression($expression, $this->_from);
598		return $this;
599	}
600	
601	/**
602	 * Any
603	 *
604	 * @param  string	$expression	Expression checking if an element is contained
605	 * @return boolean
606	 */
607	public function any($expression) {
608		$originalWhere = $this->_where;
609		
610		$result = $this->where($expression)->select($this->_from);
611		
612		$this->_where = $originalWhere;
613		
614		return count($result) > 0;
615	}
616	
617	/**
618	 * All
619	 *
620	 * @param  string	$expression	Expression checking if an all elements are contained
621	 * @return boolean
622	 */
623	public function all($expression) {
624		$originalWhere = $this->_where;
625		
626		$result = $this->where($expression)->select($this->_from);
627		
628		$this->_where = $originalWhere;
629		
630		return count($result) == count($this->_data);
631	}
632
633	/**
634	 * Contains
635	 *
636	 * @param mixed $element Is the $element contained?
637	 * @return boolean
638	 */
639	public function contains($element) {
640		return in_array($element, $this->_data);
641	}
642	
643	/**
644	 * Reverse elements
645	 * 
646	 * @param bool $preserveKeys Preserve keys?
647	 * @return PHPLinq_ILinqProvider
648	 */
649	public function reverse($preserveKeys = null) {
650		$data = array_reverse($this->select(), $preserveKeys);
651		return linqfrom($this->_from)->in($data);
652	}
653	
654	/**
655	 * Element at index
656	 *
657	 * @param mixed $index Index
658	 * @return mixed Element at $index
659	 */
660	public function elementAt($index = null) {
661		$originalWhere = $this->_where;
662		
663		$result = isset($this->_data[$index]) ? $this->_data[$index] : null;
664		
665		$this->_where = $originalWhere;
666		
667		if (count($result) > 0) {
668			return array_shift($result);
669		}
670		return null;
671	}
672	
673	/**
674	 * Element at index or default
675	 *
676	 * @param mixed $index Index
677	 * @param  mixed $defaultValue Default value to return if nothing is found
678	 * @return mixed Element at $index
679	 */
680	public function elementAtOrDefault($index = null, $defaultValue = null) {
681		$returnValue = $this->elementAt($index);
682		if (!is_null($returnValue)) {
683			return $returnValue;
684		} else {
685			return $defaultValue;
686		}
687	}
688	
689	/**
690	 * Concatenate data
691	 *
692	 * @param mixed $source
693	 * @return PHPLinq_ILinqProvider
694	 */
695	public function concat($source) {
696		$data = array_merge($this->select(), $source);
697		return linqfrom($this->_from)->in($data);
698	}
699	
700	/**
701	 * First
702	 *
703	 * @param  string	$expression	Expression which creates a resulting element
704	 * @return mixed
705	 */
706	public function first($expression = null) {
707		$linqCommand = clone $this;
708		$result = $linqCommand->skip(0)->take(1)->select($expression);
709		if (count($result) > 0) {
710			return array_shift($result);
711		}
712		return null;
713	}
714	
715	/**
716	 * FirstOrDefault 
717	 *
718	 * @param  string	$expression	Expression which creates a resulting element
719	 * @param  mixed	$defaultValue Default value to return if nothing is found
720	 * @return mixed
721	 */
722	public function firstOrDefault ($expression = null, $defaultValue = null) {
723		$returnValue = $this->first($expression);
724		if (!is_null($returnValue)) {
725			return $returnValue;
726		} else {
727			return $defaultValue;
728		}
729	}
730	
731	/**
732	 * Last
733	 *
734	 * @param  string	$expression	Expression which creates a resulting element
735	 * @return mixed
736	 */
737	public function last($expression = null) {
738		$linqCommand = clone $this;
739		$result = $linqCommand->reverse()->skip(0)->take(1)->select($expression);
740		if (count($result) > 0) {
741			return array_shift($result);
742		}
743		return null;
744	}
745	
746	/**
747	 * LastOrDefault 
748	 *
749	 * @param  string	$expression	Expression which creates a resulting element
750	 * @param  mixed	$defaultValue Default value to return if nothing is found
751	 * @return mixed
752	 */
753	public function lastOrDefault ($expression = null, $defaultValue = null) {
754		$returnValue = $this->last($expression);
755		if (!is_null($returnValue)) {
756			return $returnValue;
757		} else {
758			return $defaultValue;
759		}
760	}
761	
762	/**
763	 * Single
764	 *
765	 * @param  string	$expression	Expression which creates a resulting element
766	 * @return mixed
767	 */
768	public function single($expression = null) {
769		return $this->first($expression);
770	}
771	
772	/**
773	 * SingleOrDefault 
774	 *
775	 * @param  string	$expression	Expression which creates a resulting element
776	 * @param  mixed	$defaultValue Default value to return if nothing is found
777	 * @return mixed
778	 */
779	public function singleOrDefault ($expression = null, $defaultValue = null) {
780		return $this->firstOrDefault($expression, $defaultValue);
781	}
782	
783	/**
784	 * Join
785	 *
786	 * @param string $name
787	 * @return PHPLinq_Initiator
788	 */
789	public function join($name) {
790		return new PHPLinq_Initiator($name, $this);
791	}
792	
793	/**
794	 * On
795	 *
796	 * @param  string	$expression	Expression representing join condition
797	 * @return PHPLinq_ILinqProvider
798	 */
799	public function on($expression) {
800		$this->_joinCondition = new PHPLinq_Expression($expression, $this->_from);
801		return $this->_parentProvider;
802	}
803	
804	/**
805	 * Count elements
806	 *
807	 * @return int Element count
808	 */
809	public function count() {
810		return count($this->_data);
811	}
812	
813	/**
814	 * Sum elements
815	 *
816	 * @return mixed Sum of elements
817	 */
818	public function sum() {
819		return array_sum($this->_data); // $this->aggregate(0, '$s, $t => $s + $t');
820	}
821	
822	/**
823	 * Minimum of elements
824	 *
825	 * @return mixed Minimum of elements
826	 */
827	public function min(){
828		return min($this->_data);
829	}
830	
831	/**
832	 * Maximum of elements
833	 *
834	 * @return mixed Maximum of elements
835	 */
836	public function max(){
837		return max($this->_data);
838	}
839	
840	/**
841	 * Average of elements
842	 *
843	 * @return mixed Average of elements
844	 */
845	public function average(){
846		return $this->sum() / $this->count();
847	}
848
849	/**
850	 * Aggregate
851	 * 
852	 * Example: Equivalent of count(): $this->aggregate(0, '$s, $t => $s + 1');
853	 *
854	 * @param int $seed	Seed
855	 * @param string $expression	Expression defining the aggregate
856	 * @return mixed aggregate
857	 */
858	public function aggregate($seed = 0, $expression) {
859		$codeExpression = new PHPLinq_Expression($expression);
860		
861		$runningValue = $seed;
862		foreach ($this->_data as $value) {
863			$runningValue = $codeExpression->execute( array($runningValue, $value) );
864		}
865		
866		return $runningValue;
867	}
868}