PageRenderTime 26ms CodeModel.GetById 7ms app.highlight 14ms RepoModel.GetById 1ms app.codeStats 0ms

/trunk/Classes/PHPLinq/LinqToObjects.php

#
PHP | 872 lines | 356 code | 115 blank | 401 comment | 57 complexity | f7ef16c4eb51ee1b920eb7ae887522c6 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_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 - 2011 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) || $source instanceof Traversable;
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		if($source instanceof Traversable) {
258			$source = iterator_to_array($source);
259		}
260		
261		$this->_data = $source;
262		return $this;
263	}
264	
265	/**
266	 * Select
267	 *
268	 * @param  string	$expression	Expression which creates a resulting element
269	 * @return mixed
270	 */
271	public function select($expression = null) {
272		// Returnvalue
273		$returnValue = array();
274			
275		// Expression set?
276		if (is_null($expression) || $expression == '') {
277			$expression = $this->_from . ' => ' . $this->_from;
278		}
279
280		// OrderBy set?
281		if (is_array($this->_orderBy) && count($this->_orderBy) > 0) {
282			// Create sorter
283			$sorter = '';
284			
285			// Is there only one OrderBy expression?
286			if (count($this->_orderBy) == 1) {
287				// First sorter
288				$sorter = $this->_orderBy[0]->getFunctionReference();
289			} else {
290				// Create OrderBy expression
291				$compareCode = '';
292				
293				// Compile comparer function
294				$compareCode = "
295					\$result = null;
296				";
297				for ($i = 0; $i < count($this->_orderBy); $i++) {
298					
299					$f = substr($this->_orderBy[$i]->getFunctionReference(), 1); 
300					$compareCode .= "
301					\$result = call_user_func_array(chr(0).'$f', array({$this->_from}A, {$this->_from}B));
302					if (\$result != 0) {
303						return \$result;
304					}
305					";
306					
307				}
308				$compareCode .= "return \$result;";
309				$sorter = create_function($this->_from . 'A, ' . $this->_from . 'B', $compareCode);
310			}
311			
312			// Sort!
313			usort($this->_data, $sorter);
314		}
315		
316		// Data source
317		$dataSource = array();
318		
319		// Build possible join data
320		foreach ($this->_data as $value) {
321			// Child providers set?
322			if (count($this->_childProviders) > 0) {
323				// Data to be joined
324				$joinedData = array();
325				$joinedData[$this->_from][] = $value;
326						
327				// Join other values
328				foreach ($this->_childProviders as $provider) {
329					// Fetch possible join data
330					foreach ($provider->select() as $record) {
331						$joinValid = $provider->getJoinCondition()->execute( array( $this->_from => $value, $provider->getFromName() => $record ) );
332						if ($joinValid) {
333							$joinedData[$provider->getFromName()][] = $record;
334						}
335					}
336				}
337
338				/** BEGIN CARTESIAN JOIN */
339				
340				// Joinable array
341				$joinable = array();
342						
343				// Join data keys
344				$joinedDataKeys = array_keys($joinedData);
345						
346				// Calculate expected size of $joinable
347				$size = (count($joinedData) > 0 ? 1 : 0);
348				foreach ($joinedData as $values) {
349					$size = $size * count($values);
350				}
351				
352				// Create cartesian array
353				for ($i = 0; $i < $size; $i++) {
354					$joinable[$i] = array();
355					
356					for ($j = 0; $j < count($joinedData); $j++) {
357						array_push($joinable[$i], current($joinedData[$joinedDataKeys[$j]]));
358					}
359					
360					// Set cursor on next element in the $joinedData, beginning with the last array
361					for ($j = (count($joinedData)-1); $j >= 0; $j--) {
362						// If next returns true, then break
363						if (next($joinedData[$joinedDataKeys[$j]])) {
364							break;
365						} else {
366							// If next returns false, then reset and go on with previous $joinedData...
367							reset($joinedData[$joinedDataKeys[$j]]);
368						}
369					}
370				}
371				
372				/** END CARTESIAN JOIN */
373						
374				// Join values using selector expression
375				foreach ($joinable as $values) {
376					$dataSource[] = $values;
377				}
378			}
379		}
380		
381		// Data source filled?
382		if (count($dataSource) == 0) {
383			$dataSource = $this->_data;
384		}
385
386		// Distinct values storage
387		$distinctValues = array();
388		
389		// Create selector expression
390		$selector = new PHPLinq_Expression($expression, $this->_from);
391		
392		// Count elements
393		$elementCount = 0;
394		
395		// Loop trough data source
396		foreach ($dataSource as $value) {
397			// Is it a valid element?
398			$isValid = true;
399			
400			// OfType expresion set? Evaluate it!
401			if ($isValid && !is_null($this->_ofType)) {
402				$isValid = $this->_ofType->execute($value);
403			}
404			
405			// Where expresion set? Evaluate it!
406			if ($isValid && !is_null($this->_where)) {
407				$isValid = $this->_where->execute($value);
408			}
409			
410			// Distinct expression set? Evaluate it!
411			if ($isValid && !is_null($this->_distinct)) {
412				$distinctKey = $this->_distinct->execute($value);
413				if (isset($distinctValues[$distinctKey])) {
414					$isValid = false;
415				} else {
416					$distinctValues[$distinctKey] = 1; 
417				}
418			}
419			
420			// The element is valid, check if it is our selection range
421			if ($isValid) {
422					
423				// Skip element?
424				if (!is_null($this->_skipWhile) && $this->_skipWhile->execute($value)) {
425					$isValid = false;
426				}
427				if (!is_null($this->_skip) && $elementCount < $this->_skip) {
428					$isValid = false;
429				}
430				
431				// Take element?
432				if (!is_null($this->_takeWhile) && !$this->_takeWhile->execute($value)) {
433					$isValid = false;
434					break;
435				}
436				if (!is_null($this->_take) && count($returnValue) >= $this->_take) {
437					$isValid = false;
438					break;
439				}
440
441				// Next element
442				$elementCount++;
443
444				// Add the element to the return value if it is a valid element
445				if ($isValid) {
446					$returnValue[] = $selector->execute($value);
447				}
448			}
449		}
450		
451		// Return value
452		return $returnValue;
453	}
454	
455	/**
456	 * Where
457	 *
458	 * @param  string	$expression	Expression checking if an element should be contained
459	 * @return PHPLinq_ILinqProvider
460	 */
461	public function where($expression) {
462		$this->_where = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
463		return $this;
464	}
465	
466	/**
467	 * Take $n elements
468	 *
469	 * @param int $n
470	 * @return PHPLinq_ILinqProvider
471	 */
472	public function take($n) {
473		$this->_take = $n;
474		return $this;
475	}
476	
477	/**
478	 * Skip $n elements
479	 *
480	 * @param int $n
481	 * @return PHPLinq_ILinqProvider
482	 */
483	public function skip($n) {
484		$this->_skip = $n;
485		return $this;
486	}
487	
488	/**
489	 * Take elements while $expression evaluates to true
490	 *
491	 * @param  string	$expression	Expression to evaluate
492	 * @return PHPLinq_ILinqProvider
493	 */
494	public function takeWhile($expression) {
495		$this->_takeWhile = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
496		return $this;
497	}
498	
499	/**
500	 * Skip elements while $expression evaluates to true
501	 *
502	 * @param  string	$expression	Expression to evaluate
503	 * @return PHPLinq_ILinqProvider
504	 */
505	public function skipWhile($expression) {
506		$this->_skipWhile = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
507		return $this;
508	}
509	
510	/**
511	 * OrderBy
512	 *
513	 * @param  string	$expression	Expression to order elements by
514	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
515	 * @return PHPLinq_ILinqProvider
516	 */
517	public function orderBy($expression, $comparer = null) {
518		$this->_orderBy[0] = new PHPLinq_OrderByExpression($expression, $this->_from, false, $comparer);
519		return $this;
520	}
521	
522	/**
523	 * OrderByDescending
524	 *
525	 * @param  string	$expression	Expression to order elements by
526	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
527	 * @return PHPLinq_ILinqProvider
528	 */
529	public function orderByDescending($expression, $comparer = null) {
530		$this->_orderBy[0] = new PHPLinq_OrderByExpression($expression, $this->_from, true, $comparer);
531		return $this;
532	}
533	
534	/**
535	 * ThenBy
536	 *
537	 * @param  string	$expression	Expression to order elements by
538	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
539	 * @return PHPLinq_ILinqProvider
540	 */
541	public function thenBy($expression, $comparer = null) {
542		$this->_orderBy[] = new PHPLinq_OrderByExpression($expression, $this->_from, false, $comparer);
543		return $this;
544	}
545	
546	/**
547	 * ThenByDescending
548	 *
549	 * @param  string	$expression	Expression to order elements by
550	 * @param  string	$comparer	Comparer function (taking 2 arguments, returning -1, 0, 1)
551	 * @return PHPLinq_ILinqProvider
552	 */
553	public function thenByDescending($expression, $comparer = null) {
554		$this->_orderBy[] = new PHPLinq_OrderByExpression($expression, $this->_from, true, $comparer);
555		return $this;
556	}
557	
558	/**
559	 * Distinct
560	 *
561	 * @param  string	$expression	Expression to retrieve the key value. 
562	 * @return PHPLinq_ILinqProvider
563	 */
564	public function distinct($expression) {
565		$this->_distinct = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
566		return $this;
567	}
568	
569	/**
570	 * Select the elements of a certain type
571	 *
572	 * @param string $type	Type name
573	 */
574	public function ofType($type) {
575		// Create a new expression
576		$expression = $this->_from . ' => ';
577		
578		// Evaluate type
579		switch (strtolower($type)) {
580			case 'array':
581			case 'bool':
582			case 'double':
583			case 'float':
584			case 'int':
585			case 'integer':
586			case 'long':
587			case 'null':
588			case 'numeric':
589			case 'object':
590			case 'real':
591			case 'scalar':
592			case 'string':
593				$expression .= 'is_' . strtolower($type) . '(' . $this->_from . ')';
594				break;
595			default:
596				$expression .= 'is_a(' . $this->_from . ', "' . $type . '")';
597				break;
598		}
599		
600		// Assign expression
601		$this->_ofType = new PHPLinq_Expression($expression, $this->_from);
602		return $this;
603	}
604	
605	/**
606	 * Any
607	 *
608	 * @param  string	$expression	Expression checking if an element is contained
609	 * @return boolean
610	 */
611	public function any($expression) {
612		$originalWhere = $this->_where;
613		
614		$result = $this->where($expression)->select($this->_from);
615		
616		$this->_where = $originalWhere;
617		
618		return count($result) > 0;
619	}
620	
621	/**
622	 * All
623	 *
624	 * @param  string	$expression	Expression checking if an all elements are contained
625	 * @return boolean
626	 */
627	public function all($expression) {
628		$originalWhere = $this->_where;
629		
630		$result = $this->where($expression)->select($this->_from);
631		
632		$this->_where = $originalWhere;
633		
634		return count($result) == count($this->_data);
635	}
636
637	/**
638	 * Contains
639	 *
640	 * @param mixed $element Is the $element contained?
641	 * @return boolean
642	 */
643	public function contains($element) {
644		return in_array($element, $this->_data);
645	}
646	
647	/**
648	 * Reverse elements
649	 * 
650	 * @param bool $preserveKeys Preserve keys?
651	 * @return PHPLinq_ILinqProvider
652	 */
653	public function reverse($preserveKeys = null) {
654		$data = array_reverse($this->select(), $preserveKeys);
655		return linqfrom($this->_from)->in($data);
656	}
657	
658	/**
659	 * Element at index
660	 *
661	 * @param mixed $index Index
662	 * @return mixed Element at $index
663	 */
664	public function elementAt($index = null) {
665		$originalWhere = $this->_where;
666		
667		$result = isset($this->_data[$index]) ? $this->_data[$index] : null;
668		
669		$this->_where = $originalWhere;
670		
671		if (is_array($result) && count($result) > 0) {
672			return array_shift($result);
673		}
674		return null;
675	}
676	
677	/**
678	 * Element at index or default
679	 *
680	 * @param mixed $index Index
681	 * @param  mixed $defaultValue Default value to return if nothing is found
682	 * @return mixed Element at $index
683	 */
684	public function elementAtOrDefault($index = null, $defaultValue = null) {
685		$returnValue = $this->elementAt($index);
686		if (!is_null($returnValue)) {
687			return $returnValue;
688		} else {
689			return $defaultValue;
690		}
691	}
692	
693	/**
694	 * Concatenate data
695	 *
696	 * @param mixed $source
697	 * @return PHPLinq_ILinqProvider
698	 */
699	public function concat($source) {
700		$data = array_merge($this->select(), $source);
701		return linqfrom($this->_from)->in($data);
702	}
703	
704	/**
705	 * First
706	 *
707	 * @param  string	$expression	Expression which creates a resulting element
708	 * @return mixed
709	 */
710	public function first($expression = null) {
711		$linqCommand = clone $this;
712		$result = $linqCommand->skip(0)->take(1)->select($expression);
713		if (count($result) > 0) {
714			return array_shift($result);
715		}
716		return null;
717	}
718	
719	/**
720	 * FirstOrDefault 
721	 *
722	 * @param  string	$expression	Expression which creates a resulting element
723	 * @param  mixed	$defaultValue Default value to return if nothing is found
724	 * @return mixed
725	 */
726	public function firstOrDefault ($expression = null, $defaultValue = null) {
727		$returnValue = $this->first($expression);
728		if (!is_null($returnValue)) {
729			return $returnValue;
730		} else {
731			return $defaultValue;
732		}
733	}
734	
735	/**
736	 * Last
737	 *
738	 * @param  string	$expression	Expression which creates a resulting element
739	 * @return mixed
740	 */
741	public function last($expression = null) {
742		$linqCommand = clone $this;
743		$result = $linqCommand->reverse()->skip(0)->take(1)->select($expression);
744		if (count($result) > 0) {
745			return array_shift($result);
746		}
747		return null;
748	}
749	
750	/**
751	 * LastOrDefault 
752	 *
753	 * @param  string	$expression	Expression which creates a resulting element
754	 * @param  mixed	$defaultValue Default value to return if nothing is found
755	 * @return mixed
756	 */
757	public function lastOrDefault ($expression = null, $defaultValue = null) {
758		$returnValue = $this->last($expression);
759		if (!is_null($returnValue)) {
760			return $returnValue;
761		} else {
762			return $defaultValue;
763		}
764	}
765	
766	/**
767	 * Single
768	 *
769	 * @param  string	$expression	Expression which creates a resulting element
770	 * @return mixed
771	 */
772	public function single($expression = null) {
773		return $this->first($expression);
774	}
775	
776	/**
777	 * SingleOrDefault 
778	 *
779	 * @param  string	$expression	Expression which creates a resulting element
780	 * @param  mixed	$defaultValue Default value to return if nothing is found
781	 * @return mixed
782	 */
783	public function singleOrDefault ($expression = null, $defaultValue = null) {
784		return $this->firstOrDefault($expression, $defaultValue);
785	}
786	
787	/**
788	 * Join
789	 *
790	 * @param string $name
791	 * @return PHPLinq_Initiator
792	 */
793	public function join($name) {
794		return new PHPLinq_Initiator($name, $this);
795	}
796	
797	/**
798	 * On
799	 *
800	 * @param  string	$expression	Expression representing join condition
801	 * @return PHPLinq_ILinqProvider
802	 */
803	public function on($expression) {
804		$this->_joinCondition = new PHPLinq_Expression($expression, $this->_from);
805		return $this->_parentProvider;
806	}
807	
808	/**
809	 * Count elements
810	 *
811	 * @return int Element count
812	 */
813	public function count() {
814		return count($this->_data);
815	}
816	
817	/**
818	 * Sum elements
819	 *
820	 * @return mixed Sum of elements
821	 */
822	public function sum() {
823		return array_sum($this->_data); // $this->aggregate(0, '$s, $t => $s + $t');
824	}
825	
826	/**
827	 * Minimum of elements
828	 *
829	 * @return mixed Minimum of elements
830	 */
831	public function min(){
832		return min($this->_data);
833	}
834	
835	/**
836	 * Maximum of elements
837	 *
838	 * @return mixed Maximum of elements
839	 */
840	public function max(){
841		return max($this->_data);
842	}
843	
844	/**
845	 * Average of elements
846	 *
847	 * @return mixed Average of elements
848	 */
849	public function average(){
850		return $this->sum() / $this->count();
851	}
852
853	/**
854	 * Aggregate
855	 * 
856	 * Example: Equivalent of count(): $this->aggregate(0, '$s, $t => $s + 1');
857	 *
858	 * @param int $seed	Seed
859	 * @param string $expression	Expression defining the aggregate
860	 * @return mixed aggregate
861	 */
862	public function aggregate($seed = 0, $expression) {
863		$codeExpression = new PHPLinq_Expression($expression);
864		
865		$runningValue = $seed;
866		foreach ($this->_data as $value) {
867			$runningValue = $codeExpression->execute( array($runningValue, $value) );
868		}
869		
870		return $runningValue;
871	}
872}