数据源

数据源是模型和模型所代表的数据来源之间的联系。在很多情况下,数据是从关系型数据库中取得,比如 MySQL、PostgreSQL 或者 Microsoft SQL Server。CakePHP 的发布就带有若干针对数据库的数据源(请参看 lib/Cake/Model/Datasource/Database 中的类文件),为了便利,摘录如下:

  • Mysql
  • Postgres
  • Sqlite
  • Sqlserver

注解

你可以在 GitHub 上的 CakePHP 数据源代码库中找到更多社区贡献的数据源。

当在 app/Config/database.php 中指定数据库连接配置时,CakePHP 会以透明地使用相应的数据库数据源,用于所有的模型操作。所以,即使你并不知道数据源,你也已经一直在使用它们了。

所有上述数据源继承于一个基类 DboSource,它汇集了大多数关系型数据库通用的一些逻辑。如果你决定编写一个 RDBMS 数据源,你最好从这些中的一个(例如 MySQL 或者SQLite)开始。

当然,大多数人还是感兴趣于为外部数据来源编写数据源,比如远程 REST API,或者甚至是 LDAP 服务器。所以,这就是我们下面要介绍的。

数据源的基本 API

数据源能够,而且 应当 实现至少下面中的一个方法:createreadupdate 和/或 delete (方法的真正签名和实现细节在这里不重要,之后会述及)。你不必实现上述方法中不必要的部分 — 如果你需要只读的数据源,就没有理由实现createupdatedelete

对所有 CRUD 方法都要实现的方法:

  • describe($model)
  • listSources($data = null)
  • calculate($model, $func, $params)
  • 至少下列之一:
    • create(Model $model, $fields = null, $values = null)
    • read(Model $model, $queryData = array(), $recursive = null)
    • update(Model $model, $fields = null, $values = null, $conditions = null)
    • delete(Model $model, $id = null)
      也有可能(有时还挺有用)在数据源里定义 $_schema 类属性,而不是在模型中。

差不多就这些。把这个数据源和一个模型联系起来,你就可以象你通常那样使用Model::find()/save()/delete(),而调用这些方法的适当的数据以及/或者参数就会被传递给数据源,在那里你可以决定要实现任何你需要的特性(例如 Model::find 选项,比如对 'conditions' 的解析, 'limit' 或者甚至你自己的定制参数)。

一个例子

你想要编写自己的数据源的一个常见原因是当你要使用通常的Model::find()/save()/delete() 方法来访问第三方 API。让我们来编写一个数据源,来访问一个假想的基于 JSON 的 远程 API。我们会把它叫做 FarAwaySource,并把它放在 app/Model/Datasource/FarAwaySource.php 里:

  1. App::uses('HttpSocket', 'Network/Http');
  2.  
  3. class FarAwaySource extends DataSource {
  4.  
  5. /**
  6. * 数据源的描述,可省略
  7. */
  8. public $description = 'A far away datasource';
  9.  
  10. /**
  11. * 缺省配置选项。这些选项会在 ``app/Config/database.php`` 中定制化,并且会在
  12. * ``__construct()`` 中合并。
  13. */
  14. public $config = array(
  15. 'apiKey' => '',
  16. );
  17.  
  18. /**
  19. * 如果我们要 create() 或 update(),我们需要指定可用的字段。我们使用与在
  20. * CakeSchema 中一样的数组键,例如 fixtures 和 schema 升级。
  21. */
  22. protected $_schema = array(
  23. 'id' => array(
  24. 'type' => 'integer',
  25. 'null' => false,
  26. 'key' => 'primary',
  27. 'length' => 11,
  28. ),
  29. 'name' => array(
  30. 'type' => 'string',
  31. 'null' => true,
  32. 'length' => 255,
  33. ),
  34. 'message' => array(
  35. 'type' => 'text',
  36. 'null' => true,
  37. ),
  38. );
  39.  
  40. /**
  41. * 创建 HttpSocket,处理任何配置调整。
  42. */
  43. public function __construct($config) {
  44. parent::__construct($config);
  45. $this->Http = new HttpSocket();
  46. }
  47.  
  48. /**
  49. * 因为数据源通常连接到数据库,我们必须改变一些东西,才能使它适合没有数据库
  50. * 的情况。
  51. */
  52.  
  53. /**
  54. * listSources() 用于缓存。在定制数据源中你可能会要用自己的方式实现缓存。
  55. * 所以只要 ``return null`` 就行了。
  56. */
  57. public function listSources($data = null) {
  58. return null;
  59. }
  60.  
  61. /**
  62. * describe() 告诉模型你的 ``Model::save()`` 使用的 schema。
  63. *
  64. * 也许对你的每个模型都需要一个不同的 schema,但仍然使用一个数据源。如果是这
  65. * 样,那么在模型中设置一个 ``schema`` 属性,而从这里只返回
  66. * ``$model->schema``。
  67. */
  68. public function describe($model) {
  69. return $this->_schema;
  70. }
  71.  
  72. /**
  73. * calculate() 用来决定如何对记录进行计数,要让 ``update()`` 和 ``delete()``
  74. * 正常工作这是必须的。
  75. *
  76. * 在这里我们不计数,而是返回一个字符串传给 ``read()``,让它(指 ``read()``)
  77. * 去做真正的计数。最容易的方法是只需返回字符串 'COUNT',然后在 ``read()``
  78. * 里面检查 ``$data['fields'] === 'COUNT'``。
  79. */
  80. public function calculate(Model $model, $func, $params = array()) {
  81. return 'COUNT';
  82. }
  83.  
  84. /**
  85. * 实现 CRUD 中的 R。调用 ``Model::find()`` 时,会到达这里。
  86. */
  87. public function read(Model $model, $queryData = array(),
  88. $recursive = null) {
  89. /**
  90. * 这里我们按照上面 calculate() 方法的指示进行真正的计数。我们可以检
  91. * 查远程数据源,也可以用其它方法,来获得记录数。这里我们只是返回 1,
  92. * 这样 ``update()`` 和 ``delete()`` 就会认为记录存在。
  93. */
  94. if ($queryData['fields'] === 'COUNT') {
  95. return array(array(array('count' => 1)));
  96. }
  97. /**
  98. * 现在我们来获得远程数据,再将其解码并返回。
  99. */
  100. $queryData['conditions']['apiKey'] = $this->config['apiKey'];
  101. $json = $this->Http->get(
  102. 'http://example.com/api/list.json',
  103. $queryData['conditions']
  104. );
  105. $res = json_decode($json, true);
  106. if (is_null($res)) {
  107. $error = json_last_error();
  108. throw new CakeException($error);
  109. }
  110. return array($model->alias => $res);
  111. }
  112.  
  113. /**
  114. * 实现 CRUD 中的 C。调用 ``Model::save()`` 时不设置 $model->id,会到达这里。
  115. */
  116. public function create(Model $model, $fields = null, $values = null) {
  117. $data = array_combine($fields, $values);
  118. $data['apiKey'] = $this->config['apiKey'];
  119. $json = $this->Http->post('http://example.com/api/set.json', $data);
  120. $res = json_decode($json, true);
  121. if (is_null($res)) {
  122. $error = json_last_error();
  123. throw new CakeException($error);
  124. }
  125. return true;
  126. }
  127.  
  128. /**
  129. * 实现 CRUD 中的 U。调用 ``Model::save()`` 时设置了 $model->id,会到达这里。
  130. * 取决于远程数据源,你也许只需调用 ``$this->create()``。
  131. */
  132. public function update(Model $model, $fields = null, $values = null,
  133. $conditions = null) {
  134. return $this->create($model, $fields, $values);
  135. }
  136.  
  137. /**
  138. * 实现 CRUD 中的 D。调用 ``Model::delete()`` 时,会到达这里。
  139. */
  140. public function delete(Model $model, $id = null) {
  141. $json = $this->Http->get('http://example.com/api/remove.json', array(
  142. 'id' => $id[$model->alias . '.id'],
  143. 'apiKey' => $this->config['apiKey'],
  144. ));
  145. $res = json_decode($json, true);
  146. if (is_null($res)) {
  147. $error = json_last_error();
  148. throw new CakeException($error);
  149. }
  150. return true;
  151. }
  152.  
  153. }

接下去,我们就可以在 app/Config/database.php 文件中添加下面的代码来配置数据源:

  1. public $faraway = array(
  2. 'datasource' => 'FarAwaySource',
  3. 'apiKey' => '1234abcd',
  4. );

然后象这样在模型中使用数据库配置:

  1. class MyModel extends AppModel {
  2. public $useDbConfig = 'faraway';
  3. }

我们可以用熟悉的模型方法从远程数据源获取数据:

  1. // 从'某人(Some Person)'获得全部消息
  2. $messages = $this->MyModel->find('all', array(
  3. 'conditions' => array('name' => 'Some Person'),
  4. ));

小技巧

如果 read 方法的结果不是一个数字下标的数组,使用除 'all' 以外的其它find 类型会导致意想不到的结果。

同样我们可以保存一条新消息:

  1. $this->MyModel->save(array(
  2. 'name' => 'Some Person',
  3. 'message' => 'New Message',
  4. ));

更新上一条消息:

  1. $this->MyModel->id = 42;
  2. $this->MyModel->save(array(
  3. 'message' => 'Updated message',
  4. ));

以及删除消息:

  1. $this->MyModel->delete(42);

插件的数据源

你也可以把数据源封装在插件之中。

你只需把你的数据源文件放在Plugin/[YourPlugin]/Model/Datasource/[YourSource].php,然后用插件的语法引用它:

  1. public $faraway = array(
  2. 'datasource' => 'MyPlugin.FarAwaySource',
  3. 'apiKey' => 'abcd1234',
  4. );

连接 SQL Server

Sqlserver 数据源依赖于微软的名为 pdo_sqlsrv 的 PHP 扩展。该扩展未包含在 PHP 的基本安装中,必须单独安装。

而且必须安装 SQL Server Native Client,该扩展才能工作。由于 Native Client 只适用于 Windows,你无法在 Linux、Mac OS X 或者 FreeBSD 上安装。

所以,如果 Sqlserver 数据源报如下错误:

  1. Error: Database connection "Sqlserver" is missing, or could not be created.

请首先检查是否正确安装了 SQL Server PHP 扩展 pdo_sqlsrv 和 SQL Server NativeClient。