检索数据

如前所述,模型层的角色之一是从多种类型的存储中获取数据。CakePHP 的模型类拥有一些功能,能够帮助你搜索数据、把数据排序、分页以及过滤。模型中最常用的功能是Model::find() 方法。

find

find(string $type = 'first', array $params = array())

find 方法是所有检索数据方法中的多功能机器。$type 参数值可以是 'all''first''count''list''neighbors''threaded',或者任何自定义查询类型。切记 $type 是大小写敏感的。使用大写字母(如 All )将无法得到期望的结果。

$params 用于向各种类型的 find() 方法传递所有参数,默认有如下的键,都是可选的:

  1. array(
  2. 'conditions' => array('Model.field' => $thisValue), //查询条件数组
  3. 'recursive' => 1, //整型
  4. //字段名数组
  5. 'fields' => array('Model.field1', 'DISTINCT Model.field2'),
  6. //定义排序的字符串或者数组
  7. 'order' => array('Model.created', 'Model.field3 DESC'),
  8. 'group' => array('Model.field'), //用来分组(*GROUP BY*)的字段
  9. 'limit' => n, //整型
  10. 'page' => n, //整型
  11. 'offset' => n, //整型
  12. 'callbacks' => true //其他值可以是 false, 'before', 'after'
  13. )

也可以添加和使用额外的参数。一些 find() 类型和行为利用这个特性,你自己的模型方法也可以这么做。

如果你的 find() 操作无法匹配到任何记录,你会得到一个空数组。

find('first')

find('first', $params) 只返回一条记录。你可以把它用于任何只想得到一条记录的情况。以下是几个简单的(控制器代码)例子:

  1. public function some_function() {
  2. // ...
  3. $semiRandomArticle = $this->Article->find('first');
  4. $lastCreated = $this->Article->find('first', array(
  5. 'order' => array('Article.created' => 'desc')
  6. ));
  7. $specificallyThisOne = $this->Article->find('first', array(
  8. 'conditions' => array('Article.id' => 1)
  9. ));
  10. // ...
  11. }

在第一个示例中,没有向 find 方法传递任何参数,所以不使用任何条件和排序。find('first') 方法调用返回的格式如下:

  1. Array
  2. (
  3. [ModelName] => Array
  4. (
  5. [id] => 83
  6. [field1] => value1
  7. [field2] => value2
  8. [field3] => value3
  9. )
  10.  
  11. [AssociatedModelName] => Array
  12. (
  13. [id] => 1
  14. [field1] => value1
  15. [field2] => value2
  16. [field3] => value3
  17. )
  18. )

find('count')

find('count', $params) 返回一个整数值。以下是几个简单的(控制器代码)例子:

  1. public function some_function() {
  2. // ...
  3. $total = $this->Article->find('count');
  4. $pending = $this->Article->find('count', array(
  5. 'conditions' => array('Article.status' => 'pending')
  6. ));
  7. $authors = $this->Article->User->find('count');
  8. $publishedAuthors = $this->Article->find('count', array(
  9. 'fields' => 'DISTINCT Article.user_id',
  10. 'conditions' => array('Article.status !=' => 'pending')
  11. ));
  12. // ...
  13. }

注解

不要把数组形式的 fields 参数传递给 find('count') 方法。只需为DISTINCT count 指定字段(因为其它情况下,计数结果总是相同的,仅取决于条件)。

find('all')

find('all', $params) 返回一个数组,可能包含多个结果。实际上这是所有的find() 方法的变体、包括 paginate 方法使用的内在机制。下面是一些简单的(控制器代码)示例:

  1. public function some_function() {
  2. // ...
  3. $allArticles = $this->Article->find('all');
  4. $pending = $this->Article->find('all', array(
  5. 'conditions' => array('Article.status' => 'pending')
  6. ));
  7. $allAuthors = $this->Article->User->find('all');
  8. $allPublishedAuthors = $this->Article->User->find('all', array(
  9. 'conditions' => array('Article.status !=' => 'pending')
  10. ));
  11. // ...
  12. }

注解

上面的例子中,$allAuthors 包含 users 表的中的每个用户。因为没有传入任何条件,所以 find 方法将不会使用任何条件。

find('all') 方法调用返回的结果具有如下格式:

  1. Array
  2. (
  3. [0] => Array
  4. (
  5. [ModelName] => Array
  6. (
  7. [id] => 83
  8. [field1] => value1
  9. [field2] => value2
  10. [field3] => value3
  11. )
  12.  
  13. [AssociatedModelName] => Array
  14. (
  15. [id] => 1
  16. [field1] => value1
  17. [field2] => value2
  18. [field3] => value3
  19. )
  20.  
  21. )
  22. )

find('list')

find('list', $params) 返回一个索引数组,可用于任何需要列表的场合,比如在生成填充 select 输入元素的列表框。下面是一些简单的(控制器代码)示例:

  1. public function some_function() {
  2. // ...
  3. $allArticles = $this->Article->find('list');
  4. $pending = $this->Article->find('list', array(
  5. 'conditions' => array('Article.status' => 'pending')
  6. ));
  7. $allAuthors = $this->Article->User->find('list');
  8. $allPublishedAuthors = $this->Article->find('list', array(
  9. 'fields' => array('User.id', 'User.name'),
  10. 'conditions' => array('Article.status !=' => 'pending'),
  11. 'recursive' => 0
  12. ));
  13. // ...
  14. }

注解

上面的例子中,$allAuthors 将包含 users 表的所有用户。因为没有传入任何条件,所以 find 方法将不会使用任何条件。

调用 find('list') 的结果具有如下格式:

  1. Array
  2. (
  3. //[id] => 'displayValue',
  4. [1] => 'displayValue1',
  5. [2] => 'displayValue2',
  6. [4] => 'displayValue4',
  7. [5] => 'displayValue5',
  8. [6] => 'displayValue6',
  9. [3] => 'displayValue3',
  10. )

当调用 find('list') 时,传入的 fields 参数用于决定用什么(字段)作为数组的键和值、以及(可选的)用什么(字段)来把结果分组。默认情况下,模型的主键用作键,显示列(display field,可以用模型的 displayField 属性来配置)当作值。下面用更深入的示例来说明:

  1. public function some_function() {
  2. // ...
  3. $justusernames = $this->Article->User->find('list', array(
  4. 'fields' => array('User.username')
  5. ));
  6. $usernameMap = $this->Article->User->find('list', array(
  7. 'fields' => array('User.username', 'User.first_name')
  8. ));
  9. $usernameGroups = $this->Article->User->find('list', array(
  10. 'fields' => array('User.username', 'User.first_name', 'User.group')
  11. ));
  12. // ...
  13. }

使用上面的代码示例,结果变量类似下面这样:

  1. $justusernames = Array
  2. (
  3. //[id] => 'username',
  4. [213] => 'AD7six',
  5. [25] => '_psychic_',
  6. [1] => 'PHPNut',
  7. [2] => 'gwoo',
  8. [400] => 'jperras',
  9. )
  10.  
  11. $usernameMap = Array
  12. (
  13. //[username] => 'firstname',
  14. ['AD7six'] => 'Andy',
  15. ['_psychic_'] => 'John',
  16. ['PHPNut'] => 'Larry',
  17. ['gwoo'] => 'Gwoo',
  18. ['jperras'] => 'Joël',
  19. )
  20.  
  21. $usernameGroups = Array
  22. (
  23. ['User'] => Array
  24. (
  25. ['PHPNut'] => 'Larry',
  26. ['gwoo'] => 'Gwoo',
  27. )
  28.  
  29. ['Admin'] => Array
  30. (
  31. ['_psychic_'] => 'John',
  32. ['AD7six'] => 'Andy',
  33. ['jperras'] => 'Joël',
  34. )
  35.  
  36. )

find('threaded')

find('threaded', $params) 返回一个嵌套数组,适用于想使用模型数据的parent_id 字段来建立嵌套结果的情况。下面是几个简单的(控制器代码)示例:

  1. public function some_function() {
  2. // ...
  3. $allCategories = $this->Category->find('threaded');
  4. $comments = $this->Comment->find('threaded', array(
  5. 'conditions' => array('article_id' => 50)
  6. ));
  7. // ...
  8. }

小技巧

处理嵌套数据的更好的方法是使用 Tree 行为。

在上面的例子中,$allCategories 将包含一个代表整个分类结构的嵌套数组。调用find('threaded') 的结果具有如下格式:

  1. Array
  2. (
  3. [0] => Array
  4. (
  5. [ModelName] => Array
  6. (
  7. [id] => 83
  8. [parent_id] => null
  9. [field1] => value1
  10. [field2] => value2
  11. [field3] => value3
  12. )
  13.  
  14. [AssociatedModelName] => Array
  15. (
  16. [id] => 1
  17. [field1] => value1
  18. [field2] => value2
  19. [field3] => value3
  20. )
  21.  
  22. [children] => Array
  23. (
  24. [0] => Array
  25. (
  26. [ModelName] => Array
  27. (
  28. [id] => 42
  29. [parent_id] => 83
  30. [field1] => value1
  31. [field2] => value2
  32. [field3] => value3
  33. )
  34.  
  35. [AssociatedModelName] => Array
  36. (
  37. [id] => 2
  38. [field1] => value1
  39. [field2] => value2
  40. [field3] => value3
  41. )
  42.  
  43. [children] => Array
  44. (
  45. )
  46. )
  47. ...
  48. )
  49. )
  50. )

结果出现的顺序是可以改变的,因为它受处理的顺序的影响。例如,如果将'order' => 'name ASC' 在 params 参数中传递给 find('threaded') 方法,结果将按 name 的顺序排列。任何顺序都可以;此方法没有内置的要顶层的结果最先返回的要求。

警告

如果指定了 fields, 就总是要包含 id 和 parent_id (或者它们的当前别名):

  1. public function some_function() {
  2. $categories = $this->Category->find('threaded', array(
  3. 'fields' => array('id', 'name', 'parent_id')
  4. ));
  5. }

否则,返回的数组将不是预期的象上面那样的嵌套结构。

find('neighbors')

find('neighbors', $params) 方法执行的查找与 'first' 类似, 但返回查询的记录的前一条和后一条记录。下面是一个简单的(控制器代码)示例:

  1. public function some_function() {
  2. $neighbors = $this->Article->find(
  3. 'neighbors',
  4. array('field' => 'id', 'value' => 3)
  5. );
  6. }

在本例中可以看到,$params 数组的两个必要元素:field 和 value。如同其它形式的find 方法一样,其它元素仍然允许。(例如:如果模型采用 containable 行为,则可以在$params 参数中指定 'contain'。) 调用 find('neighbors') 返回的结果格式如下:

  1. Array
  2. (
  3. [prev] => Array
  4. (
  5. [ModelName] => Array
  6. (
  7. [id] => 2
  8. [field1] => value1
  9. [field2] => value2
  10. ...
  11. )
  12. [AssociatedModelName] => Array
  13. (
  14. [id] => 151
  15. [field1] => value1
  16. [field2] => value2
  17. ...
  18. )
  19. )
  20. [next] => Array
  21. (
  22. [ModelName] => Array
  23. (
  24. [id] => 4
  25. [field1] => value1
  26. [field2] => value2
  27. ...
  28. )
  29. [AssociatedModelName] => Array
  30. (
  31. [id] => 122
  32. [field1] => value1
  33. [field2] => value2
  34. ...
  35. )
  36. )
  37. )

注解

注意,结果总是只包含两个根元素:prev 和 next。此功能不遵循模型默认的recursive 变量。recursive 设置必须在每次调用中在参数中传入。

创建自定义查询类型

find 方法足够灵活,能够接收我们自定义的查找类型,这是通过在模型变量中定义自己的(查找)类型、并在模型类中实现特殊的函数来实现的。

模型的查找类型是 find() 选项的快捷方式。例如,如下两种 find 是等价的:

  1. $this->User->find('first');
  2. $this->User->find('all', array('limit' => 1));

以下是核心查找类型::

  • first
  • all
  • count
  • list
  • threaded
  • neighbors
    那么其它的类型呢?比如你要一个在数据库中查找所有已发表的文章的查找类型。首先要做的改动是把自定义类型添加到模型的 Model::$findMethods 变量中。
  1. class Article extends AppModel {
  2. public $findMethods = array('available' => true);
  3. }

基本上这只是让 CakePHP 接受值 available 作为 find 函数的第一个参数。接下来要实现 _findAvailable 函数。这是约定。如果想实现叫做 myFancySearch 的查找函数,那么要实现的方法就要命名为 _findMyFancySearch

  1. class Article extends AppModel {
  2. public $findMethods = array('available' => true);
  3.  
  4. protected function _findAvailable($state, $query, $results = array()) {
  5. if ($state === 'before') {
  6. $query['conditions']['Article.published'] = true;
  7. return $query;
  8. }
  9. return $results;
  10. }
  11. }

所有这些加在一起,就可以有下面这样的例子(控制器代码):

  1. class ArticlesController extends AppController {
  2.  
  3. // 将会查找所有已发表的文章,并以 created 列排序
  4. public function index() {
  5. $articles = $this->Article->find('available', array(
  6. 'order' => array('created' => 'desc')
  7. ));
  8. }
  9.  
  10. }

如上所示,这个特殊的方法 _find[Type] 接收三个参数。第一个参数为查询运行的状态,这可以是 beforeafter。采用这种方式是因为此函数只是一种回调函数,可以在查询结束前修改查询,或者在获取结果后对结果进行修改。

通常在该自定义查询方法中要检查的第一件事情是查询的状态。在 before 状态下,可以修改查询、绑定新的关联、应用更多的行为,以及解释任何在 find 方法的第二个参数中传入的特别的键。这个状态要求返回 $query 参数(修改过或没有任何改变)。

after 状态是理想的时机,来检测查询结果,注入新的数据,处理以便于以另外一种格式返回,或者对刚获取的数据做任何想做的事情。此状态需要你返回 $results 数组(修改过或没有任何改变)。

可以创建任意多想要的自定义查找,这也是复用应用程序各个模型代码的好方法。

还可以象下面这样使用'findType'选项,通过自定义的查找类型对结果进行分页:

  1. class ArticlesController extends AppController {
  2.  
  3. // 将会对所有已发布的文章进行分页
  4. public function index() {
  5. $this->paginate = array('findType' => 'available');
  6. $articles = $this->paginate();
  7. $this->set(compact('articles'));
  8. }
  9.  
  10. }

象上面这样设置控制器的 $this->paginate 属性,会导致查找的 type 变成available,而且也让你可以继续修改查找结果。

要简单地返回自定义查找类型的计数,象平常那样调用 count 方法,但在第二个参数中传入包含查找类型的数组。

  1. class ArticlesController extends AppController {
  2.  
  3. // 会得到所有已发表文章的数量(使用上面定义的 available 查找类型)
  4. public function index() {
  5. $count = $this->Article->find('count', array(
  6. 'type' => 'available'
  7. ));
  8. }
  9. }

如果你的分页页数损坏了,也许必须在 AppModel 中添加如下代码,就能修复分页计数了:

  1. class AppModel extends Model {
  2.  
  3. /**
  4. * 当 'fields' 键是数组时,从自定义查找的计数查询中删除 'fields' 键,因为它
  5. * 会彻底破坏对 Model::_findCount() 的调用
  6. *
  7. * @param string $state "before" 或 "after"
  8. * @param array $query
  9. * @param array $results
  10. * @return int 找到的记录数,或 false
  11. * @access protected
  12. * @see Model::find()
  13. */
  14. protected function _findCount($state, $query, $results = array()) {
  15. if ($state === 'before') {
  16. if (isset($query['type']) &&
  17. isset($this->findMethods[$query['type']])) {
  18. $query = $this->{
  19. '_find' . ucfirst($query['type'])
  20. }('before', $query);
  21. if (!empty($query['fields']) && is_array($query['fields'])) {
  22. if (!preg_match('/^count/i', current($query['fields']))) {
  23. unset($query['fields']);
  24. }
  25. }
  26. }
  27. }
  28. return parent::_findCount($state, $query, $results);
  29. }
  30.  
  31. }
  32. ?>

在 2.2 版更改.

不必再需要重载 _findCount 方法来修复不正确的计数结果。自定义查找的 'before'状态现在会用 $query['operation'] = 'count' 再次调用。返回的 $query 会用于_findCount()。如果必要,你可以通过检查 'operation' 键来区分,并返回不同的 $query:

  1. protected function _findAvailable($state, $query, $results = array()) {
  2. if ($state === 'before') {
  3. $query['conditions']['Article.published'] = true;
  4. if (!empty($query['operation']) && $query['operation'] === 'count') {
  5. return $query;
  6. }
  7. $query['joins'] = array(
  8. //需要的 joins 数组
  9. );
  10. return $query;
  11. }
  12. return $results;
  13. }

魔法查找类型

这些魔法函数可以用作搜寻表中特定字段的快捷方式。只要在这些函数末尾添加字段名(驼峰命名格式),并且提供字段的条件作为第一个参数。

findAllBy() 函数返回象 find('all') 方法返回的格式的结果,而 findBy() 返回与find('first') 相同的格式。

findAllBy

findAllBy<fieldName>(string $value, array $fields, array $order, int $limit, int $page, int $recursive)

findAllBy<x> 示例对应的SQL片段
$this->Product->findAllByOrderStatus('3');Product.order_status = 3
$this->Recipe->findAllByType('Cookie');Recipe.type = 'Cookie'
$this->User->findAllByLastName('Anderson');User.last_name = 'Anderson'
$this->Cake->findAllById(7);Cake.id = 7
$this->User->findAllByEmailOrUsername('jhon', 'jhon');User.email = 'jhon' OR User.username = 'jhon';
$this->User->findAllByUsernameAndPassword('jhon', '123');User.username = 'jhon' AND User.password = '123';
$this->User->findAllByLastName('psychic', array(), array('User.user_name => 'asc'));User.last_name = 'psychic' ORDER BY User.user_name ASC

返回结果数组的格式与 find('all') 的返回值格式一样。

自定义魔法查询(Finders)

自 2.8 版本开始,你可以使用任何带有魔法方法接口的自定义查询方法。例如,如果模型实现了 published 查询,你就可以通过魔法 findBy 方法来使用这些查询:

  1. $results = $this->Article->findPublishedByAuthorId(5);
  2.  
  3. // 等同于
  4. $this->Article->find('published', array(
  5. 'conditions' => array('Article.author_id' => 5)
  6. ));

2.8.0 新版功能: 2.8.0 版本增加了自定义魔法查询。

findBy

findBy<fieldName>(string $value);

findBy 魔法函数同样接受一些可选参数:

findBy<fieldName>(string $value[, mixed $fields[, mixed $order]]);

findBy<x> 示例对应的SQL片段
$this->Product->findByOrderStatus('3');Product.order_status = 3
$this->Recipe->findByType('Cookie');Recipe.type = 'Cookie'
$this->User->findByLastName('Anderson');User.last_name = 'Anderson';
$this->User->findByEmailOrUsername('jhon', 'jhon');User.email = 'jhon' OR User.username = 'jhon';
$this->User->findByUsernameAndPassword('jhon', '123');User.username = 'jhon' AND User.password = '123';
$this->Cake->findById(7);Cake.id = 7

findBy() 函数返回的结果类似于 find('first')

Model::query()

query(string $query)

你无法或者不想通过其它方模型法实现的 SQL 调用,可以使用模型的 query() 方法实现(虽然这很少有必要)。

如果使用该方法,请确保正确使用数据库驱动的 value() 方法转义(escape)所有参数。不转义参数会造成 SQL 注入漏洞。

注解

query() 不理会 $Model->cacheQueries,因为其功能与调用的模型根本毫无关系。为避免对调用查询的缓存,将第二个参数设置为 false,例如:query($query, $cachequeries = false)

query() 在查询中使用表名而不是模型名作为返回数据数组的键。例如:

  1. $this->Picture->query("SELECT * FROM pictures LIMIT 2;");

可能返回:

  1. Array
  2. (
  3. [0] => Array
  4. (
  5. [pictures] => Array
  6. (
  7. [id] => 1304
  8. [user_id] => 759
  9. )
  10. )
  11.  
  12. [1] => Array
  13. (
  14. [pictures] => Array
  15. (
  16. [id] => 1305
  17. [user_id] => 759
  18. )
  19. )
  20. )

要使用模型名作为数组键,并返回与 Find 方法一致的结果,可以将查询写成:

  1. $this->Picture->query("SELECT * FROM pictures AS Picture LIMIT 2;");

这会返回:

  1. Array
  2. (
  3. [0] => Array
  4. (
  5. [Picture] => Array
  6. (
  7. [id] => 1304
  8. [user_id] => 759
  9. )
  10. )
  11.  
  12. [1] => Array
  13. (
  14. [Picture] => Array
  15. (
  16. [id] => 1305
  17. [user_id] => 759
  18. )
  19. )
  20. )

注解

此语法及相应的数组结构仅对 MySQL 有效。在手动运行查询时,CakePHP 不提供任何对数据的抽象,所以真正的结果对不同的数据库将有所不同。

Model::field()

field(string $name, array $conditions = null, string $order = null)

返回符合 $conditions 条件、按照 $order 排序的第一条记录中以 $name 命名的单一列的值。如果没有传递条件,并且设置了模型的 id,则返回当前模型结果的那一列的值。如果没有匹配的记录,则返回 false。

  1. $this->Post->id = 22;
  2. echo $this->Post->field('name'); // 显示 id 为 22 的行的 name 列
  3.  
  4. // 显示最后创建的实例的 name 列
  5. echo $this->Post->field(
  6. 'name',
  7. array('created <' => date('Y-m-d H:i:s')),
  8. 'created DESC'
  9. );

Model::read()

read($fields, $id)

read() 方法用于设置当前模型数据(Model::$data) —— 例如在编辑过程中 —— 但是也可以在其他情况下从数据库中获取单条记录。

$fields 用来以字符串传递单个字段名、或者字段名称数组;如果为空,则读取所有字段。

$id 指定要读取的记录的 ID,默认会使用由 Model::$id 指定的当前选中记录。传递不同的值给 $id 参数就会使该条记录被选中。

read() 方法总是返回数组(即使仅请求一个字段名)。使用 field 方法来获取单个字段的值。

警告

由于 read 方法覆盖任何存储在模型的 dataid 属性中的数据,通常使用此功能是要非常小心,尤其在类似 beforeValidatebeforeSave 等模型回调函数中。通常而言,find 方法提供了比 read 方法更健壮和易用的API。

复杂的查找条件

大多数模型的 find 调用会涉及用这种或者那种方式传入一些查询条件。通常,CakePHP 更倾向于使用数组来表示在 SQL 查询中要放在 WHERE 子句后面的任何条件。

使用数组更清晰易读,而且很易于构建查询。这种语法也把查询元素(字段、数值、运算符,等等)分成离散的可操作的部分。这让 CakePHP 能够生成尽可能高效的查询,保证正确的SQL 语法,并且正确地转义(escape)查询的每一个部分。使用数组语法也让 CakePHP 能够保护你的查询免受 SQL 注入的攻击。

警告

CakePHP 只转义(escape)数组的值。你 永远不 应当把用户数据放入键中。这么做会让你容易遭受 SQL 注入的攻击。

最基本的基于数组的查询类似于这样:

  1. $conditions = array("Post.title" => "This is a post", "Post.author_id" => 1);
  2. // 使用模型的用法示例:
  3. $this->Post->find('first', array('conditions' => $conditions));

这里的结构是相当不言自明的:这将查找标题(title)等于"This is a post"、作者(author) id 等于 1 的文章。注意,我们可以只用"title"作为字段名称,但是在构建查询时,好的做法是总是指定模型名称,因为这使代码更明确,并且有助于预防将来的冲突,如果你决定改变数据结构的话。

其它的匹配类型呢?同样简单。比如说我们要查找所有标题(title)不是"This is apost"的文章:

  1. array("Post.title !=" => "This is a post")

注意,跟在字段名称之后的'!='。CakePHP 能解析任何合法的 SQL 比较操作符,包括使用LIKEBETWEEN 或者 REGEX 的匹配表达式,只要你用空格分隔开列名和操作符。这里唯一的例外是 IN (…) 这样的匹配条件。比如说要查找标题(title)列为给定的一组值之一的文章:

  1. array(
  2. "Post.title" => array("First post", "Second post", "Third post")
  3. )

要使用 NOT IN(…) 匹配条件来查找标题(title)不在给定的一组值之内的文章,那么这样做:

  1. array(
  2. "NOT" => array(
  3. "Post.title" => array("First post", "Second post", "Third post")
  4. )
  5. )

要向条件中添加更多的过滤,简单到只要给数组添加更多的键/值对:

  1. array (
  2. "Post.title" => array("First post", "Second post", "Third post"),
  3. "Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
  4. )

还可以创建对比数据库中两个字段的查询:

  1. array("Post.created = Post.modified")

上面的例子将返回创建日期和编辑日期相同的文章(即,返回从来没被编辑过的文章)。

记住,如果你发现不能用这种方法生成 WHERE 子句(例如,逻辑运算),你总是可以使用字符串来指定,比如:

  1. array(
  2. 'Model.field & 8 = 1',
  3. // 其它条件照旧
  4. )

默认情况下,CakePHP 使用逻辑 AND 来连接多个条件。这意味着,上面的代码片段仅匹配近两星期内创建的、并且标题(title)符合给定的一组标题之一的文章。不过,我们也可以同样容易地查找符合任一条件的文章:

  1. array("OR" => array(
  2. "Post.title" => array("First post", "Second post", "Third post"),
  3. "Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
  4. ))

CakePHP 接受所有合法的 SQL 逻辑运算符,包括 ANDORNOTXOR,等等,而且大小写都可以,随你选择。这些条件还可以无限制嵌套。比如说Posts 和 Authors 模型之间有 belongsTo 关系,而且要查找所有包含特定关键词("magic")或者在两星期内创建的、但仅由用户 Bob 发布的文章:

  1. array(
  2. "Author.name" => "Bob",
  3. "OR" => array(
  4. "Post.title LIKE" => "%magic%",
  5. "Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
  6. )
  7. )

如果需要在同一个字段上设置多个条件,比如想要执行一个带有多个 LIKE 条件的搜索,可以使用类似如下的条件:

  1. array('OR' => array(
  2. array('Post.title LIKE' => '%one%'),
  3. array('Post.title LIKE' => '%two%')
  4. ))

也可以使用通配符操作符 ILIKERLIKE (RLIKE 自 2.6 版本起)。

CakePHP 还能检查 null 字段。在本例中,查询将返回标题(title)不为 null 的记录:

  1. array("NOT" => array(
  2. "Post.title" => null
  3. )
  4. )

要处理 BETWEEN 查询,可以使用下面的方法:

  1. array('Post.read_count BETWEEN ? AND ?' => array(1,10))

注解

CakePHP 会根据数据库中字段的类型来决定是否为数字值加上引号。

如何处理 GROUP BY?:

  1. array(
  2. 'fields' => array(
  3. 'Product.type',
  4. 'MIN(Product.price) as price'
  5. ),
  6. 'group' => 'Product.type'
  7. )

这个查询返回的数据具有如下格式:

  1. Array
  2. (
  3. [0] => Array
  4. (
  5. [Product] => Array
  6. (
  7. [type] => Clothing
  8. )
  9. [0] => Array
  10. (
  11. [price] => 32
  12. )
  13. )
  14. [1] => Array
  15. ...

下面是使用 DISTINCT 查询的简单示例。可以按类似的方式使用其它操作符,比如MIN()MAX(),等等:

  1. array(
  2. 'fields' => array('DISTINCT (User.name) AS my_column_name'),
  3. 'order' = >array('User.id DESC')
  4. )

通过嵌套多个条件数组,可以构建非常复杂的条件:

  1. array(
  2. 'OR' => array(
  3. array('Company.name' => 'Future Holdings'),
  4. array('Company.city' => 'CA')
  5. ),
  6. 'AND' => array(
  7. array(
  8. 'OR' => array(
  9. array('Company.status' => 'active'),
  10. 'NOT' => array(
  11. array('Company.status' => array('inactive', 'suspended'))
  12. )
  13. )
  14. )
  15. )
  16. )

这生成如下 SQL:

  1. SELECT `Company`.`id`, `Company`.`name`,
  2. `Company`.`description`, `Company`.`location`,
  3. `Company`.`created`, `Company`.`status`, `Company`.`size`
  4.  
  5. FROM
  6. `companies` AS `Company`
  7. WHERE
  8. ((`Company`.`name` = 'Future Holdings')
  9. OR
  10. (`Company`.`city` = 'CA'))
  11. AND
  12. ((`Company`.`status` = 'active')
  13. OR (NOT (`Company`.`status` IN ('inactive', 'suspended'))))

子查询

本例中,想象我们有一个带有"id"、"name"和"status"列的"users"表。status 列可以是"A"、"B" 或者 "C"。我们要使用子查询获取所有 status 列不是"B"的用户(users)。

为了达到此目的,我们将获取模型的数据源,让它构建查询,就像我们调用 find() 方法那样,但是只让它返回 SQL 语句。然后,我们生成表达式,并将其添加到条件数组中:

  1. $conditionsSubQuery['"User2"."status"'] = 'B';
  2.  
  3. $db = $this->User->getDataSource();
  4. $subQuery = $db->buildStatement(
  5. array(
  6. 'fields' => array('"User2"."id"'),
  7. 'table' => $db->fullTableName($this->User),
  8. 'alias' => 'User2',
  9. 'limit' => null,
  10. 'offset' => null,
  11. 'joins' => array(),
  12. 'conditions' => $conditionsSubQuery,
  13. 'order' => null,
  14. 'group' => null
  15. ),
  16. $this->User
  17. );
  18. $subQuery = ' "User"."id" NOT IN (' . $subQuery . ') ';
  19. $subQueryExpression = $db->expression($subQuery);
  20.  
  21. $conditions[] = $subQueryExpression;
  22.  
  23. $this->User->find('all', compact('conditions'));

这会生成如下 SQL:

  1. SELECT
  2. "User"."id" AS "User__id",
  3. "User"."name" AS "User__name",
  4. "User"."status" AS "User__status"
  5. FROM
  6. "users" AS "User"
  7. WHERE
  8. "User"."id" NOT IN (
  9. SELECT
  10. "User2"."id"
  11. FROM
  12. "users" AS "User2"
  13. WHERE
  14. "User2"."status" = 'B'
  15. )

另外,如果需要象上面那样传递原生 SQL 作为部分查询,带有原生 SQL 的数据源表达式 在 find 查询的任意部分都可以使用。

预处理语句

如果还需要对查询有更多控制,可以使用预处理语句。它让你可以直接与数据库驱动对话,并且传递任何需要的自定义查询:

  1. $db = $this->getDataSource();
  2. $db->fetchAll(
  3. 'SELECT * from users where username = ? AND password = ?',
  4. array('jhon', '12345')
  5. );
  6. $db->fetchAll(
  7. 'SELECT * from users where username = :username AND password = :password',
  8. array('username' => 'jhon','password' => '12345')
  9. );