scratch

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 86  →  ?path2? @ 87
/vendor/fusonic/linq/src/Fusonic/Linq/GroupedLinq.php
@@ -0,0 +1,35 @@
<?php
 
/*
* This file is part of Fusonic-linq.
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
 
namespace Fusonic\Linq;
 
use Fusonic\Linq\Linq;
 
/**
* Class GroupedLinq
* Represents a Linq object that groups together other elements with a group key().
* @package Fusonic\Linq
*/
class GroupedLinq extends Linq
{
private $groupKey;
 
public function __construct($groupKey, $dataSource)
{
parent::__construct($dataSource);
$this->groupKey = $groupKey;
}
 
public function key()
{
return $this->groupKey;
}
}
/vendor/fusonic/linq/src/Fusonic/Linq/Helper/LinqHelper.php
@@ -0,0 +1,67 @@
<?php
 
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
 
namespace Fusonic\Linq\Helper;
 
use ArrayIterator;
use InvalidArgumentException;
use UnexpectedValueException;
 
class LinqHelper
{
const MODE_ASSERT = 'MODE_ASSERT';
const MODE_NULL = 'MODE_NULL';
 
const LINQ_ORDER_ASC = 'asc';
const LINQ_ORDER_DESC = 'desc';
 
const LINQ_ORDER_TYPE_NUMERIC = 1;
const LINQ_ORDER_TYPE_ALPHANUMERIC = 2;
const LINQ_ORDER_TYPE_DATETIME = 3;
 
public static function getBoolOrThrowException($returned)
{
if (!is_bool($returned)) {
throw new UnexpectedValueException("Return type of filter func must be boolean.");
}
return $returned;
}
 
public static function assertArgumentIsIterable($param, $argumentName)
{
if (!self::isIterable($param)) {
throw new InvalidArgumentException("Argument must be an array, or implement either the \IteratorAggregate or \Iterator interface. ArgumentName = " . $argumentName);
}
}
 
public static function getIteratorOrThrow($value)
{
if (is_array($value)) {
return new ArrayIterator($value);
}
else if($value instanceof \IteratorAggregate) {
return $value->getIterator();
}
else if($value instanceof \Iterator) {
return $value;
}
 
throw new \UnexpectedValueException("Value must be an array, or implement either the \IteratorAggregate or \Iterator interface");
}
 
public static function isIterable($param)
{
return is_array($param)
|| $param instanceof \IteratorAggregate
|| $param instanceof \Iterator;
}
}
/vendor/fusonic/linq/src/Fusonic/Linq/Iterator/DistinctIterator.php
@@ -0,0 +1,62 @@
<?php
 
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
 
namespace Fusonic\Linq\Iterator;
 
use Iterator;
 
class DistinctIterator extends \IteratorIterator
{
private $iterator;
private $distinct;
 
public function __construct(Iterator $iterator)
{
$this->iterator = $iterator;
}
 
public function current()
{
return $this->distinct->current();
}
 
public function next()
{
$this->distinct->next();
}
 
public function key()
{
return $this->distinct->key();
}
 
public function valid()
{
return $this->distinct->valid();
}
 
public function rewind()
{
if ($this->distinct === null) {
$this->getDistincts();
}
 
$this->distinct->rewind();
}
 
private function getDistincts()
{
$data = iterator_to_array($this->iterator);
$distinct = array_unique($data);
$this->distinct = new \ArrayIterator($distinct);
}
}
/vendor/fusonic/linq/src/Fusonic/Linq/Iterator/ExceptIterator.php
@@ -0,0 +1,65 @@
<?php
 
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
 
namespace Fusonic\Linq\Iterator;
 
use ArrayIterator;
use Iterator;
 
class ExceptIterator implements Iterator
{
private $first;
private $second;
private $result;
 
public function __construct(Iterator $first, Iterator $second)
{
$this->first = $first;
$this->second = $second;
}
 
public function current()
{
return $this->result->current();
}
 
public function next()
{
$this->result->next();
}
 
public function key()
{
return $this->result->key();
}
 
public function valid()
{
return $this->result->valid();
}
 
public function rewind()
{
if ($this->result === null) {
$this->getResult();
}
 
$this->result->rewind();
}
 
private function getResult()
{
$firstArray = iterator_to_array($this->first);
$secondArray = iterator_to_array($this->second);
$this->result = new ArrayIterator(array_diff($firstArray, $secondArray));
}
}
/vendor/fusonic/linq/src/Fusonic/Linq/Iterator/GroupIterator.php
@@ -0,0 +1,74 @@
<?php
 
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
 
namespace Fusonic\Linq\Iterator;
 
use Iterator;
use ArrayIterator;
use Fusonic\Linq\GroupedLinq;
 
class GroupIterator implements Iterator
{
private $iterator;
private $grouped;
private $keySelector;
 
public function __construct($iterator, $keySelector)
{
$this->iterator = $iterator;
$this->keySelector = $keySelector;
}
 
public function current()
{
$current = $this->grouped->current();
return new GroupedLinq($current['key'], new \ArrayIterator($current['values']));
}
 
public function next()
{
$this->grouped->next();
}
 
public function key()
{
return $this->grouped->key();
}
 
public function valid()
{
return $this->grouped->valid();
}
 
public function rewind()
{
if ($this->grouped === null) {
$this->doGroup();
}
 
$this->grouped->rewind();
}
 
private function doGroup()
{
$keySelector = $this->keySelector;
$this->grouped = new \ArrayIterator(array());
foreach ($this->iterator as $value) {
$key = $keySelector($value);
if (!isset($this->grouped[$key])) {
$this->grouped[$key] = array('key' => $key, 'values'=> array());
}
 
$this->grouped[$key]['values'][] = $value;
}
}
}
/vendor/fusonic/linq/src/Fusonic/Linq/Iterator/IntersectIterator.php
@@ -0,0 +1,65 @@
<?php
 
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
 
namespace Fusonic\Linq\Iterator;
 
use ArrayIterator;
use Iterator;
 
class IntersectIterator implements Iterator
{
private $first;
private $second;
private $intersections;
 
public function __construct(Iterator $first, Iterator $second)
{
$this->first = $first;
$this->second = $second;
}
 
public function current()
{
return $this->intersections->current();
}
 
public function next()
{
$this->intersections->next();
}
 
public function key()
{
return $this->intersections->key();
}
 
public function valid()
{
return $this->intersections->valid();
}
 
public function rewind()
{
if ($this->intersections === null) {
$this->calcIntersections();
}
 
$this->intersections->rewind();
}
 
private function calcIntersections()
{
$firstArray = iterator_to_array($this->first);
$secondArray = iterator_to_array($this->second);
$this->intersections = new ArrayIterator(array_intersect($firstArray, $secondArray));
}
}
/vendor/fusonic/linq/src/Fusonic/Linq/Iterator/OfTypeIterator.php
@@ -0,0 +1,94 @@
<?php
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Burgy Benjamin <benjamin.burgy@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
 
namespace Fusonic\Linq\Iterator;
 
use FilterIterator;
use Fusonic\Linq\Helper;
use Iterator;
 
/**
* Iterator for filtering the Linq query with a specified <b>type</b>.
* @package Fusonic\Linq\Iterator
*/
final class OfTypeIterator
extends
\FilterIterator
{
/**
* @var callable $acceptCallback
*/
private $acceptCallback;
 
/**
* Initializes an instance of <b>OfTypeIterator</b>.
*
* @param Iterator $iterator
* @param string $type
*/
public function __construct(Iterator $iterator, $type)
{
parent::__construct($iterator);
 
switch (strtolower($type))
{
case 'int':
case 'integer':
$this->acceptCallback = function ($current)
{
return is_int($current);
};
break;
case 'float':
case 'double':
$this->acceptCallback = function ($current)
{
return is_float($current);
};
break;
case 'string':
$this->acceptCallback = function ($current)
{
return is_string($current);
};
break;
case 'bool':
case 'boolean':
$this->acceptCallback = function ($current)
{
return is_bool($current);
};
break;
 
default:
$this->acceptCallback = function ($current) use ($type)
{
return $current instanceof $type;
};
}
}
 
/**
* (PHP 5 &gt;= 5.1.0)<br/>
* Check whether the current element of the iterator is acceptable
* @link http://php.net/manual/en/filteriterator.accept.php
* @return bool true if the current element is acceptable, otherwise false.
*/
public function accept()
{
/** @var mixed $current */
$current = $this->current();
/** @var callable $func */
$func = $this->acceptCallback;
 
return $func($current);
}
}
/vendor/fusonic/linq/src/Fusonic/Linq/Iterator/OrderIterator.php
@@ -0,0 +1,113 @@
<?php
 
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
 
namespace Fusonic\Linq\Iterator;
 
use Iterator;
use ArrayIterator;
use Fusonic\Linq;
use Fusonic\Linq\Helper;
 
class OrderIterator implements Iterator
{
private $iterator;
private $direction;
private $orderedIterator;
private $orderKeyFunc;
 
public function __construct(Iterator $items, $orderKeyFunc, $direction)
{
$this->iterator = $items;
$this->direction = $direction;
$this->orderKeyFunc = $orderKeyFunc;
}
 
public function current()
{
return $this->orderedIterator->current();
}
 
public function next()
{
$this->orderedIterator->next();
}
 
public function key()
{
return $this->orderedIterator->key();
}
 
public function valid()
{
return $this->orderedIterator->valid();
}
 
public function rewind()
{
if ($this->orderedIterator == null) {
$this->orderItems();
}
$this->orderedIterator->rewind();
}
 
public function orderItems()
{
$orderKeyFunc = $this->orderKeyFunc;
$direction = $this->direction;
 
$itemIterator = $this->iterator;
$itemIterator->rewind();
if (!$itemIterator->valid()) {
$this->orderedIterator = new ArrayIterator();
return;
}
 
$firstOrderKey = $orderKeyFunc($itemIterator->current());
 
$sortType = Helper\LinqHelper::LINQ_ORDER_TYPE_NUMERIC;
 
if ($firstOrderKey instanceof \DateTime) {
$sortType = Helper\LinqHelper::LINQ_ORDER_TYPE_DATETIME;
} elseif (!is_numeric($firstOrderKey)) {
$sortType = Helper\LinqHelper::LINQ_ORDER_TYPE_ALPHANUMERIC;
}
 
$keyMap = array();
$valueMap = array();
 
foreach ($itemIterator as $value) {
$orderKey = $orderKeyFunc != null ? $orderKeyFunc($value) : $value;
if ($sortType == Helper\LinqHelper::LINQ_ORDER_TYPE_DATETIME) {
$orderKey = $orderKey->getTimeStamp();
}
$keyMap[] = $orderKey;
$valueMap[] = $value;
}
 
if ($sortType == Helper\LinqHelper::LINQ_ORDER_TYPE_DATETIME) {
$sortType = Helper\LinqHelper::LINQ_ORDER_TYPE_NUMERIC;
}
 
if ($direction == Helper\LinqHelper::LINQ_ORDER_ASC) {
asort($keyMap, $sortType == Helper\LinqHelper::LINQ_ORDER_TYPE_NUMERIC ? SORT_NUMERIC : SORT_LOCALE_STRING);
} else {
arsort($keyMap, $sortType == Helper\LinqHelper::LINQ_ORDER_TYPE_NUMERIC ? SORT_NUMERIC : SORT_LOCALE_STRING);
}
 
$sorted = new ArrayIterator(array());
foreach ($keyMap as $key => $value) {
$sorted[] = $valueMap[$key];
}
 
$this->orderedIterator = $sorted;
}
}
/vendor/fusonic/linq/src/Fusonic/Linq/Iterator/SelectIterator.php
@@ -0,0 +1,37 @@
<?php
 
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
 
namespace Fusonic\Linq\Iterator;
 
use InvalidArgumentException;
use Iterator;
 
class SelectIterator extends \IteratorIterator
{
private $selector;
 
public function __construct(Iterator $iterator, $selector)
{
parent::__construct($iterator);
if ($selector === null) {
throw new InvalidArgumentException("Selector must not be null.");
}
 
$this->selector = $selector;
}
 
public function current()
{
$selector = $this->selector;
return $selector(parent::current());
}
}
/vendor/fusonic/linq/src/Fusonic/Linq/Iterator/SelectManyIterator.php
@@ -0,0 +1,84 @@
<?php
 
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
 
namespace Fusonic\Linq\Iterator;
 
use Iterator;
use Fusonic\Linq\Helper;
 
class SelectManyIterator implements Iterator
{
private $iterator;
private $currentIterator;
private $key = 0;
 
public function __construct(Iterator $iterator)
{
$this->iterator = $iterator;
}
 
public function current()
{
if ($this->currentIterator != null) {
return $this->currentIterator->current();
}
 
return null;
}
 
public function next()
{
if ($this->currentIterator != null) {
$this->currentIterator->next();
 
if (!$this->currentIterator->valid()) {
$this->iterator->next();
if ($this->iterator->valid()) {
$this->currentIterator = Helper\LinqHelper::getIteratorOrThrow($this->iterator->current());
if ($this->currentIterator != null) {
$this->currentIterator->rewind();
$this->key++;
}
}
} else {
$this->key++;
}
}
}
 
public function key()
{
return $this->key;
}
 
public function valid()
{
$current = $this->currentIterator;
return $current != null && $current->valid();
}
 
public function rewind()
{
$this->iterator->rewind();
if ($this->iterator->valid()) {
$current = $this->iterator->current();
$this->currentIterator = Helper\LinqHelper::getIteratorOrThrow($current);
if ($this->currentIterator != null) {
$this->currentIterator->rewind();
}
} else {
$this->currentIterator = null;
}
 
$this->key = 0;
}
}
/vendor/fusonic/linq/src/Fusonic/Linq/Iterator/WhereIterator.php
@@ -0,0 +1,34 @@
<?php
 
/*
* This file is part of Fusonic-linq.
* https://github.com/fusonic/fusonic-linq
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
 
namespace Fusonic\Linq\Iterator;
 
use Iterator;
use Fusonic\Linq\Helper;
 
class WhereIterator extends \FilterIterator
{
private $func;
 
public function __construct(Iterator $iterator, $func)
{
parent::__construct($iterator);
$this->func = $func;
}
 
public function accept()
{
$func = $this->func;
$current = $this->current();
return Helper\LinqHelper::getBoolOrThrowException($func($current));
}
}
/vendor/fusonic/linq/src/Fusonic/Linq/Linq.php
@@ -0,0 +1,732 @@
<?php
 
/*
* This file is part of Fusonic-linq.
*
* (c) Fusonic GmbH
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
 
namespace Fusonic\Linq;
 
use Countable;
use Fusonic\Linq\Iterator\ExceptIterator;
use Fusonic\Linq\Iterator\DistinctIterator;
use Fusonic\Linq\Iterator\GroupIterator;
use Fusonic\Linq\Iterator\IntersectIterator;
use Fusonic\Linq\Iterator\OfTypeIterator;
use Fusonic\Linq\Iterator\OrderIterator;
use Fusonic\Linq\Iterator\SelectIterator;
use Fusonic\Linq\Iterator\SelectManyIterator;
use Fusonic\Linq\Iterator\WhereIterator;
use Fusonic\Linq\Helper\LinqHelper;
use IteratorAggregate;
use Traversable;
use UnexpectedValueException;
use InvalidArgumentException;
use OutOfRangeException;
 
/**
* Linq is a simple, powerful and consistent library for querying, projecting and aggregating data in php.
*
* @author David Roth <david.roth@fusonic.net>.
*/
class Linq implements IteratorAggregate, Countable
{
private $iterator;
 
/**
* Creates a new Linq object using the provided dataSource.
*
* @param array|\Iterator|IteratorAggregate $dataSource A Traversable sequence as data source.
*/
public function __construct($dataSource)
{
LinqHelper::assertArgumentIsIterable($dataSource, "dataSource");
$dataSource = LinqHelper::getIteratorOrThrow($dataSource);
 
$this->iterator = $dataSource;
}
 
/**
* Creates a new Linq object using the provided dataDataSource.
* This is the recommended way for getting a new Linq instance.
*
* @param array|\Iterator|IteratorAggregate $dataSource A Traversable sequence as data source.
* @return Linq
*/
public static function from($dataSource)
{
return new Linq($dataSource);
}
 
/**
* Generates a sequence of integral numbers within a specified range.
*
* @param $start The value of the first integer in the sequence.
* @param $count The number of sequential integers to generate.
* @return Linq An sequence that contains a range of sequential int numbers.
* @throws \OutOfRangeException
*/
public static function range($start, $count)
{
if ($count < 0) {
throw new OutOfRangeException('$count must be not be negative.');
}
 
return new Linq(range($start, $start + $count - 1));
}
 
/**
* Filters the Linq object according to func return result.
*
* @param callback $func A func that returns boolean
* @return Linq Filtered results according to $func
*/
public function where($func)
{
return new Linq(new WhereIterator($this->iterator, $func));
}
 
/**
* Filters the Linq object according to type.
*
* @param string $type
*
* @return Linq Filtered results according to $func
*/
public function ofType($type)
{
return new Linq(new OfTypeIterator($this->iterator, $type));
}
 
/**
* Bypasses a specified number of elements and then returns the remaining elements.
*
* @param int $count The number of elements to skip before returning the remaining elements.
* @return Linq A Linq object that contains the elements that occur after the specified index.
*/
public function skip($count)
{
// If its an array iterator we must check the arrays bounds are greater than the skip count.
// This is because the LimitIterator will use the seek() method which will throw an exception if $count > array.bounds.
$innerIterator = $this->iterator;
if ($innerIterator instanceof \ArrayIterator) {
if ($count >= $innerIterator->count()) {
return new Linq(array());
}
}
 
return new Linq(new \LimitIterator($innerIterator, $count, -1));
}
 
/**
* Returns a specified number of contiguous elements from the start of a sequence
*
* @param int $count The number of elements to return.
* @return Linq A Linq object that contains the specified number of elements from the start.
*/
public function take($count)
{
if ($count == 0) {
return new Linq(array());
}
 
return new Linq(new \LimitIterator($this->iterator, 0, $count));
}
 
/**
* Applies an accumulator function over a sequence.
* The aggregate method makes it simple to perform a calculation over a sequence of values.
* This method works by calling $func one time for each element.
* The first element of source is used as the initial aggregate value if $seed parameter is not specified.
* If $seed is specified, this value will be used as the first value.
*
* @param callback $func An accumulator function to be invoked on each element.
* @param mixed $seed
* @throws \RuntimeException if the input sequence contains no elements.
* @return mixed Returns the final result of $func.
*/
public function aggregate($func, $seed = null)
{
$result = null;
$first = true;
 
if ($seed !== null) {
$result = $seed;
$first = false;
}
 
foreach ($this->iterator as $current) {
if (!$first) {
$result = $func($result, $current);
} else {
$result = $current;
$first = false;
}
}
if ($first) {
throw new \RuntimeException("The input sequence contains no elements.");
}
return $result;
}
 
/**
* Splits the sequence in chunks according to $chunksize.
*
* @param $chunksize Specifies how many elements are grouped together per chunk.
* @throws \InvalidArgumentException
* @return Linq
*/
public function chunk($chunksize)
{
if ($chunksize < 1) {
throw new \InvalidArgumentException("chunksize", $chunksize);
}
 
$i = -1;
return $this->select(
function ($x) use (&$i) {
$i++;
return array("index" => $i, "value" => $x);
}
)
->groupBy(
function ($pair) use ($chunksize) {
return $pair["index"] / $chunksize;
}
)
->select(
function (GroupedLinq $group) {
return $group->select(
function ($v) {
return $v["value"];
}
);
}
);
}
 
/**
* Determines whether all elements satisfy a condition.
*
* @param callback $func A function to test each element for a condition.
* @return bool True if every element passes the test in the specified func, or if the sequence is empty; otherwise, false.
*/
public function all($func)
{
foreach ($this->iterator as $current) {
$match = LinqHelper::getBoolOrThrowException($func($current));
if (!$match) {
return false;
}
}
return true;
}
 
/**
* Determines whether any element exists or satisfies a condition by invoking $func.
*
* @param callback $func A function to test each element for a condition or NULL to determine if any element exists.
* @return bool True if no $func given and the source sequence contains any elements or True if any elements passed the test in the specified func; otherwise, false.
*/
public function any($func = null)
{
foreach ($this->iterator as $current) {
if ($func === null) {
return true;
}
 
$match = LinqHelper::getBoolOrThrowException($func($current));
if ($match) {
return true;
}
}
return false;
}
 
/**
* Counts the elements of this Linq sequence.
* @return int
*/
public function count()
{
if ($this->iterator instanceof Countable) {
return $this->iterator->count();
}
 
return iterator_count($this->iterator);
}
 
/**
* Computes the average of all numeric values. Uses $func to obtain the value on each element.
*
* @param callback $func A func that returns any numeric type (int, float etc.)
* @throws \UnexpectedValueException if an item of the sequence is not a numeric value.
* @return double Average of items
*/
public function average($func = null)
{
$resultTotal = 0;
$itemCount = 0;
 
$source = $this->getSelectIteratorOrInnerIterator($func);
 
foreach ($source as $item) {
if (!is_numeric($item)) {
throw new UnexpectedValueException("Cannot calculate an average on a none numeric value");
}
 
$resultTotal += $item;
$itemCount++;
}
return $itemCount == 0 ? 0 : ($resultTotal / $itemCount);
}
 
/**
* Sorts the elements in ascending order according to a key provided by $func.
*
* @param callback $func A function to extract a key from an element.
* @return Linq A new Linq instance whose elements are sorted ascending according to a key.
*/
public function orderBy($func)
{
return $this->order($func, LinqHelper::LINQ_ORDER_ASC);
}
 
/**
* Sorts the elements in descending order according to a key provided by $func.
*
* @param callback $func A function to extract a key from an element.
* @return Linq A new Linq instance whose elements are sorted descending according to a key.
*/
public function orderByDescending($func)
{
return $this->order($func, LinqHelper::LINQ_ORDER_DESC);
}
 
private function order($func, $direction = LinqHelper::LINQ_ORDER_ASC)
{
return new Linq(new OrderIterator($this->iterator, $func, $direction));
}
 
/**
* Gets the sum of all items or by invoking a transform function on each item to get a numeric value.
*
* @param callback $func A func that returns any numeric type (int, float etc.) from the given element, or NULL to use the element itself.
* @throws \UnexpectedValueException if any element is not a numeric value.
* @return double The sum of all items.
*/
public function sum($func = null)
{
$sum = 0;
$iterator = $this->getSelectIteratorOrInnerIterator($func);
foreach ($iterator as $value) {
if (!is_numeric($value)) {
throw new UnexpectedValueException("sum() only works on numeric values.");
}
 
$sum += $value;
}
return $sum;
}
 
/**
* Gets the minimum item value of all items or by invoking a transform function on each item to get a numeric value.
*
* @param callback $func A func that returns any numeric type (int, float etc.) from the given element, or NULL to use the element itself.
* @throws \RuntimeException if the sequence contains no elements
* @throws \UnexpectedValueException
* @return double Minimum item value
*/
public function min($func = null)
{
$min = null;
$iterator = $this->getSelectIteratorOrInnerIterator($func);
foreach ($iterator as $value) {
if (!is_numeric($value) && !is_string($value) && !($value instanceof \DateTime)) {
throw new UnexpectedValueException("min() only works on numeric values, strings and DateTime objects.");
}
 
if (is_null($min)) {
$min = $value;
} elseif ($min > $value) {
$min = $value;
}
}
 
if ($min === null) {
throw new \RuntimeException("Cannot calculate min() as the Linq sequence contains no elements.");
}
 
return $min;
}
 
/**
* Returns the maximum item value according to $func
*
* @param callback $func A func that returns any numeric type (int, float etc.)
* @throws \RuntimeException if the sequence contains no elements
* @throws \UnexpectedValueException if any element is not a numeric value or a string.
* @return double Maximum item value
*/
public function max($func = null)
{
$max = null;
$iterator = $this->getSelectIteratorOrInnerIterator($func);
foreach ($iterator as $value) {
if (!is_numeric($value) && !is_string($value) && !($value instanceof \DateTime)) {
throw new UnexpectedValueException("max() only works on numeric values, strings and DateTime objects.");
}
 
if (is_null($max)) {
$max = $value;
} elseif ($max < $value) {
$max = $value;
}
}
 
if ($max === null) {
throw new \RuntimeException("Cannot calculate max() as the Linq sequence contains no elements.");
}
 
return $max;
}
 
/**
* Projects each element into a new form by invoking the selector function.
*
* @param callback $func A transform function to apply to each element.
* @return Linq A new Linq object whose elements are the result of invoking the transform function on each element of the original Linq object.
*/
public function select($func)
{
return new Linq(new SelectIterator($this->iterator, $func));
}
 
/**
* Projects each element of a sequence to a new Linq and flattens the resulting sequences into one sequence.
*
* @param callback $func A func that returns a sequence (array, Linq, Iterator).
* @throws \UnexpectedValueException if an element is not a traversable sequence.
* @return Linq A new Linq object whose elements are the result of invoking the one-to-many transform function on each element of the input sequence.
*/
public function selectMany($func)
{
return new Linq(new SelectManyIterator(new SelectIterator($this->iterator, $func)));
}
 
/**
* Performs the specified action on each element of the Linq sequence and returns the Linq sequence.
* @param callback $func A func that will be evaluated for each item in the linq sequence.
* @return Linq The original Linq sequence that was used to perform the foreach.
*/
public function each($func)
{
foreach ($this->iterator as $item) {
$func($item);
}
return $this;
}
 
/**
* Determines whether a sequence contains a specified element.
* This function will use php strict comparison (===). If you need custom comparison use the Linq::any($func) method.
*
* @param mixed $value The value to locate in the sequence.
* @return bool True if $value is found within the sequence; otherwise false.
*/
public function contains($value)
{
return $this->any(
function ($x) use ($value) {
return $x === $value;
}
);
}
 
/**
* Concatenates this Linq object with the given sequence.
*
* @param array|\Iterator $second A sequence which will be concatenated with this Linq object.
* @throws InvalidArgumentException if the given sequence is not traversable.
* @return Linq A new Linq object that contains the concatenated elements of the input sequence and the original Linq sequence.
*/
public function concat($second)
{
LinqHelper::assertArgumentIsIterable($second, "second");
 
$allItems = new \ArrayIterator(array($this->iterator, $second));
 
return new Linq(new SelectManyIterator($allItems));
}
 
/**
* Returns distinct item values of this
*
* @param callback $func
* @return Linq Distinct item values of this
*/
public function distinct($func = null)
{
return new Linq(new DistinctIterator($this->getSelectIteratorOrInnerIterator($func)));
}
 
/**
* Intersects the Linq sequence with second Iterable sequence.
*
* @param \Iterator|array An iterator to intersect with:
* @return Linq intersected items
*/
public function intersect($second)
{
LinqHelper::assertArgumentIsIterable($second, "second");
return new Linq(new IntersectIterator($this->iterator, LinqHelper::getIteratorOrThrow($second)));
}
 
/**
* Returns all elements except the ones of the given sequence.
*
* @param array|\Iterator $second
* @return Linq Returns all items of this not occuring in $second
*/
public function except($second)
{
LinqHelper::assertArgumentIsIterable($second, "second");
return new Linq(new ExceptIterator($this->iterator, LinqHelper::getIteratorOrThrow($second)));
}
 
/**
* Returns the element at a specified index.
* This method throws an exception if index is out of range.
* To instead return NULL when the specified index is out of range, use the elementAtOrNull method.
*
* @throws \OutOfRangeException if index is less than 0 or greater than or equal to the number of elements in the sequence.
* @param int $index
* @return mixed Item at $index
*/
public function elementAt($index)
{
return $this->getValueAt($index, true);
}
 
/**
* Returns the element at a specified index or NULL if the index is out of range.
*
* @param $index
* @return mixed Item at $index
*/
public function elementAtOrNull($index)
{
return $this->getValueAt($index, false);
}
 
private function getValueAt($index, $throwEx)
{
$i = 0;
foreach ($this->iterator as $value) {
if ($i == $index) {
return $value;
}
$i++;
}
 
if ($throwEx) {
throw new OutOfRangeException("Index is less than 0 or greater than or equal to the number of elements in the sequence.");
}
 
return null;
}
 
/**
* Groups the object according to the $func generated key
*
* @param callback $keySelector a func that returns an item as key, item can be any type.
* @return GroupedLinq
*/
public function groupBy($keySelector)
{
return new Linq(new GroupIterator($this->iterator, $keySelector));
}
 
/**
* Returns the last element that satisfies a specified condition.
* @throws \RuntimeException if no element satisfies the condition in predicate or the source sequence is empty.
*
* @param callback $func a func that returns boolean.
* @return Object Last item in this
*/
public function last($func = null)
{
return $this->getLast($func, true);
}
 
/**
* Returns the last element that satisfies a condition or NULL if no such element is found.
*
* @param callback $func a func that returns boolean.
* @return mixed
*/
public function lastOrNull($func = null)
{
return $this->getLast($func, false);
}
 
/**
* Returns the first element that satisfies a specified condition
* @throws \RuntimeException if no element satisfies the condition in predicate -or- the source sequence is empty / does not match any elements.
*
* @param callback $func a func that returns boolean.
* @return mixed
*/
public function first($func = null)
{
return $this->getFirst($func, true);
}
 
/**
* Returns the first element, or NULL if the sequence contains no elements.
*
* @param callback $func a func that returns boolean.
* @return mixed
*/
public function firstOrNull($func = null)
{
return $this->getFirst($func, false);
}
 
/**
* Returns the only element that satisfies a specified condition.
*
* @throws \RuntimeException if no element exists or if more than one element exists.
* @param callback $func a func that returns boolean.
* @return mixed
*/
public function single($func = null)
{
return $this->getSingle($func, true);
}
 
/**
* Returns the only element that satisfies a specified condition or NULL if no such element exists.
*
* @throws \RuntimeException if more than one element satisfies the condition.
* @param callback $func a func that returns boolean.
* @return mixed
*/
public function singleOrNull($func = null)
{
return $this->getSingle($func, false);
}
 
 
private function getWhereIteratorOrInnerIterator($func)
{
return $func === null ? $this->iterator : new WhereIterator($this->iterator, $func);
}
 
private function getSelectIteratorOrInnerIterator($func)
{
return $func === null ? $this->iterator : new SelectIterator($this->iterator, $func);
}
 
private function getSingle($func, $throw)
{
$source = $this->getWhereIteratorOrInnerIterator($func);
 
$count = 0;
$single = null;
 
foreach ($source as $stored) {
$count++;
 
if ($count > 1) {
throw new \RuntimeException("The input sequence contains more than 1 elements.");
}
 
$single = $stored;
}
 
if ($count == 0 && $throw) {
throw new \RuntimeException("The input sequence contains no matching element.");
}
 
return $single;
}
 
private function getFirst($func, $throw)
{
$source = $this->getWhereIteratorOrInnerIterator($func);
 
$count = 0;
$first = null;
 
foreach ($source as $stored) {
$count++;
$first = $stored;
break;
}
 
if ($count == 0 && $throw) {
throw new \RuntimeException("The input sequence contains no matching element.");
}
 
return $first;
}
 
private function getLast($func, $throw)
{
$source = $this->getWhereIteratorOrInnerIterator($func);
 
$count = 0;
$last = null;
 
foreach ($source as $stored) {
$count++;
$last = $stored;
}
 
if ($count == 0 && $throw) {
throw new \RuntimeException("The input sequence contains no matching element.");
}
 
return $last;
}
 
/**
* Creates an Array from this Linq object with key/value selector(s).
*
* @param callback $keySelector a func that returns the array-key for each element.
* @param callback $valueSelector a func that returns the array-value for each element.
*
* @return Array An array with all values.
*/
public function toArray($keySelector = null, $valueSelector = null)
{
if ($keySelector === null && $valueSelector === null) {
return iterator_to_array($this, false);
} elseif ($keySelector == null) {
return iterator_to_array(new SelectIterator($this->getIterator(), $valueSelector), false);
} else {
$array = array();
foreach ($this as $value) {
$key = $keySelector($value);
$array[$key] = $valueSelector == null ? $value : $valueSelector($value);
}
return $array;
}
}
 
/**
* Retrieves the iterator of this Linq class.
* @link http://php.net/manual/en/iteratoraggregate.getiterator.php
* @return Traversable An instance of an object implementing <b>Iterator</b> or
* <b>Traversable</b>
*/
public function getIterator()
{
return $this->iterator;
}
}