Source for file LinqToZendDb.php
Documentation is available at LinqToZendDb.php
* Copyright (c) 2008 - 2009 PHPLinq
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* @copyright Copyright (c) 2008 - 2009 PHPLinq (http://www.codeplex.com/PHPLinq)
* @license http://www.gnu.org/licenses/lgpl.txt LGPL
* @version 0.4.0, 2009-01-27
require_once('PHPLinq.php');
/** PHPLinq_ILinqProvider */
require_once('PHPLinq/ILinqProvider.php');
/** PHPLinq_LinqToObjects */
require_once('PHPLinq/LinqToObjects.php');
/** PHPLinq_Expression */
require_once('PHPLinq/Expression.php');
/** PHPLinq_OrderByExpression */
require_once('PHPLinq/OrderByExpression.php');
require_once('PHPLinq/Initiator.php');
/** PHPLinq_Adapter_Abstract */
require_once('PHPLinq/Adapter/Abstract.php');
require_once('Zend/Db/Table.php');
/** Register ILinqProvider */
* @copyright Copyright (c) 2008 - 2009 PHPLinq (http://www.codeplex.com/PHPLinq)
const T_FUNCTION = 1001001;
const T_PROPERTY = 1001002;
const T_CONSTANT = 1001003;
const T_VARIABLE = 1001004;
const T_OBJECT_OPERATOR = 1001005;
const T_OPERATOR = 1001006;
const T_OPERAND = 1001007;
const T_DEFAULT = 1001008;
const T_START_STOP = 1001009;
const T_SIMPLE = 1001010;
const T_ARGUMENT = 1001011;
const T_ARITHMETIC = 1001012;
* @var PHPLinq_Expression
* Take while expression is true
* @var PHPLinq_Expression
* Skip while expression is true
* @var PHPLinq_Expression
* @var PHPLinq_Expression[]
* @var PHPLinq_Expression
* @var PHPLinq_Expression
* Parent PHPLinq_ILinqProvider instance, used with join conditions
* @var PHPLinq_ILinqProvider
* Child PHPLinq_ILinqProvider instances, used with join conditions
* @var PHPLinq_ILinqProvider[]
* @var PHPLinq_Expression
* PHPLinq_Adapter_Abstract instance
* @var PHPLinq_Adapter_Abstract
* Query callback (static for all PHPLinq_LinqToZendDb !)
* Function accepting a string to which query strings can be logged.
private static $_queryCallback = null;
* Static list of PHP internal functions used for generating queries
private static $_internalFunctions = null;
* Can this provider type handle data in $source?
public static function handles($source) {
return $source instanceof Zend_Db_Table;
* Create a new class instance
* @param PHPLinq_ILinqProvider $parentProvider Optional parent PHPLinq_ILinqProvider instance, used with join conditions
* @return PHPLinq_ILinqProvider
public function __construct($name, PHPLinq_ILinqProvider $parentProvider = null) {
if (is_null(self::$_internalFunctions)) {
$internalFunctions = get_defined_functions();
$internalFunctions['internal'][] = 'print'; // Add as PHP function
self::$_internalFunctions = $internalFunctions['internal'];
if (!is_null($parentProvider)) {
$parentProvider->addChildProvider($this);
* @return PHPLinq_Expression
* Add child provider, used with joins
* @param PHPLinq_ILinqProvider $provider
* Retrieve data in data source
* @return PHPLinq_ILinqProvider
public function in($source) {
// Configure PHPLinq_Adapter_Abstract
$zendDbAdapter = get_class($source->getAdapter());
$phpLinqAdapter = str_replace('Zend_Db', 'PHPLinq', $zendDbAdapter);
require_once(str_replace('_', '/', $phpLinqAdapter) . '.php');
$this->_adapter = new $phpLinqAdapter($source->getAdapter());
* @param string $expression Expression which creates a resulting element
public function select($expression = null) {
// Zend_Db_Select expression
if (is_null($expression) || $expression == '') {
$expression = $this->_from . ' => ' . $this->_from;
$dataSourceInfo = $this->_data->info();
$dataSource = $this->_data->select()->from( array($this->_from => $dataSourceInfo['name']), $this->_columns );
// Create selector expression
// Build Zend_Db_Select chain
$zendDbSelect = $dataSource;
$zendDbSelect = $zendDbSelect->distinct();
// Check if the child providers can be supported
$joinInfo = $provider->getSource()->info();
$dataSource = $dataSource->join(
array($provider->getFromName() => $joinInfo['name']),
$this->_convertToSql($provider->getJoinCondition()->getFunction()->getFunctionCode())
// Where expresion set? Evaluate it!
$dataSource->getAdapter()->getQuoteIdentifierSymbol()
$zendDbSelect = $zendDbSelect->where($functionCode);
foreach ($this->_orderBy as $orderByExpression) {
$orderBy[] = new Zend_Db_Expr(
$orderByExpression->getExpression()->getFunction()->getFunctionCode()
) . ($orderByExpression->isDescending() ? ' DESC' : ' ASC')
$zendDbSelect = $zendDbSelect->order($orderBy);
// No integrity check needed, joins can be free format
$zendDbSelect->setIntegrityCheck(false);
if (!is_null(self::$_queryCallback)) {
if (!is_array(self::$_queryCallback) && in_array(self::$_queryCallback, self::$_internalFunctions)) {
self::$_queryCallback = create_function('$query', self::$_queryCallback . '($query);');
call_user_func(self::$_queryCallback, $zendDbSelect->__toString());
$results = $zendDbSelect->query(Zend_Db::FETCH_OBJ)->fetchAll();
// Row class set? If so, convert all data!
if ($this->_data->getRowClass() != 'Zend_Db_Table_Row') {
$rowClass = $this->_data->getRowClass();
foreach ($results as $result) {
$tempResult = call_user_func( array($rowClass, 'fromArray'), (array) $result );
$tempResults[] = $tempResult;
// Get data in mode FETCH_NAMED
$tempResults = $zendDbSelect->query(Zend_Db::FETCH_NAMED)->fetchAll();
$info = array( $this->_data->info() );
$info[] = $provider->getSource()->info();
// Build proper resultset
foreach ($tempResults as $tempResult) {
$currentResult = array();
// For each table, add a new object with values
for ($i = 0; $i < count($info); $i++ ) {
$currentObject = array();
foreach($info[$i]['cols'] as $column) {
$currentObject[$column] = $tempResult[$column];
// Some searching to do...
for ($j = $i; $j >= 0; $j-- ) {
if (isset ($tempResult[$column][$j])) {
$currentObject[$column] = $tempResult[$column][$j];
// Add object to result table.
// Row class set? If so, convert all data!
if ( ($i == 0 && $this->_data->getRowClass() != 'Zend_Db_Table_Row') ||
$this->_childProviders[$i - 1]->getSource()->getRowClass() != 'Zend_Db_Table_Row') {
$rowClass = $i == 0 ? $this->_data->getRowClass() : $this->_childProviders[$i - 1]->getSource()->getRowClass();
$currentResult[] = call_user_func( array($rowClass, 'fromArray'), (array) $currentObject );
$currentResult[] = (object) $currentObject;
$results[] = $currentResult;
// Loop trough data source
foreach ($results as $value) {
// Is it a valid element?
// OfType expresion set? Evaluate it!
// The element is valid, check if it is our selection range
// Add the element to the return value if it is a valid element
$returnValue[] = $selector->execute($value);
* Converts PHP code into SQL code
* @param string $phpCode Code to convert
$adapterFunctions = $this->_adapter->getFunctions();
for ($i = 0; $i < count($tokens); $i++ ) {
$previousToken = $i > 0 ? $tokens[$i - 1] : null;
$nextToken = $i < count($tokens) - 1 ? $tokens[$i + 1] : null;
if (in_array($token[1], $adapterFunctions)) {
// It is an adapter function!
$stack[$tokenId]['token'] = '$this->_adapter->' . $token[1];
$stack[$tokenId]['type'] = self::T_FUNCTION;
} else if (in_array($token[1], self::$_internalFunctions)) {
// If it is a PHP function, let's take a lucky
// shot and expect the syntax to be the same...
$stack[$tokenId]['token'] = $token[1];
$stack[$tokenId]['type'] = self::T_FUNCTION;
// Probably some sort of constant / property name
$stack[$tokenId]['token'] = $this->_adapter->quoteIdentifier($token[1]) . '\'';
$stack[$tokenId]['type'] = self::T_PROPERTY;
$stack[$tokenId]['token'] = '\'' . $token[1] . '\'';
$stack[$tokenId]['type'] = self::T_CONSTANT;
$stack[$tokenId]['token'] = $this->_adapter->quote($token[1]);
$stack[$tokenId]['type'] = self::T_VARIABLE;
$stack[$tokenId - 1]['token'] = '\'' . addslashes($stack[$tokenId - 1]['token']) . '.';
$stack[$tokenId]['token'] = '\'.\'';
$stack[$tokenId]['type'] = self::T_OBJECT_OPERATOR;
$stack[$tokenId]['token'] = '\' ' . $this->_adapter->operator('=') . ' \'';
$stack[$tokenId]['type'] = self::T_OPERATOR;
$stack[$tokenId]['token'] = '\' ' . $this->_adapter->operator('!=') . ' \'';
$stack[$tokenId]['type'] = self::T_OPERATOR;
case T_IS_GREATER_OR_EQUAL:
$stack[$tokenId]['token'] = '\' ' . $this->_adapter->operator('>=') . ' \'';
$stack[$tokenId]['type'] = self::T_OPERATOR;
case T_IS_SMALLER_OR_EQUAL:
$stack[$tokenId]['token'] = '\' ' . $this->_adapter->operator('<=') . ' \'';
$stack[$tokenId]['type'] = self::T_OPERATOR;
$stack[$tokenId]['token'] = '\' ' . $this->_adapter->operand('AND') . ' \'';
$stack[$tokenId]['type'] = self::T_OPERAND;
$stack[$tokenId]['token'] = '\' ' . $this->_adapter->operand('OR') . ' \'';
$stack[$tokenId]['type'] = self::T_OPERAND;
$stack[$tokenId]['token'] = '\'' . $token[1] . '\'';
$stack[$tokenId]['type'] = self::T_DEFAULT;
if ($token != '(' && $token != ')') {
$stack[$tokenId]['token'] = $token;
$stack[$tokenId]['type'] = self::T_SIMPLE;
$stack[$tokenId]['type'] = self::T_ARGUMENT;
} else if ($token == '+' || $token == '-' || $token == '*' || $token == '/' || $token == '%' || $token == '.') {
$stack[$tokenId]['token'] = '\'' . $this->_adapter->operator($token) . '\'';
$stack[$tokenId]['type'] = self::T_ARITHMETIC;
} else if ($token == '>' || $token == '<') {
$stack[$tokenId]['token'] = '\' ' . $this->_adapter->operator($token) . ' \'';
$stack[$tokenId]['type'] = self::T_OPERATOR;
$stack[$tokenId]['token'] = '';
$stack[$tokenId]['type'] = self::T_START_STOP;
if (!is_array($token) && $token == '(') $depth++ ;
if (!is_array($token) && $token == ')') $depth-- ;
$stack[$tokenId]['depth'] = $depth;
foreach ($stack as $node) {
for ($i = 0; $i <= $node['depth']; $i++) {
echo $node['type'] . ' - ' . $node['token'] . "\r\n";
// Build compilation string
$functionDepth = array();
for ($i = 0; $i < count($stack); $i++ ) {
$previousToken = $i > 0 ? $stack[$i - 1] : null;
$nextToken = $i < count($stack) - 1 ? $stack[$i + 1] : null;
if ($token['depth'] == $depth && $token['type'] != self::T_START_STOP) {
$compileCode .= $token['token'];
if ($token['depth'] > $depth && $token['type'] == self::T_START_STOP && !is_null($previousToken) && $previousToken['type'] == self::T_FUNCTION) {
$functionDepth[$depth] = self::T_FUNCTION;
} else if ($token['depth'] < $depth && $token['type'] == self::T_START_STOP && $functionDepth[ $token['depth'] ] == self::T_FUNCTION) {
} else if ($token['depth'] > $depth && $token['type'] == self::T_START_STOP) {
$functionDepth[$depth] = self::T_START_STOP;
} else if ($token['depth'] < $depth && $token['type'] == self::T_START_STOP) {
// Next token needs concatenation?
if (!is_null($nextToken) && $nextToken['type'] != self::T_ARGUMENT) {
!($token['type'] == self::T_FUNCTION && $nextToken['type'] == self::T_START_STOP) &&
!(!is_null($previousToken) && $previousToken['type'] == self::T_FUNCTION && $token['type'] == self::T_START_STOP) &&
!($token['type'] == self::T_ARGUMENT) &&
!(!is_null($nextToken) && $nextToken['type'] == self::T_START_STOP && isset ($functionDepth[ $nextToken['depth'] ]) && $functionDepth[ $nextToken['depth'] ] == self::T_FUNCTION) &&
!($token['type'] == self::T_VARIABLE && !is_null($nextToken) && $nextToken['type'] == self::T_PROPERTY)
if ($token['depth'] < $depth) {
unset ($functionDepth[$token['depth']]);
$depth = $token['depth'];
$compileCode = '$compileCode = ' . $compileCode . ';';
* @param string $expression Expression checking if an element should be contained
* @return PHPLinq_ILinqProvider
public function where($expression) {
* @return PHPLinq_ILinqProvider
public function take($n) {
* @return PHPLinq_ILinqProvider
public function skip($n) {
* Take elements while $expression evaluates to true
* @param string $expression Expression to evaluate
* @return PHPLinq_ILinqProvider
* Skip elements while $expression evaluates to true
* @param string $expression Expression to evaluate
* @return PHPLinq_ILinqProvider
* @param string $expression Expression to order elements by
* @param string $comparer Comparer function (taking 2 arguments, returning -1, 0, 1)
* @return PHPLinq_ILinqProvider
public function orderBy($expression, $comparer = null) {
* @param string $expression Expression to order elements by
* @param string $comparer Comparer function (taking 2 arguments, returning -1, 0, 1)
* @return PHPLinq_ILinqProvider
* @param string $expression Expression to order elements by
* @param string $comparer Comparer function (taking 2 arguments, returning -1, 0, 1)
* @return PHPLinq_ILinqProvider
public function thenBy($expression, $comparer = null) {
* @param string $expression Expression to order elements by
* @param string $comparer Comparer function (taking 2 arguments, returning -1, 0, 1)
* @return PHPLinq_ILinqProvider
* @param string $expression Ignored.
* @return PHPLinq_ILinqProvider
* Select the elements of a certain type
* @param string $type Type name
public function ofType($type) {
// Create a new expression
$expression = $this->_from . ' => ';
$expression .= 'is_a(' . $this->_from . ', "' . $type . '")';
* @param string $expression Expression checking if an element is contained
public function any($expression) {
$originalWhere = $this->_where;
$countFrom = $this->_from . ' => ' . $this->_from . '->cnt';
$result = $this->where($expression)->select($countFrom);
$this->_where = $originalWhere;
* @param string $expression Expression checking if an all elements are contained
public function all($expression) {
$originalWhere = $this->_where;
$countFrom = $this->_from . ' => ' . $this->_from . '->cnt';
$result1 = $this->where($expression)->select($countFrom);
$result2 = $this->where(null)->select($countFrom);
$this->_where = $originalWhere;
return $result1[0] == $result2[0];
* Contains - Not performed as query! (heavy)
* @param mixed $element Is the $element contained?
* Reverse elements - Not performed as query! (heavy)
* @param bool $preserveKeys Preserve keys?
* @return PHPLinq_ILinqProvider
public function reverse($preserveKeys = null) {
* @param mixed $index Index
* @return mixed Element at $index
$originalWhere = $this->_where;
$result = $this->where(null)->take(1)->skip($index)->select();
$this->_where = $originalWhere;
if (count($result) > 0) {
* Element at index or default
* @param mixed $index Index
* @param mixed $defaultValue Default value to return if nothing is found
* @return mixed Element at $index
* @return PHPLinq_ILinqProvider
public function concat($source) {
* @param string $expression Expression which creates a resulting element
public function first($expression = null) {
$linqCommand = clone $this;
$result = $linqCommand->skip(0)->take(1)->select($expression);
if (count($result) > 0) {
* @param string $expression Expression which creates a resulting element
* @param mixed $defaultValue Default value to return if nothing is found
public function firstOrDefault ($expression = null, $defaultValue = null) {
$returnValue = $this->first($expression);
* @param string $expression Expression which creates a resulting element
public function last($expression = null) {
$linqCommand = clone $this;
$result = $linqCommand->reverse()->skip(0)->take(1)->select($expression);
if (count($result) > 0) {
* @param string $expression Expression which creates a resulting element
* @param mixed $defaultValue Default value to return if nothing is found
public function lastOrDefault ($expression = null, $defaultValue = null) {
$returnValue = $this->last($expression);
* @param string $expression Expression which creates a resulting element
public function single($expression = null) {
return $this->first($expression);
* @param string $expression Expression which creates a resulting element
* @param mixed $defaultValue Default value to return if nothing is found
public function singleOrDefault ($expression = null, $defaultValue = null) {
* @return PHPLinq_Initiator
public function join($name) {
* @param string $expression Expression representing join condition
* @return PHPLinq_ILinqProvider
public function on($expression) {
* @return int Element count
public function count() {
* @return mixed Sum of elements
return array_sum($this->_data); // $this->aggregate(0, '$s, $t => $s + $t');
* @return mixed Minimum of elements
* @return mixed Maximum of elements
* @return mixed Average of elements
* Example: Equivalent of count(): $this->aggregate(0, '$s, $t => $s + 1');
* @param string $expression Expression defining the aggregate
* @return mixed aggregate
public function aggregate($seed = 0, $expression) {
foreach ($this->_data as $value) {
$runningValue = $codeExpression->execute( array($runningValue, $value) );
* Get query callback (static for all PHPLinq_LinqToZendDb !)
* Function accepting a string to which query strings can be logged.
return self::$_queryCallback;
* Set query callback (static for all PHPLinq_LinqToZendDb !)
* Function accepting a string to which query strings can be logged.
self::$_queryCallback = $value;
|