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