PageRenderTime 59ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/trunk/Classes/PHPLinq/LinqToAzure.php

#
PHP | 1122 lines | 509 code | 153 blank | 460 comment | 149 complexity | 38357880e9bd26530b8e660144bb9c0b 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_LinqToObjects */
  32. require_once('PHPLinq/LinqToObjects.php');
  33. /** PHPLinq_Expression */
  34. require_once('PHPLinq/Expression.php');
  35. /** PHPLinq_OrderByExpression */
  36. require_once('PHPLinq/OrderByExpression.php');
  37. /** PHPLinq_Initiator */
  38. require_once('PHPLinq/Initiator.php');
  39. /** Microsoft_WindowsAzure_Storage_Table */
  40. require_once 'Microsoft/WindowsAzure/Storage/Table.php';
  41. /** Register ILinqProvider */
  42. PHPLinq_Initiator::registerProvider('PHPLinq_LinqToAzure');
  43. /**
  44. * PHPLinq_LinqToAzure
  45. *
  46. * @category PHPLinq
  47. * @package PHPLinq
  48. * @copyright Copyright (c) 2008 - 2011 PHPLinq (http://www.codeplex.com/PHPLinq)
  49. */
  50. class PHPLinq_LinqToAzure implements PHPLinq_ILinqProvider {
  51. /** Constants */
  52. const T_FUNCTION = 1001001;
  53. const T_PROPERTY = 1001002;
  54. const T_CONSTANT = 1001003;
  55. const T_VARIABLE = 1001004;
  56. const T_OBJECT_OPERATOR = 1001005;
  57. const T_OPERATOR = 1001006;
  58. const T_OPERAND = 1001007;
  59. const T_DEFAULT = 1001008;
  60. const T_START_STOP = 1001009;
  61. const T_SIMPLE = 1001010;
  62. const T_ARGUMENT = 1001011;
  63. const T_ARITHMETIC = 1001012;
  64. /**
  65. * Default variable name
  66. *
  67. * @var string
  68. */
  69. private $_from = '';
  70. /**
  71. * Data source
  72. *
  73. * @var mixed
  74. */
  75. private $_data = null;
  76. /**
  77. * Where expression
  78. *
  79. * @var PHPLinq_Expression
  80. */
  81. private $_where = null;
  82. /**
  83. * Take n elements
  84. *
  85. * @var int?
  86. */
  87. private $_take = null;
  88. /**
  89. * Skip n elements
  90. *
  91. * @var int?
  92. */
  93. private $_skip = null;
  94. /**
  95. * Take while expression is true
  96. *
  97. * @var PHPLinq_Expression
  98. */
  99. private $_takeWhile = null;
  100. /**
  101. * Skip while expression is true
  102. *
  103. * @var PHPLinq_Expression
  104. */
  105. private $_skipWhile = null;
  106. /**
  107. * OrderBy expressions
  108. *
  109. * @var PHPLinq_Expression[]
  110. */
  111. private $_orderBy = array();
  112. /**
  113. * Distinct expression
  114. *
  115. * @var PHPLinq_Expression
  116. */
  117. private $_distinct = null;
  118. /**
  119. * OfType expression
  120. *
  121. * @var PHPLinq_Expression
  122. */
  123. private $_ofType = null;
  124. /**
  125. * Parent PHPLinq_ILinqProvider instance, used with join conditions
  126. *
  127. * @var PHPLinq_ILinqProvider
  128. */
  129. private $_parentProvider = null;
  130. /**
  131. * Child PHPLinq_ILinqProvider instances, used with join conditions
  132. *
  133. * @var PHPLinq_ILinqProvider[]
  134. */
  135. private $_childProviders = array();
  136. /**
  137. * Join condition
  138. *
  139. * @var PHPLinq_Expression
  140. */
  141. private $_joinCondition = null;
  142. /**
  143. * Is object destructing?
  144. *
  145. * @var bool
  146. */
  147. private $_isDestructing;
  148. /**
  149. * Columns to select
  150. *
  151. * @var string
  152. */
  153. private $_columns = '*';
  154. /**
  155. * Storage client
  156. *
  157. * @var Microsoft_WindowsAzure_Storage_Table
  158. */
  159. private $_storageClient = null;
  160. /**
  161. * Static list of PHP internal functions used for generating queries
  162. *
  163. * @var array
  164. */
  165. private static $_internalFunctions = null;
  166. /**
  167. * Can this provider type handle data in $source?
  168. *
  169. * @param mixed $source
  170. * @return bool
  171. */
  172. public static function handles($source) {
  173. return (is_array($source) && count($source) >= 2 && $source[0] instanceof Microsoft_WindowsAzure_Storage_Table && $source[1] != '');
  174. }
  175. /**
  176. * Create a new class instance
  177. *
  178. * @param string $name
  179. * @param PHPLinq_ILinqProvider $parentProvider Optional parent PHPLinq_ILinqProvider instance, used with join conditions
  180. * @return PHPLinq_ILinqProvider
  181. */
  182. public function __construct($name, PHPLinq_ILinqProvider $parentProvider = null) {
  183. if (is_null(self::$_internalFunctions)) {
  184. $internalFunctions = get_defined_functions();
  185. $internalFunctions['internal'][] = 'print'; // Add as PHP function
  186. self::$_internalFunctions = $internalFunctions['internal'];
  187. }
  188. $this->_from = $name;
  189. if (!is_null($parentProvider)) {
  190. $this->_parentProvider = $parentProvider;
  191. $parentProvider->addChildProvider($this);
  192. }
  193. return $this;
  194. }
  195. /**
  196. * Class destructor
  197. */
  198. public function __destruct() {
  199. $this->_isDestructing = true;
  200. if (isset($this->_parentProvider) && !is_null($this->_parentProvider)) {
  201. if (!$this->_parentProvider->__isDestructing()) {
  202. $this->_parentProvider->__destruct();
  203. }
  204. $this->_parentProvider = null;
  205. unset($this->_parentProvider);
  206. }
  207. if (!is_null($this->_childProviders)) {
  208. foreach ($this->_childProviders as $provider) {
  209. $provider->__destruct();
  210. $provider = null;
  211. unset($provider);
  212. }
  213. }
  214. }
  215. /**
  216. * Is object destructing?
  217. *
  218. * @return bool
  219. */
  220. public function __isDestructing() {
  221. return $this->_isDestructing;
  222. }
  223. /**
  224. * Get join condition
  225. *
  226. * @return PHPLinq_Expression
  227. */
  228. public function getJoinCondition() {
  229. return $this->_joinCondition;
  230. }
  231. /**
  232. * Add child provider, used with joins
  233. *
  234. * @param PHPLinq_ILinqProvider $provider
  235. */
  236. public function addChildProvider(PHPLinq_ILinqProvider $provider) {
  237. $this->_childProviders[] = $provider;
  238. }
  239. /**
  240. * Retrieve "from" name
  241. *
  242. * @return string
  243. */
  244. public function getFromName() {
  245. return $this->_from;
  246. }
  247. /**
  248. * Retrieve data in data source
  249. *
  250. * @return mixed
  251. */
  252. public function getSource() {
  253. return $this->_data;
  254. }
  255. /**
  256. * Set source of data
  257. *
  258. * @param mixed $source
  259. * @return PHPLinq_ILinqProvider
  260. */
  261. public function in($source) {
  262. $this->_data = $source;
  263. $this->_storageClient = $source[0];
  264. return $this;
  265. }
  266. /**
  267. * Select
  268. *
  269. * @param string $expression Expression which creates a resulting element
  270. * @return mixed
  271. */
  272. public function select($expression = null) {
  273. // Return value
  274. $returnValue = array();
  275. // Expression set?
  276. if (is_null($expression) || $expression == '') {
  277. $expression = $this->_from . ' => ' . $this->_from;
  278. }
  279. // Data source
  280. $dataSource = $this->_data[0]->select()->from($this->_data[1]);
  281. // Create selector expression
  282. $selector = new PHPLinq_Expression($expression, $this->_from);
  283. // Join set?
  284. if (count($this->_childProviders) > 0) {
  285. throw new PHPLinq_Exception('Joins are not supported in ' . __CLASS__ . '.');
  286. }
  287. // Where expresion set? Can Azure Table Storage run it? Evaluate it!
  288. if (!is_null($this->_where) && $this->_supportedByAzure($this->_where->getFunction()->getFunctionCode())) {
  289. $functionCode = $this->_convertToAzure(
  290. $this->_where->getFunction()->getFunctionCode()
  291. );
  292. $dataSource = $dataSource->where($functionCode);
  293. }
  294. // OrderBy set?
  295. /*if (is_array($this->_orderBy) && count($this->_orderBy) > 0) {
  296. $orderBy = array();
  297. foreach ($this->_orderBy as $orderByExpression) {
  298. if ($this->_supportedByAzure($orderByExpression->getExpression()->getFunction()->getFunctionCode())) {
  299. $dataSource = $dataSource->orderBy(
  300. $this->_convertToAzure(
  301. $orderByExpression->getExpression()->getFunction()->getFunctionCode()
  302. ), ($orderByExpression->isDescending() ? 'desc' : 'asc')
  303. );
  304. }
  305. }
  306. }*/
  307. // Query
  308. $results = null;
  309. if (isset($this->_data[2])) {
  310. $results = $this->_storageClient->retrieveEntities($dataSource, $this->_data[2]);
  311. } else {
  312. $results = $this->_storageClient->retrieveEntities($dataSource);
  313. }
  314. // OrderBy set?
  315. if (is_array($this->_orderBy) && count($this->_orderBy) > 0) {
  316. // Create sorter
  317. $sorter = '';
  318. // Is there only one OrderBy expression?
  319. if (count($this->_orderBy) == 1) {
  320. // First sorter
  321. $sorter = $this->_orderBy[0]->getFunctionReference();
  322. } else {
  323. // Create OrderBy expression
  324. $compareCode = '';
  325. // Compile comparer function
  326. $compareCode = "
  327. \$result = null;
  328. ";
  329. for ($i = 0; $i < count($this->_orderBy); $i++) {
  330. $f = substr($this->_orderBy[$i]->getFunctionReference(), 1);
  331. $compareCode .= "
  332. \$result = call_user_func_array(chr(0).'$f', array({$this->_from}A, {$this->_from}B));
  333. if (\$result != 0) {
  334. return \$result;
  335. }
  336. ";
  337. }
  338. $compareCode .= "return \$result;";
  339. $sorter = create_function($this->_from . 'A, ' . $this->_from . 'B', $compareCode);
  340. }
  341. // Sort!
  342. usort($results, $sorter);
  343. }
  344. // Count elements
  345. $elementCount = 0;
  346. // Loop trough data source
  347. foreach ($results as $value) {
  348. // Is it a valid element?
  349. $isValid = true;
  350. // OfType expresion set? Evaluate it!
  351. if ($isValid && !is_null($this->_ofType)) {
  352. $isValid = $this->_ofType->execute($value);
  353. }
  354. // Where expresion set? Evaluate it!
  355. if ($isValid && !is_null($this->_where)) {
  356. $isValid = $this->_where->execute($value);
  357. }
  358. // Distinct expression set? Evaluate it!
  359. if ($isValid && !is_null($this->_distinct)) {
  360. $distinctKey = $this->_distinct->execute($value);
  361. if (isset($distinctValues[$distinctKey])) {
  362. $isValid = false;
  363. } else {
  364. $distinctValues[$distinctKey] = 1;
  365. }
  366. }
  367. // The element is valid, check if it is our selection range
  368. if ($isValid) {
  369. // Skip element?
  370. if (!is_null($this->_skipWhile) && $this->_skipWhile->execute($value)) {
  371. $isValid = false;
  372. }
  373. if (!is_null($this->_skip) && $elementCount < $this->_skip) {
  374. $isValid = false;
  375. }
  376. // Take element?
  377. if (!is_null($this->_takeWhile) && !$this->_takeWhile->execute($value)) {
  378. $isValid = false;
  379. break;
  380. }
  381. if (!is_null($this->_take) && count($returnValue) >= $this->_take) {
  382. $isValid = false;
  383. break;
  384. }
  385. // Next element
  386. $elementCount++;
  387. // Add the element to the return value if it is a valid element
  388. if ($isValid) {
  389. $returnValue[] = $selector->execute($value);
  390. }
  391. }
  392. }
  393. // Return value
  394. return $returnValue;
  395. }
  396. /**
  397. * PHP code supported by Azure?
  398. *
  399. * @param string $phpCode Code to verify
  400. * @return boolean
  401. */
  402. private function _supportedByAzure($phpCode) {
  403. $supported = true;
  404. // Go parse!
  405. $tokens = token_get_all('<?php ' . $phpCode . '?>');
  406. foreach ($tokens as $token) {
  407. if (is_array($token) && $token[0] == T_STRING) {
  408. if (in_array($token[1], self::$_internalFunctions)) {
  409. return false;
  410. }
  411. }
  412. }
  413. // All is ok!
  414. return true;
  415. }
  416. /**
  417. * Converts PHP code into Azure query code
  418. *
  419. * @param string $phpCode Code to convert
  420. * @return string
  421. */
  422. private function _convertToAzure($phpCode) {
  423. // Some fast cleaning
  424. $phpCode = str_replace('return ', '', $phpCode);
  425. $phpCode = str_replace(';', '', $phpCode);
  426. // Go parse!
  427. $tokens = token_get_all('<?php ' . $phpCode . '?>');
  428. $stack = array();
  429. $tokenId = 0;
  430. $depth = 0;
  431. for ($i = 0; $i < count($tokens); $i++) {
  432. // Ignore token?
  433. $ignoreToken = false;
  434. // Token details
  435. $previousToken = $i > 0 ? $tokens[$i - 1] : null;
  436. $token = $tokens[$i];
  437. $nextToken = $i < count($tokens) - 1 ? $tokens[$i + 1] : null;
  438. // Parse token
  439. if (is_array($token)) {
  440. switch ($token[0]) {
  441. case T_OPEN_TAG:
  442. case T_CLOSE_TAG:
  443. $ignoreToken = true;
  444. break;
  445. case T_STRING:
  446. if (!in_array($token[1], self::$_internalFunctions)) {
  447. // Probably some sort of constant / property name
  448. if (!is_null($previousToken) && is_array($previousToken) && $previousToken[0] == T_OBJECT_OPERATOR) {
  449. $stack[$tokenId]['token'] = '\'' . $token[1] . '\'';
  450. $stack[$tokenId]['type'] = self::T_PROPERTY;
  451. } else {
  452. die($token[1]);
  453. $stack[$tokenId]['token'] = '\'' . $token[1] . '\'';
  454. $stack[$tokenId]['type'] = self::T_CONSTANT;
  455. }
  456. }
  457. break;
  458. case T_VARIABLE:
  459. case T_OBJECT_OPERATOR:
  460. $ignoreToken = true;
  461. break;
  462. case T_IS_IDENTICAL:
  463. case T_IS_EQUAL:
  464. $stack[$tokenId]['token'] = '\' eq \'';
  465. $stack[$tokenId]['type'] = self::T_OPERATOR;
  466. break;
  467. case T_IS_NOT_EQUAL:
  468. case T_IS_NOT_IDENTICAL:
  469. $stack[$tokenId]['token'] = '\' ne \'';
  470. $stack[$tokenId]['type'] = self::T_OPERATOR;
  471. break;
  472. case T_IS_GREATER_OR_EQUAL:
  473. $stack[$tokenId]['token'] = '\' ge \'';
  474. $stack[$tokenId]['type'] = self::T_OPERATOR;
  475. break;
  476. case T_IS_SMALLER_OR_EQUAL:
  477. $stack[$tokenId]['token'] = '\' le \'';
  478. $stack[$tokenId]['type'] = self::T_OPERATOR;
  479. break;
  480. case T_BOOLEAN_AND:
  481. case T_LOGICAL_AND:
  482. $stack[$tokenId]['token'] = '\' and \'';
  483. $stack[$tokenId]['type'] = self::T_OPERAND;
  484. break;
  485. case T_BOOLEAN_OR:
  486. case T_LOGICAL_OR:
  487. $stack[$tokenId]['token'] = '\' or \'';
  488. $stack[$tokenId]['type'] = self::T_OPERAND;
  489. break;
  490. default:
  491. $stack[$tokenId]['token'] = '\'' . str_replace('"', '\\\'', $token[1]) . '\'';
  492. $stack[$tokenId]['type'] = self::T_DEFAULT;
  493. break;
  494. }
  495. } else {
  496. // Simple token
  497. if ($token != '(' && $token != ')') {
  498. $stack[$tokenId]['token'] = '\'' . $token . '\'';
  499. $stack[$tokenId]['type'] = self::T_SIMPLE;
  500. if ($token == ',') {
  501. $stack[$tokenId]['type'] = self::T_ARGUMENT;
  502. } else if ($token == '+' || $token == '-' || $token == '*' || $token == '/' || $token == '%' || $token == '.') {
  503. $stack[$tokenId]['token'] = '\'' . $token . '\'';
  504. $stack[$tokenId]['type'] = self::T_ARITHMETIC;
  505. } else if ($token == '>') {
  506. $stack[$tokenId]['token'] = '\' gt \'';
  507. $stack[$tokenId]['type'] = self::T_OPERATOR;
  508. } else if ($token == '<') {
  509. $stack[$tokenId]['token'] = '\' lt \'';
  510. $stack[$tokenId]['type'] = self::T_OPERATOR;
  511. }
  512. } else {
  513. $stack[$tokenId]['token'] = '';
  514. $stack[$tokenId]['type'] = self::T_START_STOP;
  515. }
  516. }
  517. // Token metadata
  518. if (!$ignoreToken) {
  519. // Depth
  520. if (!is_array($token) && $token == '(') $depth++;
  521. if (!is_array($token) && $token == ')') $depth--;
  522. $stack[$tokenId]['depth'] = $depth;
  523. // Identifier
  524. $tokenId++;
  525. }
  526. }
  527. // Display tree
  528. /*
  529. foreach ($stack as $node) {
  530. for ($i = 0; $i <= $node['depth']; $i++) {
  531. echo "| ";
  532. }
  533. echo $node['type'] . ' - ' . $node['token'] . "\r\n";
  534. }
  535. die();
  536. */
  537. // Build compilation string
  538. $compileCode = '';
  539. $functionDepth = array();
  540. $depth = 0;
  541. for ($i = 0; $i < count($stack); $i++) {
  542. // Token details
  543. $previousToken = $i > 0 ? $stack[$i - 1] : null;
  544. $token = $stack[$i];
  545. $nextToken = $i < count($stack) - 1 ? $stack[$i + 1] : null;
  546. // Regular token
  547. if ($token['depth'] == $depth && $token['type'] != self::T_START_STOP) {
  548. $compileCode .= $token['token'];
  549. }
  550. // Start/stop
  551. if ($token['depth'] > $depth && $token['type'] == self::T_START_STOP && !is_null($previousToken) && $previousToken['type'] == self::T_FUNCTION) {
  552. $compileCode .= '(';
  553. $functionDepth[$depth] = self::T_FUNCTION;
  554. } else if ($token['depth'] < $depth && $token['type'] == self::T_START_STOP && $functionDepth[ $token['depth'] ] == self::T_FUNCTION) {
  555. $compileCode .= ')';
  556. } else if ($token['depth'] > $depth && $token['type'] == self::T_START_STOP) {
  557. $compileCode .= '\'(\'';
  558. $functionDepth[$depth] = self::T_START_STOP;
  559. } else if ($token['depth'] < $depth && $token['type'] == self::T_START_STOP) {
  560. $compileCode .= '\')\'';
  561. }
  562. // Next token needs concatenation?
  563. if (!is_null($nextToken) && $nextToken['type'] != self::T_ARGUMENT) {
  564. if (
  565. !($token['type'] == self::T_FUNCTION && $nextToken['type'] == self::T_START_STOP) &&
  566. !(!is_null($previousToken) && $previousToken['type'] == self::T_FUNCTION && $token['type'] == self::T_START_STOP) &&
  567. !($token['type'] == self::T_ARGUMENT) &&
  568. !(!is_null($nextToken) && $nextToken['type'] == self::T_START_STOP && isset($functionDepth[ $nextToken['depth'] ]) && $functionDepth[ $nextToken['depth'] ] == self::T_FUNCTION) &&
  569. !($token['type'] == self::T_VARIABLE && !is_null($nextToken) && $nextToken['type'] == self::T_PROPERTY)
  570. ) {
  571. $compileCode .= ' . ';
  572. }
  573. }
  574. // Depth
  575. if ($token['depth'] < $depth) {
  576. unset($functionDepth[$token['depth']]);
  577. }
  578. $depth = $token['depth'];
  579. }
  580. // Compile
  581. $compileCode = '$compileCode = ' . $compileCode . ';';
  582. eval($compileCode);
  583. return $compileCode;
  584. }
  585. /**
  586. * Where
  587. *
  588. * @param string $expression Expression checking if an element should be contained
  589. * @return PHPLinq_ILinqProvider
  590. */
  591. public function where($expression) {
  592. $this->_where = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
  593. return $this;
  594. }
  595. /**
  596. * Take $n elements
  597. *
  598. * @param int $n
  599. * @return PHPLinq_ILinqProvider
  600. */
  601. public function take($n) {
  602. $this->_take = $n;
  603. return $this;
  604. }
  605. /**
  606. * Skip $n elements
  607. *
  608. * @param int $n
  609. * @return PHPLinq_ILinqProvider
  610. */
  611. public function skip($n) {
  612. $this->_skip = $n;
  613. return $this;
  614. }
  615. /**
  616. * Take elements while $expression evaluates to true
  617. *
  618. * @param string $expression Expression to evaluate
  619. * @return PHPLinq_ILinqProvider
  620. */
  621. public function takeWhile($expression) {
  622. $this->_takeWhile = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
  623. return $this;
  624. }
  625. /**
  626. * Skip elements while $expression evaluates to true
  627. *
  628. * @param string $expression Expression to evaluate
  629. * @return PHPLinq_ILinqProvider
  630. */
  631. public function skipWhile($expression) {
  632. $this->_skipWhile = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
  633. return $this;
  634. }
  635. /**
  636. * OrderBy
  637. *
  638. * @param string $expression Expression to order elements by
  639. * @param string $comparer Comparer function (taking 2 arguments, returning -1, 0, 1)
  640. * @return PHPLinq_ILinqProvider
  641. */
  642. public function orderBy($expression, $comparer = null) {
  643. $this->_orderBy[0] = new PHPLinq_OrderByExpression($expression, $this->_from, false, $comparer);
  644. return $this;
  645. }
  646. /**
  647. * OrderByDescending
  648. *
  649. * @param string $expression Expression to order elements by
  650. * @param string $comparer Comparer function (taking 2 arguments, returning -1, 0, 1)
  651. * @return PHPLinq_ILinqProvider
  652. */
  653. public function orderByDescending($expression, $comparer = null) {
  654. $this->_orderBy[0] = new PHPLinq_OrderByExpression($expression, $this->_from, true, $comparer);
  655. return $this;
  656. }
  657. /**
  658. * ThenBy
  659. *
  660. * @param string $expression Expression to order elements by
  661. * @param string $comparer Comparer function (taking 2 arguments, returning -1, 0, 1)
  662. * @return PHPLinq_ILinqProvider
  663. */
  664. public function thenBy($expression, $comparer = null) {
  665. $this->_orderBy[] = new PHPLinq_OrderByExpression($expression, $this->_from, false, $comparer);
  666. return $this;
  667. }
  668. /**
  669. * ThenByDescending
  670. *
  671. * @param string $expression Expression to order elements by
  672. * @param string $comparer Comparer function (taking 2 arguments, returning -1, 0, 1)
  673. * @return PHPLinq_ILinqProvider
  674. */
  675. public function thenByDescending($expression, $comparer = null) {
  676. $this->_orderBy[] = new PHPLinq_OrderByExpression($expression, $this->_from, true, $comparer);
  677. return $this;
  678. }
  679. /**
  680. * Distinct
  681. *
  682. * @param string $expression Ignored.
  683. * @return PHPLinq_ILinqProvider
  684. */
  685. public function distinct($expression) {
  686. $this->_distinct = !is_null($expression) ? new PHPLinq_Expression($expression, $this->_from) : null;
  687. return $this;
  688. }
  689. /**
  690. * Select the elements of a certain type
  691. *
  692. * @param string $type Type name
  693. */
  694. public function ofType($type) {
  695. // Create a new expression
  696. $expression = $this->_from . ' => ';
  697. // Evaluate type
  698. switch (strtolower($type)) {
  699. case 'array':
  700. case 'bool':
  701. case 'double':
  702. case 'float':
  703. case 'int':
  704. case 'integer':
  705. case 'long':
  706. case 'null':
  707. case 'numeric':
  708. case 'object':
  709. case 'real':
  710. case 'scalar':
  711. case 'string':
  712. $expression .= 'is_' . strtolower($type) . '(' . $this->_from . ')';
  713. break;
  714. default:
  715. $expression .= 'is_a(' . $this->_from . ', "' . $type . '")';
  716. break;
  717. }
  718. // Assign expression
  719. $this->_ofType = new PHPLinq_Expression($expression, $this->_from);
  720. return $this;
  721. }
  722. /**
  723. * Any
  724. *
  725. * @param string $expression Expression checking if an element is contained
  726. * @return boolean
  727. */
  728. public function any($expression) {
  729. $originalWhere = $this->_where;
  730. $result = $this->where($expression)->select($this->_from);
  731. $this->_where = $originalWhere;
  732. return count($result) > 0;
  733. }
  734. /**
  735. * All
  736. *
  737. * @param string $expression Expression checking if an all elements are contained
  738. * @return boolean
  739. */
  740. public function all($expression) {
  741. $originalWhere = $this->_where;
  742. $result = $this->where($expression)->select($this->_from);
  743. $this->_where = $originalWhere;
  744. return count($result) == count($this->_data);
  745. }
  746. /**
  747. * Contains - Not performed as query! (heavy)
  748. *
  749. * @param mixed $element Is the $element contained?
  750. * @return boolean
  751. */
  752. public function contains($element) {
  753. return in_array($element, $this->select());
  754. }
  755. /**
  756. * Reverse elements - Not performed as query! (heavy)
  757. *
  758. * @param bool $preserveKeys Preserve keys?
  759. * @return PHPLinq_ILinqProvider
  760. */
  761. public function reverse($preserveKeys = null) {
  762. $data = array_reverse($this->select(), $preserveKeys);
  763. return linqfrom($this->_from)->in($data);
  764. }
  765. /**
  766. * Element at index
  767. *
  768. * @param mixed $index Index
  769. * @return mixed Element at $index
  770. */
  771. public function elementAt($index = null) {
  772. $originalWhere = $this->_where;
  773. $result = $this->where(null)->take(1)->skip($index)->select();
  774. $this->_where = $originalWhere;
  775. if (count($result) > 0) {
  776. return array_shift($result);
  777. }
  778. return null;
  779. }
  780. /**
  781. * Element at index or default
  782. *
  783. * @param mixed $index Index
  784. * @param mixed $defaultValue Default value to return if nothing is found
  785. * @return mixed Element at $index
  786. */
  787. public function elementAtOrDefault($index = null, $defaultValue = null) {
  788. $returnValue = $this->elementAt($index);
  789. if (!is_null($returnValue)) {
  790. return $returnValue;
  791. } else {
  792. return $defaultValue;
  793. }
  794. }
  795. /**
  796. * Concatenate data
  797. *
  798. * @param mixed $source
  799. * @return PHPLinq_ILinqProvider
  800. */
  801. public function concat($source) {
  802. $data = array_merge($this->select(), $source);
  803. return linqfrom($this->_from)->in($data);
  804. }
  805. /**
  806. * First
  807. *
  808. * @param string $expression Expression which creates a resulting element
  809. * @return mixed
  810. */
  811. public function first($expression = null) {
  812. $linqCommand = clone $this;
  813. $result = $linqCommand->skip(0)->take(1)->select($expression);
  814. if (count($result) > 0) {
  815. return array_shift($result);
  816. }
  817. return null;
  818. }
  819. /**
  820. * FirstOrDefault
  821. *
  822. * @param string $expression Expression which creates a resulting element
  823. * @param mixed $defaultValue Default value to return if nothing is found
  824. * @return mixed
  825. */
  826. public function firstOrDefault ($expression = null, $defaultValue = null) {
  827. $returnValue = $this->first($expression);
  828. if (!is_null($returnValue)) {
  829. return $returnValue;
  830. } else {
  831. return $defaultValue;
  832. }
  833. }
  834. /**
  835. * Last
  836. *
  837. * @param string $expression Expression which creates a resulting element
  838. * @return mixed
  839. */
  840. public function last($expression = null) {
  841. $linqCommand = clone $this;
  842. $result = $linqCommand->reverse()->skip(0)->take(1)->select($expression);
  843. if (count($result) > 0) {
  844. return array_shift($result);
  845. }
  846. return null;
  847. }
  848. /**
  849. * LastOrDefault
  850. *
  851. * @param string $expression Expression which creates a resulting element
  852. * @param mixed $defaultValue Default value to return if nothing is found
  853. * @return mixed
  854. */
  855. public function lastOrDefault ($expression = null, $defaultValue = null) {
  856. $returnValue = $this->last($expression);
  857. if (!is_null($returnValue)) {
  858. return $returnValue;
  859. } else {
  860. return $defaultValue;
  861. }
  862. }
  863. /**
  864. * Single
  865. *
  866. * @param string $expression Expression which creates a resulting element
  867. * @return mixed
  868. */
  869. public function single($expression = null) {
  870. return $this->first($expression);
  871. }
  872. /**
  873. * SingleOrDefault
  874. *
  875. * @param string $expression Expression which creates a resulting element
  876. * @param mixed $defaultValue Default value to return if nothing is found
  877. * @return mixed
  878. */
  879. public function singleOrDefault ($expression = null, $defaultValue = null) {
  880. return $this->firstOrDefault($expression, $defaultValue);
  881. }
  882. /**
  883. * Join
  884. *
  885. * @param string $name
  886. * @return PHPLinq_Initiator
  887. */
  888. public function join($name) {
  889. return new PHPLinq_Initiator($name, $this);
  890. }
  891. /**
  892. * On
  893. *
  894. * @param string $expression Expression representing join condition
  895. * @return PHPLinq_ILinqProvider
  896. */
  897. public function on($expression) {
  898. $this->_joinCondition = new PHPLinq_Expression($expression, $this->_from);
  899. return $this->_parentProvider;
  900. }
  901. /**
  902. * Count elements
  903. *
  904. * @return int Element count
  905. */
  906. public function count() {
  907. $linqCommand = clone $this;
  908. $result = $linqCommand->select('$x');
  909. return count($result);
  910. }
  911. /**
  912. * Sum elements
  913. *
  914. * @return mixed Sum of elements
  915. */
  916. public function sum() {
  917. $linqCommand = clone $this;
  918. $result = $linqCommand->select('$x');
  919. return array_sum($result); // $this->aggregate(0, '$s, $t => $s + $t');
  920. }
  921. /**
  922. * Minimum of elements
  923. *
  924. * @return mixed Minimum of elements
  925. */
  926. public function min(){
  927. $linqCommand = clone $this;
  928. $result = $linqCommand->select('$x');
  929. return min($result);
  930. }
  931. /**
  932. * Maximum of elements
  933. *
  934. * @return mixed Maximum of elements
  935. */
  936. public function max(){
  937. $linqCommand = clone $this;
  938. $result = $linqCommand->select('$x');
  939. return max($result);
  940. }
  941. /**
  942. * Average of elements
  943. *
  944. * @return mixed Average of elements
  945. */
  946. public function average(){
  947. $linqCommand = clone $this;
  948. $result = $linqCommand->select('$x');
  949. return sum($result) / count($result);
  950. }
  951. /**
  952. * Aggregate
  953. *
  954. * Example: Equivalent of count(): $this->aggregate(0, '$s, $t => $s + 1');
  955. *
  956. * @param int $seed Seed
  957. * @param string $expression Expression defining the aggregate
  958. * @return mixed aggregate
  959. */
  960. public function aggregate($seed = 0, $expression) {
  961. $codeExpression = new PHPLinq_Expression($expression);
  962. $runningValue = $seed;
  963. foreach ($this->_data as $value) {
  964. $runningValue = $codeExpression->execute( array($runningValue, $value) );
  965. }
  966. return $runningValue;
  967. }
  968. }