PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/trunk/Classes/PHPLinq/LinqToObjects.php

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