表达式
Between 表达式是开箱即用的,但是它们只支持第一种情况,无需额外的修改:
$Query = $Table
->find()
->where(function($exp) {
return $exp->between('start_date', '2014-01-01', '2014-12-32', 'date');
});
如果您想通过 Between 方法处理第二种情况,那么您必须将所有值作为表达式传递,这很容易出错,因为在这种情况下它们不会受到转义/参数绑定的影响,您' d 必须自己执行此操作(这是绝对不推荐的!请参阅中的安全说明的手册PDO::quote()),大致如下:
use Cake\Database\Expression\IdentifierExpression;
use Cake\Database\Expression\QueryExpression;
use Cake\ORM\Query;
// ...
$Query = $Table
->find()
->where(function(QueryExpression $exp, Query $query) {
return $exp->between(
$query->newExpr(
$query->connection()->driver()->quote(
'2014-03-31',
\PDO::PARAM_STR
)
),
new IdentifierExpression('start_date'),
new IdentifierExpression('end_date')
);
});
对于 CakePHP 附带的所有 SQL 方言都支持的基本 SQL 表达式来说,这可能会感觉有点不方便,因此您可能有理由使用带有值 Bindig 的原始 SQL 片段。
但应该注意的是,当涉及到跨方言支持时,表达式通常是更好的选择,因为它们可以(或多或少)在编译时轻松转换,请参阅实现SqlDialectTrait::_expressionTranslators()
。此外,表达式通常支持自动标识符引用。
值绑定
通过手动值绑定,您几乎可以创建任何您喜欢的东西。但应该注意的是,只要有可能,您就应该使用表达式,因为它们更容易移植,对于很多表达式来说,这种情况已经是开箱即用的。
$Query = $Table
->find()
->where([
'start_date BETWEEN :start AND :end'
])
->bind(':start', '2014-01-01', 'date')
->bind(':end', '2014-12-31', 'date');
这样第二种情况也可以很容易地解决,例如:
$Query = $Table
->find()
->where([
':date BETWEEN start_date AND end_date'
])
->bind(':date', '2014-03-31', 'date');
两者的混合(最安全且最兼容的方法)
也可以混合两者,即使用一个利用自定义绑定的表达式,大致如下:
use Cake\Database\Expression\IdentifierExpression;
use Cake\Database\Expression\QueryExpression;
use Cake\ORM\Query;
// ...
$Query = $Table
->find()
->where(function(QueryExpression $exp, Query $query) {
return $exp->between(
$query->newExpr(':date'),
new IdentifierExpression('start_date'),
new IdentifierExpression('end_date')
);
})
->bind(':date', '2014-03-31', 'date');
这样您就可以使用可能可移植的表达式来处理第二种情况,而不必担心手动引用/转义输入数据和标识符。
使用数组语法进行常规比较
说了这么多,最后BETWEEN
与使用两个单独的简单条件相同,如下所示:
$Query = $Table
->find()
->where([
'start_date >=' => '2014-01-01',
'start_date <=' => '2014-12-32',
]);
$Query = $Table
->find()
->where([
'start_date >=' => '2014-03-31',
'end_date <=' => '2014-03-31',
]);
但不要生气,如果您一直读到这里,至少您了解了查询生成器的来龙去脉。
See also
- Cookbook > 数据库访问和 ORM > 查询生成器 > 高级条件
- API > \Cake\Database\Query::bind()
Currently there seems to be only two options. The core now supports this out of the box, the following is just kept for reference.
值绑定(通过数据库查询生成器)
现在 ORM 查询构建器(Cake\ORM\Query
),例如调用时正在检索的那个find()
在表对象上,不支持值绑定
https://github.com/cakephp/cakephp/issues/4926
因此,为了能够使用绑定,您必须使用底层数据库查询构建器(Cake\Database\Query
),例如可以通过以下方式检索Connection::newQuery().
这是一个例子:
$conn = ConnectionManager::get('default');
$Query = $conn->newQuery();
$Query
->select('*')
->from('table_name')
->where([
'start_date BETWEEN :start AND :end'
])
->bind(':start', new \DateTime('2014-01-01'), 'date')
->bind(':end', new \DateTime('2014-12-31'), 'date');
debug($Query->execute()->fetchAll());
这将导致类似于此的查询
SELECT
*
FROM
table_name
WHERE
start_date BETWEEN '2014-01-01' AND '2014-12-31'
自定义表达式类
另一种选择是定制表达生成适当的 SQL 片段的类。这是一个例子。
列名应包装到标识符表达式对象中,以便自动引用(如果启用了自动引用),key > value 数组语法用于绑定值,其中数组键是实际值,数组值是数据类型。
请注意,直接传递用户输入的列名并不安全,因为它们不会被转义!使用白名单或类似的内容来确保列名称可以安全使用!
值之间的字段
use App\Database\Expression\BetweenComparison;
use Cake\Database\Expression\IdentifierExpression;
// ...
$between = new BetweenComparison(
new IdentifierExpression('created'),
['2014-01-01' => 'date'],
['2014-12-31' => 'date']
);
$TableName = TableRegistry::get('TableName');
$Query = $TableName
->find()
->where($between);
debug($Query->execute()->fetchAll());
这将生成类似于上面的查询。
字段之间的值
use App\Database\Expression\BetweenComparison;
use Cake\Database\Expression\IdentifierExpression;
// ...
$between = new BetweenComparison(
['2014-03-31' => 'date'],
new IdentifierExpression('start_date'),
new IdentifierExpression('end_date')
);
$TableName = TableRegistry::get('TableName');
$Query = $TableName
->find()
->where($between);
debug($Query->execute()->fetchAll());
另一方面,这将导致类似于此的查询
SELECT
*
FROM
table_name
WHERE
'2014-03-31' BETWEEN start_date AND end_date
表达式类
namespace App\Database\Expression;
use Cake\Database\ExpressionInterface;
use Cake\Database\ValueBinder;
class BetweenComparison implements ExpressionInterface {
protected $_field;
protected $_valueA;
protected $_valueB;
public function __construct($field, $valueA, $valueB) {
$this->_field = $field;
$this->_valueA = $valueA;
$this->_valueB = $valueB;
}
public function sql(ValueBinder $generator) {
$field = $this->_compilePart($this->_field, $generator);
$valueA = $this->_compilePart($this->_valueA, $generator);
$valueB = $this->_compilePart($this->_valueB, $generator);
return sprintf('%s BETWEEN %s AND %s', $field, $valueA, $valueB);
}
public function traverse(callable $callable) {
$this->_traversePart($this->_field, $callable);
$this->_traversePart($this->_valueA, $callable);
$this->_traversePart($this->_valueB, $callable);
}
protected function _bindValue($value, $generator, $type) {
$placeholder = $generator->placeholder('c');
$generator->bind($placeholder, $value, $type);
return $placeholder;
}
protected function _compilePart($value, $generator) {
if ($value instanceof ExpressionInterface) {
return $value->sql($generator);
} else if(is_array($value)) {
return $this->_bindValue(key($value), $generator, current($value));
}
return $value;
}
protected function _traversePart($value, callable $callable) {
if ($value instanceof ExpressionInterface) {
$callable($value);
$value->traverse($callable);
}
}
}