PageRenderTime 62ms CodeModel.GetById 27ms app.highlight 28ms RepoModel.GetById 1ms app.codeStats 0ms

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

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