PageRenderTime 55ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

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

#
PHP | 868 lines | 353 code | 114 blank | 401 comment | 54 complexity | a2b0bb7a48bc0f02dcd34c15835ae65b MD5 | raw file
Possible License(s): LGPL-2.0
  1. <?php
  2. /**
  3. * PHPLinq
  4. *
  5. * Copyright (c) 2008 - 2009 PHPLinq
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 2.1 of the License, or (at your option) any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this library; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  20. *
  21. * @category PHPLinq
  22. * @package PHPLinq
  23. * @copyright Copyright (c) 2008 - 2009 PHPLinq (http://www.codeplex.com/PHPLinq)
  24. * @license http://www.gnu.org/licenses/lgpl.txt LGPL
  25. * @version ##VERSION##, ##DATE##
  26. */
  27. /** 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 - 2009 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);
  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. $this->_data = $source;
  223. return $this;
  224. }
  225. /**
  226. * Select
  227. *
  228. * @param string $expression Expression which creates a resulting element
  229. * @return mixed
  230. */
  231. public function select($expression = null) {
  232. // Returnvalue
  233. $returnValue = array();
  234. // Expression set?
  235. if (is_null($expression) || $expression == '') {
  236. $expression = $this->_from . ' => ' . $this->_from;
  237. }
  238. // OrderBy set?
  239. if (is_array($this->_orderBy) && count($this->_orderBy) > 0) {
  240. // Create sorter
  241. $sorter = '';
  242. // Is there only one OrderBy expression?
  243. if (count($this->_orderBy) == 1) {
  244. // First sorter
  245. $sorter = $this->_orderBy[0]->getFunctionReference();
  246. } else {
  247. // Create OrderBy expression
  248. $compareCode = '';
  249. // Compile comparer function
  250. $compareCode = "
  251. \$result = null;
  252. ";
  253. for ($i = 0; $i < count($this->_orderBy); $i++) {
  254. $f = substr($this->_orderBy[$i]->getFunctionReference(), 1);
  255. $compareCode .= "
  256. \$result = call_user_func_array(chr(0).'$f', array({$this->_from}A, {$this->_from}B));
  257. if (\$result != 0) {
  258. return \$result;
  259. }
  260. ";
  261. }
  262. $compareCode .= "return \$result;";
  263. $sorter = create_function($this->_from . 'A, ' . $this->_from . 'B', $compareCode);
  264. }
  265. // Sort!
  266. usort($this->_data, $sorter);
  267. }
  268. // Data source
  269. $dataSource = array();
  270. // Build possible join data
  271. foreach ($this->_data as $value) {
  272. // Child providers set?
  273. if (count($this->_childProviders) > 0) {
  274. // Data to be joined
  275. $joinedData = array();
  276. $joinedData[$this->_from][] = $value;
  277. // Join other values
  278. foreach ($this->_childProviders as $provider) {
  279. // Fetch possible join data
  280. foreach ($provider->select() as $record) {
  281. $joinValid = $provider->getJoinCondition()->execute( array( $this->_from => $value, $provider->getFromName() => $record ) );
  282. if ($joinValid) {
  283. $joinedData[$provider->getFromName()][] = $record;
  284. }
  285. }
  286. }
  287. /** BEGIN CARTESIAN JOIN */
  288. // Joinable array
  289. $joinable = array();
  290. // Join data keys
  291. $joinedDataKeys = array_keys($joinedData);
  292. // Calculate expected size of $joinable
  293. $size = (count($joinedData) > 0 ? 1 : 0);
  294. foreach ($joinedData as $values) {
  295. $size = $size * count($values);
  296. }
  297. // Create cartesian array
  298. for ($i = 0; $i < $size; $i++) {
  299. $joinable[$i] = array();
  300. for ($j = 0; $j < count($joinedData); $j++) {
  301. array_push($joinable[$i], current($joinedData[$joinedDataKeys[$j]]));
  302. }
  303. // Set cursor on next element in the $joinedData, beginning with the last array
  304. for ($j = (count($joinedData)-1); $j >= 0; $j--) {
  305. // If next returns true, then break
  306. if (next($joinedData[$joinedDataKeys[$j]])) {
  307. break;
  308. } else {
  309. // If next returns false, then reset and go on with previous $joinedData...
  310. reset($joinedData[$joinedDataKeys[$j]]);
  311. }
  312. }
  313. }
  314. /** END CARTESIAN JOIN */
  315. // Join values using selector expression
  316. foreach ($joinable as $values) {
  317. $dataSource[] = $values;
  318. }
  319. }
  320. }
  321. // Data source filled?
  322. if (count($dataSource) == 0) {
  323. $dataSource = $this->_data;
  324. }
  325. // Distinct values storage
  326. $distinctValues = array();
  327. // Create selector expression
  328. $selector = new PHPLinq_Expression($expression, $this->_from);
  329. // Count elements
  330. $elementCount = 0;
  331. // Loop trough data source
  332. foreach ($dataSource as $value) {
  333. // Is it a valid element?
  334. $isValid = true;
  335. // OfType expresion set? Evaluate it!
  336. if ($isValid && !is_null($this->_ofType)) {
  337. $isValid = $this->_ofType->execute($value);
  338. }
  339. // Where expresion set? Evaluate it!
  340. if ($isValid && !is_null($this->_where)) {
  341. $isValid = $this->_where->execute($value);
  342. }
  343. // Distinct expression set? Evaluate it!
  344. if ($isValid && !is_null($this->_distinct)) {
  345. $distinctKey = $this->_distinct->execute($value);
  346. if (isset($distinctValues[$distinctKey])) {
  347. $isValid = false;
  348. } else {
  349. $distinctValues[$distinctKey] = 1;
  350. }
  351. }
  352. // The element is valid, check if it is our selection range
  353. if ($isValid) {
  354. // Skip element?
  355. if (!is_null($this->_skipWhile) && $this->_skipWhile->execute($value)) {
  356. $isValid = false;
  357. }
  358. if (!is_null($this->_skip) && $elementCount < $this->_skip) {
  359. $isValid = false;
  360. }
  361. // Take element?
  362. if (!is_null($this->_takeWhile) && !$this->_takeWhile->execute($value)) {
  363. $isValid = false;
  364. break;
  365. }
  366. if (!is_null($this->_take) && count($returnValue) >= $this->_take) {
  367. $isValid = false;
  368. break;
  369. }
  370. // Next element
  371. $elementCount++;
  372. // Add the element to the return value if it is a valid element
  373. if ($isValid) {
  374. $returnValue[] = $selector->execute($value);
  375. }
  376. }
  377. }
  378. // Return value
  379. return $returnValue;
  380. }
  381. /**
  382. * Where
  383. *
  384. * @param string $expression Expression checking if an element should be contained
  385. * @return PHPLinq_ILinqProvider
  386. */
  387. public function where($expression) {
  388. $this->_where = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
  389. return $this;
  390. }
  391. /**
  392. * Take $n elements
  393. *
  394. * @param int $n
  395. * @return PHPLinq_ILinqProvider
  396. */
  397. public function take($n) {
  398. $this->_take = $n;
  399. return $this;
  400. }
  401. /**
  402. * Skip $n elements
  403. *
  404. * @param int $n
  405. * @return PHPLinq_ILinqProvider
  406. */
  407. public function skip($n) {
  408. $this->_skip = $n;
  409. return $this;
  410. }
  411. /**
  412. * Take elements while $expression evaluates to true
  413. *
  414. * @param string $expression Expression to evaluate
  415. * @return PHPLinq_ILinqProvider
  416. */
  417. public function takeWhile($expression) {
  418. $this->_takeWhile = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
  419. return $this;
  420. }
  421. /**
  422. * Skip elements while $expression evaluates to true
  423. *
  424. * @param string $expression Expression to evaluate
  425. * @return PHPLinq_ILinqProvider
  426. */
  427. public function skipWhile($expression) {
  428. $this->_skipWhile = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
  429. return $this;
  430. }
  431. /**
  432. * OrderBy
  433. *
  434. * @param string $expression Expression to order elements by
  435. * @param string $comparer Comparer function (taking 2 arguments, returning -1, 0, 1)
  436. * @return PHPLinq_ILinqProvider
  437. */
  438. public function orderBy($expression, $comparer = null) {
  439. $this->_orderBy[0] = new PHPLinq_OrderByExpression($expression, $this->_from, false, $comparer);
  440. return $this;
  441. }
  442. /**
  443. * OrderByDescending
  444. *
  445. * @param string $expression Expression to order elements by
  446. * @param string $comparer Comparer function (taking 2 arguments, returning -1, 0, 1)
  447. * @return PHPLinq_ILinqProvider
  448. */
  449. public function orderByDescending($expression, $comparer = null) {
  450. $this->_orderBy[0] = new PHPLinq_OrderByExpression($expression, $this->_from, true, $comparer);
  451. return $this;
  452. }
  453. /**
  454. * ThenBy
  455. *
  456. * @param string $expression Expression to order elements by
  457. * @param string $comparer Comparer function (taking 2 arguments, returning -1, 0, 1)
  458. * @return PHPLinq_ILinqProvider
  459. */
  460. public function thenBy($expression, $comparer = null) {
  461. $this->_orderBy[] = new PHPLinq_OrderByExpression($expression, $this->_from, false, $comparer);
  462. return $this;
  463. }
  464. /**
  465. * ThenByDescending
  466. *
  467. * @param string $expression Expression to order elements by
  468. * @param string $comparer Comparer function (taking 2 arguments, returning -1, 0, 1)
  469. * @return PHPLinq_ILinqProvider
  470. */
  471. public function thenByDescending($expression, $comparer = null) {
  472. $this->_orderBy[] = new PHPLinq_OrderByExpression($expression, $this->_from, true, $comparer);
  473. return $this;
  474. }
  475. /**
  476. * Distinct
  477. *
  478. * @param string $expression Expression to retrieve the key value.
  479. * @return PHPLinq_ILinqProvider
  480. */
  481. public function distinct($expression) {
  482. $this->_distinct = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
  483. return $this;
  484. }
  485. /**
  486. * Select the elements of a certain type
  487. *
  488. * @param string $type Type name
  489. */
  490. public function ofType($type) {
  491. // Create a new expression
  492. $expression = $this->_from . ' => ';
  493. // Evaluate type
  494. switch (strtolower($type)) {
  495. case 'array':
  496. case 'bool':
  497. case 'double':
  498. case 'float':
  499. case 'int':
  500. case 'integer':
  501. case 'long':
  502. case 'null':
  503. case 'numeric':
  504. case 'object':
  505. case 'real':
  506. case 'scalar':
  507. case 'string':
  508. $expression .= 'is_' . strtolower($type) . '(' . $this->_from . ')';
  509. break;
  510. default:
  511. $expression .= 'is_a(' . $this->_from . ', "' . $type . '")';
  512. break;
  513. }
  514. // Assign expression
  515. $this->_ofType = new PHPLinq_Expression($expression, $this->_from);
  516. return $this;
  517. }
  518. /**
  519. * Any
  520. *
  521. * @param string $expression Expression checking if an element is contained
  522. * @return boolean
  523. */
  524. public function any($expression) {
  525. $originalWhere = $this->_where;
  526. $result = $this->where($expression)->select($this->_from);
  527. $this->_where = $originalWhere;
  528. return count($result) > 0;
  529. }
  530. /**
  531. * All
  532. *
  533. * @param string $expression Expression checking if an all elements are contained
  534. * @return boolean
  535. */
  536. public function all($expression) {
  537. $originalWhere = $this->_where;
  538. $result = $this->where($expression)->select($this->_from);
  539. $this->_where = $originalWhere;
  540. return count($result) == count($this->_data);
  541. }
  542. /**
  543. * Contains
  544. *
  545. * @param mixed $element Is the $element contained?
  546. * @return boolean
  547. */
  548. public function contains($element) {
  549. return in_array($element, $this->_data);
  550. }
  551. /**
  552. * Reverse elements
  553. *
  554. * @param bool $preserveKeys Preserve keys?
  555. * @return PHPLinq_ILinqProvider
  556. */
  557. public function reverse($preserveKeys = null) {
  558. $data = array_reverse($this->select(), $preserveKeys);
  559. return linqfrom($this->_from)->in($data);
  560. }
  561. /**
  562. * Element at index
  563. *
  564. * @param mixed $index Index
  565. * @return mixed Element at $index
  566. */
  567. public function elementAt($index = null) {
  568. $originalWhere = $this->_where;
  569. $result = isset($this->_data[$index]) ? $this->_data[$index] : null;
  570. $this->_where = $originalWhere;
  571. if (count($result) > 0) {
  572. return array_shift($result);
  573. }
  574. return null;
  575. }
  576. /**
  577. * Element at index or default
  578. *
  579. * @param mixed $index Index
  580. * @param mixed $defaultValue Default value to return if nothing is found
  581. * @return mixed Element at $index
  582. */
  583. public function elementAtOrDefault($index = null, $defaultValue = null) {
  584. $returnValue = $this->elementAt($index);
  585. if (!is_null($returnValue)) {
  586. return $returnValue;
  587. } else {
  588. return $defaultValue;
  589. }
  590. }
  591. /**
  592. * Concatenate data
  593. *
  594. * @param mixed $source
  595. * @return PHPLinq_ILinqProvider
  596. */
  597. public function concat($source) {
  598. $data = array_merge($this->select(), $source);
  599. return linqfrom($this->_from)->in($data);
  600. }
  601. /**
  602. * First
  603. *
  604. * @param string $expression Expression which creates a resulting element
  605. * @return mixed
  606. */
  607. public function first($expression = null) {
  608. $linqCommand = clone $this;
  609. $result = $linqCommand->skip(0)->take(1)->select($expression);
  610. if (count($result) > 0) {
  611. return array_shift($result);
  612. }
  613. return null;
  614. }
  615. /**
  616. * FirstOrDefault
  617. *
  618. * @param string $expression Expression which creates a resulting element
  619. * @param mixed $defaultValue Default value to return if nothing is found
  620. * @return mixed
  621. */
  622. public function firstOrDefault ($expression = null, $defaultValue = null) {
  623. $returnValue = $this->first($expression);
  624. if (!is_null($returnValue)) {
  625. return $returnValue;
  626. } else {
  627. return $defaultValue;
  628. }
  629. }
  630. /**
  631. * Last
  632. *
  633. * @param string $expression Expression which creates a resulting element
  634. * @return mixed
  635. */
  636. public function last($expression = null) {
  637. $linqCommand = clone $this;
  638. $result = $linqCommand->reverse()->skip(0)->take(1)->select($expression);
  639. if (count($result) > 0) {
  640. return array_shift($result);
  641. }
  642. return null;
  643. }
  644. /**
  645. * LastOrDefault
  646. *
  647. * @param string $expression Expression which creates a resulting element
  648. * @param mixed $defaultValue Default value to return if nothing is found
  649. * @return mixed
  650. */
  651. public function lastOrDefault ($expression = null, $defaultValue = null) {
  652. $returnValue = $this->last($expression);
  653. if (!is_null($returnValue)) {
  654. return $returnValue;
  655. } else {
  656. return $defaultValue;
  657. }
  658. }
  659. /**
  660. * Single
  661. *
  662. * @param string $expression Expression which creates a resulting element
  663. * @return mixed
  664. */
  665. public function single($expression = null) {
  666. return $this->first($expression);
  667. }
  668. /**
  669. * SingleOrDefault
  670. *
  671. * @param string $expression Expression which creates a resulting element
  672. * @param mixed $defaultValue Default value to return if nothing is found
  673. * @return mixed
  674. */
  675. public function singleOrDefault ($expression = null, $defaultValue = null) {
  676. return $this->firstOrDefault($expression, $defaultValue);
  677. }
  678. /**
  679. * Join
  680. *
  681. * @param string $name
  682. * @return PHPLinq_Initiator
  683. */
  684. public function join($name) {
  685. return new PHPLinq_Initiator($name, $this);
  686. }
  687. /**
  688. * On
  689. *
  690. * @param string $expression Expression representing join condition
  691. * @return PHPLinq_ILinqProvider
  692. */
  693. public function on($expression) {
  694. $this->_joinCondition = new PHPLinq_Expression($expression, $this->_from);
  695. return $this->_parentProvider;
  696. }
  697. /**
  698. * Count elements
  699. *
  700. * @return int Element count
  701. */
  702. public function count() {
  703. return count($this->_data);
  704. }
  705. /**
  706. * Sum elements
  707. *
  708. * @return mixed Sum of elements
  709. */
  710. public function sum() {
  711. return array_sum($this->_data); // $this->aggregate(0, '$s, $t => $s + $t');
  712. }
  713. /**
  714. * Minimum of elements
  715. *
  716. * @return mixed Minimum of elements
  717. */
  718. public function min(){
  719. return min($this->_data);
  720. }
  721. /**
  722. * Maximum of elements
  723. *
  724. * @return mixed Maximum of elements
  725. */
  726. public function max(){
  727. return max($this->_data);
  728. }
  729. /**
  730. * Average of elements
  731. *
  732. * @return mixed Average of elements
  733. */
  734. public function average(){
  735. return $this->sum() / $this->count();
  736. }
  737. /**
  738. * Aggregate
  739. *
  740. * Example: Equivalent of count(): $this->aggregate(0, '$s, $t => $s + 1');
  741. *
  742. * @param int $seed Seed
  743. * @param string $expression Expression defining the aggregate
  744. * @return mixed aggregate
  745. */
  746. public function aggregate($seed = 0, $expression) {
  747. $codeExpression = new PHPLinq_Expression($expression);
  748. $runningValue = $seed;
  749. foreach ($this->_data as $value) {
  750. $runningValue = $codeExpression->execute( array($runningValue, $value) );
  751. }
  752. return $runningValue;
  753. }
  754. }