Zend_Cloud_DocumentService 为所有主要的文档数据库抽象了接口——不管是在云计算还是在本地部署——所以开发者通过一个 API 就可以访问它们的通常功能。换句话来说,一个应用程序可以使用这些数据库和服务,而不必关心这些应用程序是如何被部署的。在部署的时候,可以通过修改配置来选择数据源。
文档数据库和服务在应用程序的开发阶段变得越来越普遍。这些数据源不同于传统的关系数据源,它们为了性能、可扩展性以及灵活性,尽量避免复杂的关系。文档基础的服务例子有 Amazon SimpleDB 和 Azure Table Storage。
Simple Cloud API 为提供商指特性提供了一些灵活的机制,通过在每一个方法签名中使用一个 $options 数组。一些适配器要求特定的选项也必须被加入到 $options 数组中。从一个配置文件中检索这些选项从而维护所有服务和数据库的兼容性,这是一个很好的实践;不被识别的选项被会简单的被忽略,这使得根据不同环境使用不同的服务变为可能。
如果更多的提供商需求被要求,开发者应该继承特定的 Zend_Cloud_DocumentService 适配器,来为这些特性增加支持。在这种方式中,可以在 Simple Cloud 适配器的子类中,通过对 Simple Cloud API 扩展的引用,从而调用提供商指定的特性。
Zend_Cloud_DocumentService_Adapter 接口定义了每个具体的文档服务适配器需要应用的方法。以下是 Simple Cloud API 的内置适配器:
为了初始化一个文档服务适配器,使用 Zend_Cloud_DocumentService_Factory::getAdapter() 这个静态方法,它可以接受一个配置数组或者一个 Zend_Config 对象。document_adapter 键应该指定具体的适配器类,通过类名。适配器特定的键同时也可以配置参数传递。
$adapterClass = 'Zend_Cloud_DocumentService_Adapter_SimpleDb'; $documents = Zend_Cloud_DocumentService_Factory::getAdapter(array( Zend_Cloud_DocumentService_Factory::DOCUMENT_ADAPTER_KEY => $adapterClass, Zend_Cloud_DocumentService_Adapter_SimpleDb::AWS_ACCESS_KEY => $amazonKey, Zend_Cloud_DocumentService_Adapter_SimpleDb::AWS_SECRET_KEY => $amazonSecret ));
选项键 | 描述 | 用于 | 是否必须 | 默认值 |
---|---|---|---|---|
document_class | 用来表示返回文档的类。这个类必须继承 Zend_Cloud_DocumentService_Document 从而确保兼容所有的文档服务。对于返回一个文档或者文档集合的所有方法,将会使用到这个类。 |
构造函数 | 否 | Zend_Cloud_Document_Service_Document |
documentset_class | 用于表示文档集合的类。默认是 Zend_Cloud_DocumentService_DocumentSet 。 典型的,这个类的对象将会由 listDocuments() 和 query() 返回。任何为这个配置值提供的类,必须继承 Zend_Cloud_DocumentService_DocumentSet 。 |
构造函数 | 否 | Zend_Cloud_DocumentService_DocumentSet |
选项键 | 描述 | 用于 | 是否必须 | 默认值 |
---|---|---|---|---|
query_class | 用来为这个文档服务创建和收集查询的类 select() 将会创建这个类名的对象, listDocuments() 也可以达到这个效果。 |
构造函数 | 否 | Zend_Cloud_DocumentService_Adapter_SimpleDb_Query |
aws_accesskey | 你的 Amazon AWS 访问钥匙 | 构造函数 | 是 | 没有 |
aws_secretkey | 你的 Amazon AWS 密匙 | 构造函数 | 是 | 没有 |
http_adapter | 在所有访问操作中将要使用的 HTTP 适配器 | 构造函数 | 否 | Zend_Http_Client_Adapter_Socket |
merge | 如果是一个 true,则所有的属性值将会被合并。你也可以 指定一个键对数组,这些键是将要被合并的属性的键名,同时值指示是否需要合并;一个 true 将会合并给定的键。在这个数组中任何没有被指定的属性将会被替换。 |
updateDocument() | 否 | True |
return_documents | 如果是一个 true, query() 将返回一个 Zend_Cloud_DocumentService_DocumentSet 对象 这个对象包括 Zend_Cloud_DocumentService_Document 对象(默认情况下);否则它将返回一个数组的数组。 |
query() | 否 | True |
选项键 | 描述 | 用于 | 是否必须 | 默认值 |
---|---|---|---|---|
query_class | 用来为这个文档服务创建和收信查询语句的类 select() 将会创建这个类名的对象, listDocuments() 也有相同的作用。 |
构造函数 | 否 | Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query |
default_partition_key | 如果在文档标志符(document identifier)没有指定的时候,将要被使用的默认的分区键。 Windows Azure 要求一个双层文档 ID,由一个 PartitionKey 和一个 RowKey 组成。PartitionKey 将典型的是一个在你的数据库或者一个集合中通用的,而 RowKey 的值将会变化。由此,这个设置将允许你来指定所有的文档将要使用的默认的 PartitionKey。 如果没有指定,适配器将默认使用集合的名字作为 PartitionKey |
构造函数, setDefaultPartitionKey() | 否 | 文档所属的任何集合的名字 |
storage_accountname | Windows Azure 账号名 | 构造函数 | 是 | 没有 |
storage_accountkey | Windows Azure 账号钥匙/td> | 构造函数 | 是 | 没有 |
storage_host | Windows Azure 访问主机,默认是 table.core.windows.net |
构造函数 | 否 | table.core.windows.net |
storage_proxy_host | 代理主机名 | 构造函数 | 否 | 没有 |
storage_proxy_port | 代理端口 | 构造函数 | 否 | 8080 |
storage_proxy_credentials | 代理证书 | 构造函数 | 否 | 没有 |
HTTP 适配器 | 用于所以访问操作的 HTTP 适配器 | 构造函数 | 否 | 没有 |
verify_etag | 确认目标文档上的 ETag,同时只有当 ETag 和期望值相匹配的时候才执行操作 |
updateDocument() , replaceDocument() , deleteDocument() | 否 | False |
每一个基于文档的服务和数据库在它们的 API 内使用它们自己的专业术语和结构。SimpleCloud API 把广大提供商品之间共享的一些普遍概念和操作识别和抽象出来。
文档储存由大量的集合(collections)组成,这些集合是与 SQL 数据库中的数据库表相类似的逻辑储存单位。集合包括文档(documents),文档事实是一套键->值对,同时带有一些针对储存引擎的元数据(metadata),文档由一个独一无二的 document ID 区分开来。
每一个文档拥有它自己的结构(字段组合),而无须与其它任何文档结构相匹配,甚至是同一个集合内的文档(也无须结构匹配)。事实上,你可以在文档被创建以后,再修改它的结构。
可以通过 ID 或者查询一个集合来检索文档。
文档由 Zend_Cloud_DocumentService_Document 类来代表。注意这个文档类不对提供的 ID 和数据进行验证,也不强制必须和每一个适配器的要求相兼容。
可以通过把键名作为对象的属性和作为数组的元素来对文档字段进行访问。
Zend_Cloud_DocumentService_Document 基本接口如下:
/** * ArrayAccess 允许通过数组键来访问字段: * $doc['fieldname'] * * IteratorAggregate 允许反复访问全部的字段: * foreach ($document as $field => $value) { * echo "$field: $value/n"; * } * * Countable 统计全部的字段: * count($document) */ class Zend_Cloud_DocumentService_Document implements ArrayAccess, IteratorAggregate, Countable { const KEY_FIELD = '_id'; /** * $fields 可以是一个数组或者一个应用了 ArrayAccess 类的对象。 * 如果不提供 $id,它会查找一个与 KEY_FIELD 相匹配的字段, * 作为 ID。 */ public function __construct($fields, $id = null); public function setId($id); public function getId(); public function getFields(); public function getField($name); public function setField($name, $value); /** * 这些允许重载,所以你可以访问字段,就如同他们是 * 文档的内在属性 */ public function __get($name); public function __set($name, $value); /** * 另外,你可以如同通过 getters 和 setters 一样访问字段 * $document->setFoo($value); // set "Foo" field to value * $value = $document->getFoo(); // get "Foo" field value public function __call($name, $args); }
注意:Windows Azure 文档标识符
Windos Azure 技术上要求绑定两个字段来区别文档:PartitionKey 和 RowKey,由此,键名将被 array(PartitionKey, RowKey) 这种结构完全认证——这使得它们无法移植。在大多数情况下,PartitionKey 对于一个单独的集合的文档是不会有变化的——而且极可能在你整个数据库表中都不会有变化。因此,DocumentService 为指定键提供了几个选择:
- 数组键总是如预期的发生作用
- 如果提供了一个字符串键:
- 如果给构造函数提供一个 default_partition_key 值,或者传递到 setDefaultPartitionKey() 方法中,那个值将会被当作 PartitionKey 使用
- 否则,你正在操作的集合名字将会被使用
如果你想使你的应用程序最大程度的可移植,方便的做法是使用字符串键。只不过要注意你的记录将会包括一些额外的字段来表示你不应该把你的数据移植到其它服务的键(PartitionKey,RowKey 和之前没有讨论过的时间戳 Timestamp)
$document = new Zend_Cloud_DocumentServiceDocument(array( 'key1' => 'value1', 'key2' => 123, 'key3' => 'thurdvalue', ), "DocumentId"); $document->otherkey = 'some more data'; echo "key 1: " . $document->key1 . "/n"; // 对象表示法 echo "key 2: " . $document['key2'] . "/n"; // 数组表示法
$document = $documents->fetchDocument("mydata", $id); echo "Document ID: " . $document->getID() . "/n"; foreach ($document->getFields() as $key => $value) { echo "Field $key is $value/n"; }
如果在文档服务中发生一些错误,Zend_Cloud_DocumentService_Exception 被抛出。如果异常是因为下层的服务驱动引发的,你可以使用 getClientException() 方法来检索原始异常。
由于不同的云提供商应用不同的服务,一些驱动并不应用特定的特性。在这种情况下,Zend_Cloud_OperationNotAvailableException 被抛出。
使用 createCollection() 来创建一个新的集合。
$documents->createCollection("mydata");
如果你调用 createCollection() 的时候,使用一个已经存在的集合名字作为参数,服务将不会做任何事情,并且不会动现存的集合。
通过调用 deleteCollection() 方法来删除一个集合。
$documents->deleteCollection("mydata");
删除一个集合,将会删除这个集合下的所有文档。
注意:对于一些服务而言,删除一个集合需要相当多的时间。在集合和它的文档没有被彻底删除之前,你不能重新创建一个名字相同的集合。
删除一个不存在的集合将不会有影响。
可以用 listCollection() 返回一个现存的集合清单。这个方法返回一个数组,这个数组包括了你在创建这个适配器时指定的账户下的所有集合的名字。
$list = $documents->listCollections(); foreach ($list as $collection) { echo "My Collections: $collection/n"; }
为了插入一个文档,你需要提供一个 Zend_Cloud_DocumentService_Document 对象或者相关数据的关联数组,以及你要插入文档的集合。
许多提供商要求你为你的文档提供一个文档 ID。如果使用一个 Zend_Cloud_DocumentService_Document,你可以在实例化这个对象的时候,通过传递 ID 给构造函数来指定文档 ID。如果使用一个关联数组,键名将会是适配器特定的位置;例如,在 Azure,ID 是由 PartitionKey 和 RowKey 组成;在Amazon SimpleDB,ID 就是 ItemName;你可以通过在 _id 字段指定键名来使得更有可移植性。
由此,最简单同时也是最具兼容性的做法是通过使用一个 Document 对象来指定键。
// 实例化和创建文档对象 $document = new Zend_Cloud_DocumentService_Document(array( 'key1' => 'value1', 'key2' => 123, 'key3' => 'thirdvalue', ), "DocumentID"); // 插入到“mydata”集合 $documents->insertDocument("mydata", $document);
替换一个文档意味着移除与一个特定文档键相关的所有文档数据,同时用一套新的数据替换它。不同于更新,这个操作不会合并新旧数据,而是把文档整个替换掉。替换操作,就像 insertDocument(),接受一个 Zend_Cloud_DocumentService_Document 文档或者一个键->值对的数组作为参数,用来指定新字段的名字和值,同时需要一个文档所在集合的名字作为参数。
注意:文档 ID 是必须的
为了替换文档,文档 ID 是必须的。正如插入一个文档,如果你使用一个关联数组来描述文档,你将需要提供一个提供商相关的键,来指示文档 ID。因此,要在提供商之中替换一个文档最具兼容性的做法是使用 Document 对象,正如以下所示。
$document = new Zend_Cloud_DocumentService_Document(array( 'key1' => 'value1', 'key2' => 123, 'key3' => 'thirdvalue', ), "DocumentID"); // 更新位于 mydata 集合中的文档 $documents->replaceDocument("mydata", $document);
你也可以使用一个存在的 Document 对象,重新分配字段或者/和分配新的字段,然后把它传递给 replaceDocument() 方法:
$document->key4 = '4th value'; // 更新位于 mydata 集合中的文档 $documents->replaceDocument("mydata", $document);
更新一个文档,在一个存在的文档中改变键->值对。这个操作不会分享替换操作的语音;没有指定值的键,在数据组中不会被更改。你必须同时提供一个文档键和数据,既可以通过传递一个 Zend_Cloud_DocumentService_Document 文档对象也可以通过传递一个数组,给这个方法。如果键是空的,同时还提供了一个文档对象,文档键将被使用。
// 更新一个字段 $documents->updateDocument("mydata", "DocumentID", array("key2" => "new value")); // 或者使用文档对象;这可能是一个已经从服务中检索到的文档对象 $document = new Zend_Cloud_DocumentService_Document(array) 'key1' => 'value", 'key2' => 123, 'key3' => 'thirdvalue', ), "DocumentID"); $documents->updateDocument("mydata", null, $document); // 复制文档到另外一个 ID $documents->updateDocument("mydata", "AnotherDocumentID", $document);
Amazon SimpleDB 支持多值字段,所以数据更新将会和旧的键值合并,而不是取代它们。选项合并应该包括一个将要被合并的字段名字的数组。这个数组应该是键->值对;键对应字段键,值是一个布尔值,指示合并的状态(true 表示要被合并;false 将不合并)。在合并选项中没有指明的任何键将被会替换,而不是合并。
// key2 被重写,key3 被合并 $documents->updateDocument('mydata', 'DocumentID', array('key2' => 'new value', 'key3' => 'additional value'), array('merge' => array('key3' => true)) );
通过传递键给 deleteDocument() 可以删除一个文档。删除一个不存在的文档没有影响。
$documents->deleteDocument("集合名字", "DocumentID");
你可以通过指定一个文档的键,来捕获相对应的文档。fetchDodument() 返回一个 Zend_Cloud_DocumentService_Document 的实例。
$document = $service->fetchDocument('集合名', 'DocumentID'); echo "Document ID: " . var_export($document->getID(), 1) . "/n"; foreach ($document->getFields() as $key => $value) { echo "Field $key is $value/n"; }
为了在集合内找到符合标准的文档,可以使用 query() 方法。这个方法既可以接受一个字符串作为参数,这个字符串是一个依赖适配器的查询语句,然后被原样传递给具体的适配器;也可以接受一个 Zend_Cloud_DocumentService_Query 查询对象实例作为参数。返回的是一个 Zend_Cloud_DocumentService_DocumentSet,它包括了符合查询语句条件的 Zend_Cloud_DocumentService_Document 实例。DocumentSet 对象是可反复和可计算的。
$docs = $documents->query( "集合名字", "RowKey eq 'rowkey2' or RowKey eq 'rowkey2'" ); foreach ($docs as $doc) { $id = $doc->getId(); echo "Found document with ID: " . var_export($id, 1) . "/n"; }
如果使用一个查询对象,典型的,你将使用 select() 方法来进行检索。这将确保查询对象对于你的适配器是有意义的,这将确保查询对象集合成你的适配器所能理解的语法。
$query = $service->select(); $query->from('集合名字') ->where('year > ?', array(1945)) ->limit(3); $docs = $documents->query('集合名字', $query); foreach ($docs as $doc) { $id = $doc->getId(); echo "Found document with ID: " . var_export($id, 1) . "/n"; }
Zend_Cloud_DocumentService_Query 类并不限制哪一个查询从句可以被使用,但从句必须被底层的适配器所支持。现在支持的从句包括以下:
注意: Windows Azure 忽略这个从句的参数,而总是返回全部的文档
注意: 这个从句目前还不被 Windows Azure 支持。
为了方便用户,select() 方法实例化一个与适配器相关的查询对象,然后为它设置成 SELECT 从句。
$query = $documents->select() ->from('集合名字') ->where('year > ?', array(1945)) ->limit(3); $docs = $documents->query('集合名字', $query); foreach ($docs as $doc) { $id = $doc->getId(); echo "Found document with ID: " . var_export($id, 1) . "/n"; }
有时候有必要访问 Document API 正在运行的服务的适配器。这个可以通过使用 getAdapter() 方法来完成。
注意: 访问底层的适配器将会破坏在服务之间的可移植性,所以只有在异常环境下才能使用它。
// 由于 SimpleCloud Document API 不支持多文件上传,所以使用具体的适配器 $amazonSdb = $documents->getAdapter(); $amazonSdb->batchPutAttributes($items, '集合名字');