diff --git a/lib/Cake/Model/Datasource/Database/Expression/QueryExpression.php b/lib/Cake/Model/Datasource/Database/Expression/QueryExpression.php index 08c9df2d8a5..0eae916ef51 100644 --- a/lib/Cake/Model/Datasource/Database/Expression/QueryExpression.php +++ b/lib/Cake/Model/Datasource/Database/Expression/QueryExpression.php @@ -13,6 +13,11 @@ class QueryExpression implements Countable { protected $_identifier; + protected $_bindingsCount = 0; + + protected $_replaceArrayParams = false; + + public function __construct($conditions = [], $types = [], $conjunction = 'AND') { $this->_conjunction = strtoupper($conjunction); $this->_identifier = substr(spl_object_hash($this), 7, 9); @@ -89,6 +94,10 @@ public function in($field, $values, $type = null) { return $this->add([$field . ' IN' => $values], $type ? [$field => $type] : []); } + public function notIn($field, $values, $type = null) { + return $this->add([$field . ' NOT IN' => $values], $type ? [$field => $type] : []); + } + /** * Associates a query placeholder to a value and a type for next execution * @@ -101,7 +110,7 @@ public function in($field, $values, $type = null) { */ public function bind($token, $value, $type) { $param = $token; - $number = count($this->_bindings); + $number = $this->_bindingsCount++; if (is_numeric($token)) { $param = '?'; @@ -109,7 +118,12 @@ public function bind($token, $value, $type) { $param = sprintf(':c%s%s', $this->_identifier, $number); } - $this->_bindings[$number] = compact('value', 'type') + [ + if (strpos($type, '[]') !== false) { + $param = sprintf(':array%d', $number); + $type = str_replace('[]', '', $type); + } + + $this->_bindings[$number] = compact('value', 'type', 'token') + [ 'placeholder' => substr($param, 1) ]; return $param; @@ -134,6 +148,9 @@ public function __toString() { } public function sql() { + if ($this->_replaceArrayParams) { + $this->_replaceArrays(); + } $conjunction = $this->_conjunction; return '(' . implode(" $conjunction ", $this->_conditions) . ')'; } @@ -188,7 +205,34 @@ protected function _parseCondition($field, $value, $types) { } $type = isset($types[$expression]) ? $types[$expression] : null; - return sprintf('%s %s %s', $expression, $operator, $this->bind($field, $value, $type)); + $template = '%s %s %s'; + + if (in_array(strtolower(trim($operator)), ['in', 'not in'])) { + $type = $type ?: 'string'; + $type .= strpos($type, '[]') === false ? '[]' : null; + $template = '%s %s (%s)'; + $this->_replaceArrayParams = true; + } + + return sprintf($template, $expression, $operator, $this->bind($field, $value, $type)); + } + + protected function _replaceArrays() { + foreach ($this->_conditions as $k => $condition) { + if (!is_string($condition)) { + continue; + } + $condition = preg_replace_callback('/(:array(\d+))/', function($match) { + $params = []; + $binding = $this->_bindings[$match[2]]; + foreach ($this->_bindings[$match[2]]['value'] as $value) { + $params[] = $this->bind($binding['token'], $value, $binding['type']); + } + unset($this->_bindings[$match[2]]); + return implode(', ', $params); + }, $condition); + $this->_conditions[$k] = $condition; + } } } diff --git a/lib/Cake/Test/TestCase/Model/Datasource/Database/QueryTest.php b/lib/Cake/Test/TestCase/Model/Datasource/Database/QueryTest.php index c27d496925b..b618fba46d7 100644 --- a/lib/Cake/Test/TestCase/Model/Datasource/Database/QueryTest.php +++ b/lib/Cake/Test/TestCase/Model/Datasource/Database/QueryTest.php @@ -692,6 +692,47 @@ public function testSelectWhereOperatorMethods() { ->where(function($exp) { return $exp->isNotNull('visible'); }) ->execute(); $this->assertCount(2, $result); + + $query = new Query($this->connection); + $result = $query + ->select(['id']) + ->from('dates') + ->where(function($exp) { + return $exp->in('visible', ['Y', 'N']); + }) + ->execute(); + $this->assertCount(2, $result); + + $query = new Query($this->connection); + $result = $query + ->select(['id']) + ->from('dates') + ->where(function($exp) { + return $exp->in( + 'posted', + [new \DateTime('2012-12-21 12:00'), new \DateTime('2012-12-22 12:00')], + 'datetime' + ); + }) + ->execute(); + $this->assertCount(2, $result); + $this->assertEquals(['id' => 1], $result->fetch('assoc')); + $this->assertEquals(['id' => 2], $result->fetch('assoc')); + + $query = new Query($this->connection); + $result = $query + ->select(['id']) + ->from('dates') + ->where(function($exp) { + return $exp->notIn( + 'posted', + [new \DateTime('2012-12-21 12:00'), new \DateTime('2012-12-22 12:00')], + 'datetime' + ); + }) + ->execute(); + $this->assertCount(1, $result); + $this->assertEquals(['id' => 3], $result->fetch('assoc')); } }