PageRenderTime 49ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/trunk/Classes/PHPLinq/LinqToZendDb.php

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