萨迈耶/蒙戈伊

一个 Yii MongoDB ORM

安装量78,767

依赖: 0

建议者: 0

安全性: 0

星标: 135

关注者: 22

分支: 75

开放问题: 0

类型:yii-extension


README

您可以在扩展的 Github 页面上找到更易于使用的文档版本: http://sammaye.github.io/MongoYii

蒙戈伊

为 Yii 框架提供支持 MongoDB 的另一个活动记录处理器。

原因

对于 Yii 已经有一个名为 YiiMongoDBSuite 的优秀扩展,那么为什么还要做另一个呢?YiiMongoDBSuite 有一些缺陷,我希望解决。

  • 不支持原生 $or
  • 代码库非常大且复杂
  • 不太支持 PHP 驱动程序的最新版本(1.3.x 系列)
  • 掩盖了 MongoDB 查询语言,在最上面叠加了一个查询语言层

在有些闲暇时间后,我决定自己制作一个 MongoDB 扩展。它基本上是 MongoDB 和 Yii 之间的“胶水”,并且在这一点上它被设计得相当灵活。

有几个设计点我希望强制执行

  • 以原始形式公开 MongoDB 查询语言
  • 使此扩展的编程简单且易于维护
  • 确保此扩展与 MongoDB 驱动程序的新旧版本都兼容
  • 尝试使事物更高效
  • 尽可能遵循 Yiis 自己的 CActiveRecord API,而不损害 MongoDB “语义”例如查询操作符的名称和使用 MongoCursor

好的,我们已经建立了一些理由,现在是时候真正讨论这个扩展了。

设置扩展

为了使用扩展,您首先需要设置它。要做的第一件事是下载源代码,并将其放置在您应用程序结构中可访问的位置,我选择了 protected/extensions/MongoYii

一旦您将源代码放置到位,您需要编辑您的 main.php 配置文件(如果您打算在控制台中使用此扩展,则还需要修改 console.php)并添加以下类型的配置

'mongodb' => array(
	'class' => 'EMongoClient',
	'server' => 'mongodb://localhost:27017',
	'db' => 'super_test'
),

并将 MongoYii 目录添加到您的 import 部分

'application.extensions.MongoYii.*',
'application.extensions.MongoYii.validators.*',
'application.extensions.MongoYii.behaviors.*',
'application.extensions.MongoYii.util.*'

这就是扩展的基本设置。

您会注意到我使用了 EMongoClient。这有点误导,因为它实际上代表了 MongoClientMongoDB 的组合。这意味着每次您在 EMongoClient 上调用魔法 __call 时,就像这样

Yii::app()->mongodb->getSomething();

它将尝试调用 EMongoClient 中的 getSomething 函数,或者,如果该函数不存在,则尝试在 MongoDB 类中调用它。

如果您想在 MongoClientMongo 类上调用函数,您需要按如下方式获取连接对象

Yii::app()->mongodb->getConnection()->getSomething();

EMongoClient 还被设计为以与所有版本的驱动程序兼容的方式处理完整的写入关注和读取首选项。

注意:默认情况下,模型将在您的配置中寻找名为 mongodb 的组件,所以请确保除非您修改扩展,或者在不使用活动记录的情况下使用它,否则请确保您的默认(主)连接是一个名为 mongodb 的组件。

如果您希望设置日志以将条目插入MongoDB(例如在CDbLogRoute中),您可以将以下内容添加到您的'log'组件配置中

	'log'=>array(
		'class'=>'CLogRouter',
		'routes'=>array(
			array(
				'class'=>'CFileLogRoute',
				'levels'=>'error, warning',
			),

			[ ... ]

			array(
				'class'=>'EMongoLogRoute',
				'connectionId'=>'my_connection_id', // optional, defaults to 'mongodb'
				'logCollectionName'=>'my_log_collection', // optional, defaults to 'YiiLog'
			),
			
		),
	),

提供自定义mongodb组件/多个连接

每个EMongoDocumentEMongoModel的继承类,即您的模型将有一个可覆盖的函数getMongoComponent()。您可以简单地重写这个函数以返回您自定义的应用程序组件,例如

public function getMongoComponent()
{
	return Yii::app()->someweirddbconnectionINIT;
}

并且该模型现在将使用该新应用程序组件来获取其信息。如果您使用不同的数据库来存储不同的模型,这也很有帮助。

Composer

MongoYii完全支持Composer,并在packagist上列出。packagist

作为额外的注意事项,由@ujovlado在相关的问题上发布;如果您仅使用Composer为Yii扩展,则可以将vendor-dir更改为protected/extensions以设置更全面的解决方案。

{
    "config": {
        "vendor-dir": "protected/extensions"
    }
}

但是,要自动使用Composer加载MongoYii,您需要降级到1.0.3,其中Yii安装程序未过时。如果这两个选项都失败,您也可以创建自己的脚本来处理已删除的安装程序。

目前,MongoYii不处理命名空间,并且这不太可能在Yii1中改变。

写关注(以前称为“安全”写入)

此扩展使用新的w变量全局处理您希望对MongoDB施加的写关注级别。

默认情况下,扩展将假定已确认写入,这意味着safe=truew=1,具体取决于您的驱动程序版本。要更改此设置,只需将w添加到您的mongodb组件配置,并根据PHP文档给出一个值。

对于使用1.3.x系列驱动程序的用户,还有一个j选项,可以在配置中将其设置为truefalse,以允许您控制写入是否被日记确认。

注意:写关注是从驱动程序本身抽象出来的,以使此变量与所有版本的驱动程序兼容,因此请在需要时使用配置或EMongoClientwj类变量设置写关注,否则不会在活动记录中使用该写关注。

注意:当您直接接触数据库时,写关注的工作方式不同,并且EMongoClient类中发出的写关注将不起作用。在这种情况下,您应始终确保根据驱动程序版本手动指定写关注。

这可能在将来改变,但到目前为止,当您想要活动记录消失时,它就会消失。

读取偏好

对于使用旧驱动程序的用户,只有一个额外的配置变量可供您使用,即setSlaveOkay。在您的配置中将此设置为truefalse,以使从副本集成员读取成为可能。

对于使用1.3.x系列驱动程序的用户,您有RP配置变量。RP配置变量与MongoClient类中的setReadPreference选项的一对一关系,只有一个例外。第一个参数不是常量,而是常量的名称。在配置中使用读取偏好的示例:

'RP' => array('RP_SECONDARY' /* The name of the constant from the documentation */,
	array(/* Would normally be read tags, if any */))

请参阅驱动程序文档以获取完整的选项集。

要随时更改读取偏好,请使用适用于您的驱动程序的功能;对于1.3.x系列

Yii::app()->mongodb->setReadPreference(MongoClient::RP_PRIMARY, array());

以及1.3之前的版本

Yii::app()->mongodb->setSlaveOkay(true);

注意:与写入关注不同,RPsetSlaveOkay变量在不同版本的驱动程序之间不互锁,使用EMongoClientRP变量不会转换为slaveOkay

在不使用Active Record的情况下使用MongoDB

您可以使用与通常使用驱动程序相同的方法直接在任何时候调用数据库。例如,要查询数据库

Yii::app()->mongodb->collection->find(array('name' => 'sammaye'));

因此,MongoYii的Active Record元素在需要时可以快速消失。

EMongoModel

EMongoModelEMongoDocument的一个简化版本。

这是将其从EMongoDocument分离出来的原因,以提供一个小型精简的Active Model,用于子文档。每次您创建基于类的子文档时,都可以扩展此类。

EMongoModel实现了所有CModel的功能,但增加和更改了一些功能。

魔术函数

获取器和设置器应继承Yii的所有功能。

虚拟属性

此扩展支持通过@virtual的doc块注释语法进行虚拟属性,例如

class User extends EMongoModel{
    /** @virtual */
    public $somevar;
}

这些变量可以像其他所有内容一样使用,但它们永远不会保存到MongoDB中。

注意:由于PHP对象访问的工作方式,最好将所有记录字段(无论是虚拟的还是非虚拟的)都设为public

关系

与SQL不同,在SQL中您有许多复杂的关系类型,在MongoDB中您通常只有两种:onemany

正如你所猜的,您只能在此扩展中定义两种类型的关系:onemany。让我们看看一个例子

function relations(){
	return array(
		'others' => array('many', 'Other', 'otherId', 'sort' => array('_id'=>-1), 'skip' => 1, 'limit' => 10)
	);
}

您会认出很多来自Yii自己的Active Record,事实上很多都是相同的。我们定义一个关系名称作为键,然后我们定义文本中的onemany(由于只有两种类型,因此常量似乎没有用),然后我们定义一个类名,在此例中为Other,然后我们定义该类的外键,otherId

关系的默认行为是尝试使用当前模型的主键_id来查询外键。这对于EMongoModel来说是一个问题,因为它没有主键。确保如果您在EMongoModel中使用此功能,您定义一个on子句来替换当前模型的主键。

on子句支持多种字段类型。它可以接受一个DBRef或一个ObjectId或一个ObjectId数组,具体取决于您如何定义您的文档。

您也可以像在Yii中一样定义一个where子句。这是与MongoDB中常规查询语法的一对一关系,并且扩展将基本上将此子句与您定义的主键字段合并,以查询关系。

缓存

截至5.x,MongoYii中的关系缓存默认开启。这意味着现在所有关系都已缓存。如果您想确保某个关系不被缓存,您可以将'cache' => false显式添加到关系定义中,如下所示

function relations(){
	return array(
		'others' => array('many', 'Other', 'otherId', 'cache' => false)
	);
}

5.x之前的版本默认没有开启关系缓存。

getDocument()

仅获取“原样”文档。这意味着如果您放入嵌套EMongoModel的元对象,它将在输出中返回这些对象。

getRawDocument()

将移除所有由扩展使用的类,并返回一个适合用于MongoDB的文档。

getJSONDocument()

将运行 getRawDocument() 然后将输出作为JSON字符串返回。

getBSONDocument()

将运行 getRawDocument() 然后将输出作为BSON字符串返回。

EMongoDocument

EMongoDocument 扩展 EMongoModel 并实现其所有功能以及数据库访问所需的功能。它还尽可能实现了 CActiveRecord 的功能。

注意: 允许使用数据库的函数不在此文档部分定义。相反,这些函数实际上定义在“查询”部分。如果您想了解 EMongoDocument 的这部分,请转到“查询”部分。

collectionName()

返回表示集合名称的字符串。所有活动记录模型都应该实现此功能,尽管它不是 abstract

primaryKey()

目前只返回 _id 作为键。

使用自定义主键

如果您正在使用不是 ObjectId(在PHP驱动器中也称为 MongoId)的主键,那么您应该覆盖 EMongoDocumentgetPrimaryKey 函数以不返回 MongoId

public function getPrimaryKey($value=null){
	if($value===null)
		$value=$this->{$this->primaryKey()};
	return (string)$value;
}

您当然可以在该函数中添加任何所需的程序或格式化代码,以确保主键在查询时为MongoDB准备好。

范围

范围以与 CActiveRecord 相同的所有常规方式完全支持,但有一个区别;术语。

此扩展的范围和查询使用以下词语来描述其部分

  • condition 用于描述条件本身
  • sort 用于描述排序
  • skip 用于描述偏移量
  • limit 用于描述限制

例如,一个完整的默认范围,省略已删除的模型,以跳过第一个获取最新的10个

array(
	'condition' => array('deleted' => array('$ne' => 1)),
	'sort' => array('date' => -1),
	'skip' => 1,
	'limit' => 11
)

您也可以定义自己的范围,但是与您在Yii中习惯的做法略有不同

function someScope(){
	$this->mergeDbCriteria(array(
		'condition'=>array('scoped' => true),
		'sort'=>array('date'=>-1),
		'skip'=>1,
		'limit'=>11
	));
}

如您所注意到的,EMongoDocument中的 _criteria 变量,通常是一个 EMongoCriteria 对象,实际上是完全基于数组的。

这适用于所有范围操作;它们都是基于数组的。

为了帮助您不需要 EMongoCriteria 对象,EMongoDocument 提供了一个名为 mergeCriteria 的辅助函数,用于合并标准对象。使用此函数不会影响模型本身,而只是合并标准以返回。以下为使用 mergeCriteria 函数的示例

function someScope(){

	$criteria = array(
		'condition'=>array('scoped' => true),
		'sort'=>array('date'=>-1),
		'skip'=>1,
		'limit'=>11
	);

	if($this->deleted)
		$criteria = $this->mergeCriteria($criteria,array('condition'=>array('deleted'=>1)));

	$this->mergeDbCriteria($criteria);
	reutrn $this;
}

注意: 与Yii一样,通常范围不会被自动重置,请使用 resetScope() 来重置范围。

equals()

检查当前模型是否等于作为参数发送的另一个模型。

exists()

检查数据库中是否存在具有作为第一个参数提供的标准的文档。

clean()

清除文档的所有属性和关系。

refresh()

运行 clean() 然后从数据库中重新填充模型。

getCollection()

返回原始的 MongoCollection

通常最好不使用此功能,而是使用扩展包装版 - updateAlldeleteAll。这些函数与手动在 getCollection() 上执行操作的唯一区别是,这些函数理解扩展的写入关注点。

ensureIndexes()

此函数允许用户通过数组定义确保一组索引。

当在 init() 函数中使用时,这最有用,可以在模型启动时生成预制的索引。一个好的例子是

public function init()
{
    if(YII_DEBUG){
        $this->ensureIndexes(array(
            array('username' => 1),
            array(array('email' => 1), array('unique' => true)),
            array(array('description' => 1))
        ));
    }
}

上述示例代码片段展示了定义索引的不同方式。

默认情况下,函数输入数组中的每个元素都将是一个索引定义,其中元素 0 是字段,而 1 是选项。

但是,您不需要定义索引选项。您还可以通过不定义 0 元素,而是定义一个仅包含索引字段的关联数组来进一步简化定义。

setAttributes()

了解MongoYii如何默认分配整数非常重要,甚至可以说是强制性的。由于MongoDB没有严格的字段类型处理,像复选框等布尔整数值很容易变成字符串,这会破坏您的应用程序,并导致您需要反复转换对象或更改查询方式(因为当然,MongoDB在查询时是类型感知的)。

MongoYii会将任何数字、实整数(也称为“正数”或“无符号整数”),不以0开头且不包含字母的数转换为 int

这很重要,因为MongoDB可以原生存储的最大整数仅为32位。为了使MongoDB能够存储更大的整数,您必须在驱动程序中使用可用的 native_long 配置变量。

如果您在32位系统上,您需要向配置变量堆栈中添加另一个变量:long_as_object

注意:大于系统限制的整数将保留为字符串。这意味着在32位系统上,您可以从表单数据中分配的最大 int 是2147483647,而在64位系统上是9223372036854775807。如果您想使用超出系统限制的表单中的 int 数据类型,您将需要自行处理字段,无论是在 CHttpRequest 处理器中还是在验证器中使用。

示例

现在我们已经讨论了 EMongoDocument,让我们看看最基本的示例

class User extends EMongoDocument{
	function collectionName(){
		return 'users';
	}

	public static function model($className=__CLASS__){
		return parent::model($className);
	}
}

这是可以存在的最基本文档 - 没有预定义的模式,只需要一个 model 函数(与Yii活动记录相同)和 tableName,也称为 collectionName

随着时间的推移,您可能想添加某些字段,如虚拟属性等,以使您的生活更轻松。

class User extends EMongoDocument{

	/** @virtual */
	public $agree = 1;

	public $addresses = array();

	function collectionName(){
		return 'users';
	}

	public static function model($className=__CLASS__){
		return parent::model($className);
	}
}

请注意,尽管不需要,我也添加了 addresses 字段。我这样做是因为PHP使用魔法函数的方式。

如果您魔术般地访问一个数组,您就不能同时操作它,因为它是对变量的间接访问。所以这里有一个好的建议:如果您计划有子文档,可能最好在类中显式声明字段为变量。

查询

查询尝试尽可能多地暴露原生MongoDB查询语言。提供了一个 EMongoCriteria 类,但是它不是必需的,也不比通过数组执行提供更多的功能。该 EMongoCriteria 类在任何地方都没有被依赖,也不需要。

缓存

MongoYii还支持通过 EMongoCacheDependency(请参阅本文档底部)进行完整的缓存,支持在文档中定义的活动模型查询缓存。

以下是一个示例:

$dep = new EMongoCacheDependency('article', [['_id' => new MongoId('540477726803fad51b8b4568')], 'sort' => ['a' => 1]]);
$c = Article::model()->cache(4, $dep)->findAll();

$c 的结果将从缓存表中提取到您的应用程序中,直到依赖项被认为已过期。

就像在正常的Yii活动记录中一样,您也可以指定在依赖项过期之前实际应该缓存的查询数量。

find()

find() 非常简单。它本质上是对驱动程序自己的 find() 函数的 1-1 映射,并实现了相同的特定功能。就像驱动版一样,它也返回一个游标实例 (EMongoCursor),可以用来从数据库中懒加载结果。

无论是否找到结果,它都会返回一个游标。但是,如果找不到结果,则 count 将返回 0,并且迭代器将没有任何迭代。

注意: 游标不会预加载文档,如果您想完成此操作,请将 find 的调用包裹在 iterator_to_array 的调用中。

findOne() 和 findBy_id()

findOne,就像 findBy_id,是驱动程序自己的 findOne 方法的直接 1-1 实现,如果找到,则返回活动记录模型,否则返回 null

findBy_id 函数接受字符串形式的十六进制表示的 ObjectId 或包装在 MongoId 类中,并使用 findOne 函数查找具有该 _id 的记录,返回相同的结果。它基本上是 findOne 的辅助函数,以使您的生活更加轻松。

范围

此扩展的读取函数完全支持模型中的范围。

示例

好的,现在我们基本了解了查询,让我们看看一个例子

$c = User::model()->recently()->find(array('deleted' => 0))->sort(array('joined' => -1))->skip(2)->limit(3);

这看起来可能很复杂,但我现在会为您分解它

  • User::model() 获取我们的模型
  • ->recently() 实际上是一个范围,这不是必需的,但有助于演示
  • ->find(/*...*/) 实际上是 MongoDB 驱动的 find 方法,并返回一个实现了 MongoCursorEMongoCursor
  • ->sort() 实际上是 MongoCursor 上的 MongoDB 驱动的 sort 方法
  • ->limit() 再次,基本上是 MongoCursor 上的 MongoDB 驱动的自己的 limit 函数

有关支持的运算符的参考,请参阅 MongoDB 文档: http://docs.mongodb.org/manual/reference/operators/

注意: 已省略其他函数,如 findByAttributes,因为使用 MongoDB 的查询语言实现这些函数似乎没有意义。

save()

save 保存文档,并作为外部访问活动记录模型中的 insertupdate 的方式使用,即

if($user->validate()) $user->save();

如果文档是新的,它将插入,否则它将更新。

insert()

此函数用于活动记录模型内部。如果记录是新的,它将尝试插入,否则它将抛出错误。

update()

此函数用于活动记录模型内部。如果记录不是新的,它将尝试更新它,否则它将抛出错误。

如果您向此函数或 save 函数发送属性,它将尝试在这些属性上执行 $set,否则它将保存模型。

delete()

此函数用于删除当前活动记录。

deleteByPk() 和 updateByPk()

这些是更新和删除函数的辅助函数,但它们直接作用于数据库,而不是通过活动记录。

以下是一个示例

User::model()->deleteByPk($_id[, array('deleted' => 1)[, array('w' => 2)]]);
User::model()->updateByPk($_id, array('$set' => array('d' => 1)[, array('deleted' => 1)[, array('w' => 2)]]);

方括号 [] 中显示的参数是可选的。

这些函数可以接受字符串或 MongoId 作为 $_id 参数。

updateAll() 和 deleteAll()

实际上与上面相同,只是这些直接翻译为 MongoDB 驱动的自己的 updatedelete 函数。

注意: UpdateAll 默认为 multi true

验证

验证基本上没有改变,只是由于 Yiis 自己需要 SQL,某些验证器名称发生了变化。

unique

unique 验证器现在是 EMongoUnqiueValidator

array('username', 'EMongoUniqueValidator', 'className' => 'User', 'attributeName' => 'username')

exist

exist 验证器现在是 EMongoExistValidator

array('user_id', 'EMongoExistValidator', 'className' => 'User', 'attributeName' => '_id')

EMongoIdValidator

此验证器被添加作为一个简单而灵活的方法,用于自动化转换 MongoId 的十六进制表示形式(例如:addffrg33334455add0001)到数据库操作所需的 MongoId 对象。此验证器还可以处理需要转换为 MongoId 的字符串数组。

array('ids,id', 'EMongoIdValidator'), // ids is an array while id is a single string value

EMongoSubdocumentValidator

这是一个子文档验证器,请参阅“子文档”部分以获取完整文档。

行为

EMongoTimestampBehaviour

这是 MongoYii 版本的 CTimestampBehavior 行为,将使用 MongoDate 字段,但是,可以添加一个表达式到 timestampExpression 使行为返回整数时间戳。

该行为的用法与正常行为非常相似,实际上只有名称不同

function behaviors(){
	return array(
		'EMongoTimestampBehaviour'
	);
}

子文档

大多数情况下,此扩展不支持自动处理子文档。有几个原因,首先是性能问题——自动化子文档的使用需要加载许多类来处理不同的子文档及其验证。

另一个主要原因是,在我参与的所有项目中,每次我尝试通过活动记录自动化子文档时,我总是放弃它并手动处理。已经多次证明,您很少真正需要自动子文档,通常您需要比此扩展能提供的更大的控制权来存储它们。

所以,这就是放弃在活动记录中自动处理子文档背后的简要理解。

这并不意味着您不能完全嵌入子文档类;在保存时,活动记录类将遍历文档并尝试去除任何 EMongoModelEMongoDocument 类。

除此之外,还有一个子文档验证器,技术上它可以接受多层嵌套。请记住,但是,对于您使用的每一层,它都会引起重复。这将对您的应用程序的性能产生影响。

基于数组的子文档的示例

function rules(){
	return array(
		array('addresses', 'subdocument', 'type' => 'many', 'rules' => array(
			array('road,town,county,post_code', 'safe'),
			array('telephone', 'integer')
		)),
	);
}

而基于类的子文档的示例

function rules(){
	return array(
		array('addresses', 'subdocument', 'type' => 'many', 'class' => 'Other'),
	);
}

type 定义了子文档的类型,就像关系一样,这可以是 onemany

验证器将评估规则,就像它们与原始模型完全分离一样,因此从理论上讲,您可以使用任何您想要的验证器。

验证器的错误输出将根据子文档的 onemany 类型而有所不同。对于 one,验证器将直接在字段上输出模型错误,而对于 many,它将为每个嵌入的模型(行)创建一个新的元素,并在该新元素中包含字段上的嵌套错误,例如

array(
	'addresses' => array(
		0 => array(
			'telephone' => array(
				0 => 'Some error here'
			)
		)
	)
)

注意:为了在 1.1.4 中实现过滤器功能,验证器现在默认会覆盖您输入的内容,以验证器输出的结果为准。这意味着如果您的规则定义不正确,您将丢失子文档中的字段。您可以通过将此验证器的 strict 参数设置为 false 来解决这个问题。

注意:关于这一点,为了避免在每次保存根文档时进行迭代(因为默认情况下在 Yii 中保存时都会执行验证),您应该将子文档验证器限制在特定场景中,这些场景将积极使用它们。

处理子文档

如我们所知,MongoYii 不会为您自动处理子文档。如果您希望有自动处理子文档的处理程序,通常建议您根据自己的场景创建自己的处理程序。一个原因是许多人有许多不同的文档设置,因为没有为子文档预定义模式,所以我不可能在不考虑子文档存在的所有可能性的情况下提供自动化使用。

对于这个解释,我们假设您不想创建自己的子文档处理程序,而是愿意使用 MongoYii 和 PHP 内置的能力。处理子文档在很大程度上取决于您打算如何管理和使用它们。

好的,让我们从顶部开始;您是否为这些子文档使用了一个类?如果答案是“是的,先生!”那么您的子文档可能相当复杂,并且您的应用程序中有一个专门的部分,其中包括自己的控制器等一切,例如,博客文章上的评论。

现在,您必须问自己第二个问题;每次保存这些子文档时,您是替换它们还是希望使用诸如 $push$pull$pullAll$pushAll$addToSet 等修饰符?

如果您希望每次都使用修饰符,那么管理此类文档的最佳方式是将子文档的单个类扩展为 EMongoModel,例如,Comment 将扩展 EMongoModel

例如,添加评论到帖子时,您会这样做

if(isset($_POST['Comment'])){
	$comment=new Comment;
	$comment->attributes=$_POST['Comment'];
	if($comment->validate())
		$response = Post::model()->updateAll(array('_id' => $someId), array('$push' => $comment->getRawDocument()));
}

您会为大多数其他操作使用相对类似的行为。在这种情况下,MongoYii 只是一个辅助程序和粘合剂,使您的生活变得稍微容易一些,然而,最终它不会自动为您管理子文档。

如果您没有使用类,那么您的子文档可能相当原始,并且很可能只是根文档的细节,并且您每次都会替换它们。如果使用复杂类但每次保存时都替换子文档列表,这种场景也适用。

如果是这种情况,您可以使用上面提到的子文档验证器来处理您的子文档,或者您实际上可以编程这样做

$valid=true;
$user=User::model()->findBy_id($uid);
if(isset($_POST['numbers'])){
	foreach($_POST['numbers'] as $row){
		$d=new Model();
		$d->attributes = $row;
		$valid=$d->validate()&&$valid;
		$user->numbers[] = $d;
	}
}
if($valid) $user->save();

作为一个例子。

作为一个附加的注释,您实际上可以将包含子文档的文档中的数组字段视为任何其他字段。例如,这将有效

$m=new Something();
$m->name='thing';
$parentClass->things[6] = $m;
$parentClass->save();

因此,子文档在这个扩展中非常灵活,并且它们不会让您陷入只能这样思考的困境,就像MongoDB本身一样。

使用 ActiveDataProvider

此扩展包含一个名为 EMongoDataProviderCActiveDataProvider 辅助程序。它的工作方式完全相同,只是调用方式不同。

您不是使用 EMongoCriteria 或类似的东西,而是使用数组,如下所示

new EMongoDataProvider(array(
	'criteria' => array(
		'condition' => array(),
		'sort' => array(),
		'skip' => 1,
		'limit' => 1
	),
	/* All other options */
));

criteria 选项基本上与游标的各个部分相关。

此扩展完全支持 CGridView(感谢 @acardinale 的修复)并且应该也能够处理 CListView

关于上面的注释,CGridView 最好在您在 CGridView 小部件的定义中预定义要显示的模式时使用。因此,为了显示用户模型的一个示例

$this->widget('zii.widgets.grid.CGridView', array(
	'id'=>'user-grid',
	'dataProvider'=>$model->search(),
	'filter'=>$model,
	'columns'=>array(
		'_id',
		'username',
		'addresses',
		'create_time',
		array(
			'class'=>'CButtonColumn',
			'template'=>'{update}{delete}',
		),
	),
));

这通常是最佳方法,因为当然,MongoDB 是无模式的(具有灵活的模式更为合适),因此有时在严格的表中工作效果不佳。

EMongoCriteria

EMongoCriteria 类可以帮助在应用程序的多个部分构建模块化查询,提供一层抽象层和辅助函数,使您能够更好地创建复杂查询。

使用 EMongoCriteria 的简短但完整的示例将是

$c = new EMongoCriteria();
User::model()->find($c
				->addCondition(array('name' => 'sammaye')) // This is basically a select
				->addOrCondition(array(array('interest' => 'Drinking'), array('interest' => 'Clubbing'))) // This is adding a $or condition to our select
				->skip(2) // This skips a number of rows
				->limit(3) // This limits by a number of rows
					);

因此,您可以看到我们如何快速轻松地构建非常复杂的查询。

就像 CDbCriteria 一样,您也可以直接从构造函数设置查询的所有这些属性,如下所示

$c = new EMongoCriteria(array(
	'condition' => array('name'=>'sammaye'),
	'limit' => 10
));

EMongoCriteria 类实现了许多您期望从 CDbCriteria 得到的函数。

setCondition() / getCondition()

这基本上只是设置和获取查询的条件。

addCondition()

向查询添加一个正常(非 $or)条件,并接受一个 array 作为其唯一参数。

addOrCondition()

添加一个$or条件,并接受一个arrays数组作为其唯一参数,其中每个嵌套的array都是$or中的一个条件(类似于驱动程序)。

请注意,调用此函数将覆盖之前在条件中放置的任何$or

getSort() / setSort()

获取和设置查询的排序。

getSkip() / setSkip()

获取和设置查询的跳过数。

getLimit() / setLimit()

获取和设置查询的限制。

getProject() / setProject()

设置条件的投影,以指定要包含/排除的字段。

getSelect() / setSelect()

这些为getProject()setProject()提供别名。

compare()

这与CDbCriteria非常相似,并在此基础上进行了大量工作。

您只需输入columnvaluematchPartial参数值(按此顺序),然后EMongoCriteria类将创建一个条件并将其合并到当前条件中,具体取决于输入的数据。例如

$c->compare('name', 'sammaye');

$c->compare('i', '<4');

如第二个示例所示,比较函数将接受一定数量的运算符。支持的运算符包括:<><=>=<>=

值得注意的是,该函数目前仅接受AND条件。

mergeWith()

CDbCriteria一样,这会将数组或另一个EMongoCriteria对象合并到这个对象中,并传输其所有属性。

例如

$c->mergeWith($otherC);

现在$c将具有所有合并自$otherC的属性。

toArray()

这基本上会将EMongoCriteria转换为语法数组的形式

array(
	'condition' => array(),
	'skip' => 1,
	'limit' => 1,
	'sort' => array(),
	'project' => array()
)

默认情况下,调用方式如下

$c->toArray();

全查询和部分查询

当您不想检索整个文档时,可以只返回部分结果。

无论是EMongoCriteria还是基于数组的常规查询,都支持通过两种方法进行投影。首先是在EMongoCriteria中的project变量

$c->project=array('_id'=>0,'d'=>1);

或者作为定义的数组中的元素(例如,范围)

functions scope(){
	return array(
		'project' => array('_id'=>0,'d'=>1)
	);
}

其次,作为注入到活动记录模型读取函数的参数中,例如

User::model()->find(array(),array('_id'=>0,'d'=>1));

这些将返回partial=trueEMongoDocument实例,无论是显式还是通过游标对象。该规范在所有现有读取函数(如findOnefindBy_idfindByPk)中实现,但它们不接受在写入函数中(如updateinsertsave等)。

当文档作为部分返回时,它将只保存查询结果中包含的根级别字段。

注意:当使用$elemMatch投影时,MongoYii将处理该结果作为该字段的最终结果。换句话说,当您保存根文档时,MongoYii将考虑该单个投影子文档作为完整字段值,并将删除该字段中所有其他子文档。

注意:如果从根文档中省略了_id(通过'_id' => 0),则不允许保存该文档。该扩展程序将抛出一个异常,指出未设置_id字段。

GridFS

MongoYii有一个名为EMongoFile的GridFS处理程序。这个类是专门设计为辅助工具的,并不是使用MongoYii与GridFS一起使用的必需品。它所做的简化了将文件上传到GridFS、保存和从GridFS检索文件的过程。它特别针对从表单上传文件。

让我们通过一个例子来了解它的用法,这个例子取自于测试仓库中的示例。要从表单上传新文件,你只需像这样在类上调用静态函数populate

EMongoFile::populate($model,'avatar')

这实际上意味着:"从模型user和字段avatar获取上传的文件" 其余操作与普通上传表单类似。如果populate返回的不是null,则表示它找到了一些内容。

要保存文件到GridFS,只需调用save()。由于类直接扩展了EMongoDocument,因此这意味着你可以访问其他类中的所有常规活动记录功能。

如果你希望为文件对象本身添加验证器,你必须将其指向类的file变量;确保只在create时允许对文件对象的验证器,否则Yii将不知道如何处理MongoGridFSFile对象。

注意:目前,如果你选择在更新时调用保存,它将覆盖以前的文件。尚未实现版本控制。

稍后检索文件与保存文件一样简单,与查找任何其他记录没有区别

EMongoFile::model()->findOne(array('userId'=>Yii::app()->user->id))

此代码片段假定我们希望找到具有当前会话中用户userId元数据字段的文件。

使用urlManager

如果你希望使用urlManager在URL中正则匹配_id,可以使用以下方法

'<controller:\w+>/<action:\w+>/<id:[a-z0-9]{24}>'=>'<controller>/<action>',

它将尝试挑选出一个24个字符长度的字母数字_id

版本化文档模型

MongoYii的2.5.x版本添加了版本化文档的能力。

如果你对版本化或它在某些场景中的好处感到困惑,那么MongoDB的创建者可以找到一个很好的、简单且易于阅读的博客文章,描述了它在Mongoose中的添加。

要设置一个版本化的文档,你可以简单地创建一个实现version()并返回true的模型,以及可选的versionField()

class versioned extends EMongoDocument{
	public function versioned(){
		return true;
	}
	
	public function versionField(){
		return '_v'; // This is actually the default value in EMongoDocument
	}
	
	public static function model($className=__CLASS__){
		return parent::model($className);
    }	
}

一旦设置了文档的版本化能力,在运行时就不能更改,换句话说,你不能执行$doc->removeVersion()来停止对特定插入的版本控制产生影响。

在设置文档模型后,版本化将在后台工作,你不需要做任何事情,每次调用save时,它都会确保你拥有的版本是最新的。

数据库迁移

尽管MongoDB是无模式的,但你有时可能需要修改记录。为此,你可以使用yiic migratemongo命令。它的工作方式与yiic migrate相同。有关详细用法,请参阅yii文档

要启用此命令,请在你的配置文件中添加一个commandMap条目

'commandMap' => array(
    'migratemongo' => array(
        'class' => 'application.extensions.MongoYii.util.EMigrateMongoCommand'
    )
)

已知缺陷

  • 子文档不是自动化的,我已在上面说明了原因
  • 聚合框架与活动记录不太匹配,因此它不支持在模型中直接使用,但是每个模型都有一个aggregate辅助函数,但它不会返回模型实例,而是返回MongoDB服务器的直接响应结果。

我确信还有更多,但这是你需要考虑的此扩展的即时缺陷。

错误

可能有一些,但是我将努力接受拉取请求并修复已报告的错误。

请将所有问题,包括错误和/或疑问,报告在 GitHub 问题跟踪器 上。

示例

请查看测试文件夹,以获取如何使用此扩展的更多示例,它相当全面。

还有一款使用 MongoYii 构建的演示应用程序。它有效地模拟了一个维基百科类型的网站,并允许用户(包括会话)和文章管理。然而,如果您仍在学习 Yii,这不是一个好的起点,但如果您在学习 MongoYii,它是一个好的起点。

演示应用程序存储库在此处可以找到.

运行测试

测试需要所有依赖项编译的 PHPUnit 插件。使用 PEAR,您可以发起以下命令

sudo pear install --force --alldeps phpunit/PHPUnit &&
pear install phpunit/dbUnit &&
pear install phpunit/PHPUnit_Story &&
pear install phpunit/PHPUnit_Selenium &&
pear install phpunit/PHP_Invoker

之后,您可以简单地告诉 PHPUnit 在 tests/ 文件夹中运行所有测试,而无需真正的顺序。

贡献

当向 MongoYii 添加大量功能时,请尽量提供相应的单元测试。如果没有单元测试,您的功能,即您的项目最可能依赖的功能,可能在未来的版本中损坏。

如果您打算向 MongoYii 贡献更改,我应该解释我对 EMongoCriteria 类存在的看法。我个人认为它是不必要的。

原因有很多。在 SQL 中,抽象是由以下原因之一(但不是全部)来证明的

  • 不同的实现(即 MySQL 和 MSSQL 和 PostgreSQL)创建了略有不同的语法
  • SQL 是一种基于字符串的查询语言,因此具有面向对象的抽象层是合理的
  • SQL 有一些相当复杂且难以形成的查询,这使得抽象层很有用

MongoDB 没有这些问题;首先,它已经有一个面向对象的查询接口,其次,它可以通过 CMap::MergeArray() 简单地合并不同的查询,最重要的是,它只有一个语法,因为 MongoDB 只是一个数据库。除此之外,由于 MongoDB 的查询构建方式,这个类实际上可能会限制您的查询,使生活更加困难,甚至可能创建性能较差的查询(特别是由于在这个类中执行 $or 的困难程度)。

因此,我相信 EMongoCriteria 类只是多余的负担,消耗了我可以用在其他任务上的内存。

因此,我期望对 MongoYii 某些部分的修改与/无 EMongoCriteria 兼容。

所以,我期望所有修改都兼容,无论是否使用 EMongoCriteria

工具

util 文件夹包含一些对 MongoYii 的通用扩展,人们可能会觉得它们很有用。这个文件夹中的一些东西属于此类,比如替换根目录之外可能看起来不相关的 Yii 内部组件。

EMongoCache

这是由 Rajcsányi Zoltán 实现的 CCache 的 MongoYii 版本。

要使用它,首先将其放置在您的配置中

'components'=>array(
	...
	'cache' => array(
		'class'=>'application.extensions.MongoYii.util.EMongoCache',
		// 'ensureIndex' => true, //set to false after first use of the cache
		// 'mongoConnectionId' => 'mongodb',
		// 'collectionName' => 'mongodb_cache',		
	),
}

注释掉的行是可选参数,如果需要,您可以发送这些参数。

现在是一个使用它的示例

// flush cache
Yii::app()->cache->flush();

// add data to cache
Yii::app()->cache->set('apple', 'fruit');
Yii::app()->cache->set('onion', 'vegetables');
Yii::app()->cache->set(1, 'one');
Yii::app()->cache->set(2, 'two');
Yii::app()->cache->set('one', 1);
Yii::app()->cache->set('two', 2);

// delete from cache
Yii::app()->cache->delete(1);
Yii::app()->cache->delete('two');

// read from cache
echo Yii::app()->cache->get(2);

// multiple read from cache
$arr = Yii::app()->cache->mget(array('apple', 1, 'two'));

print_r($arr); // Array( [apple] => fruit [1] => [two] => )

EMongoMessageSource

这是由 Rajcsányi Zoltán 实现的 MongoYii 的 Yii::t() 版本。

要使用它,首先将其添加到您的配置中

'components' => array(
	...
	'messages' => array(
		'class' => 'application.extensions.MongoYii.util.EMongoMessageSource',
		// 'mongoConnectionId' => 'mongodb', 
		// 'collectionName' => 'YiiMessages',               
	)        
)

注释掉的行是可选参数,如果需要,您可以发送这些参数。

然后添加一些消息到翻译表中

db.YiiMessages.insert( { category:"users", message:"Freund", translations: [ {language:"eng", message:"Friend"} ] } );

然后简单地获取这些消息

<?=Yii::t('users', 'Freund'); ?>

EMongoSession

这是由我实现的 MongoYii 的 CHttpSession 版本。

要使用它,只需将其包含在配置中

'session' => array(
	'class' => 'application.extensions.MongoYii.util.EMongoSession',
)

然后像使用 Yiis 的普通会话一样使用它。

EMongoAuthManager

这是由 @tvollstaedt 替换 Yiis 的 auth manager 的 MongoDB 版本。

要使用它,只需将其放置在配置中

'authManager' => array(
	'class' => 'EMongoAuthManager',
)

它将以与其他任何 auth manager 相同的方式工作。

注意:您可能需要使用数据库迁移来确保您的应用程序实例中的授权设置保持最新。

EMongoPagination

这是由@kimbeejay为MongoYii构建的CPagination的替代品。

它使用与CPagination相同的API,除了让您知道其存在外,无需额外的文档(除了CPagination之外)。

EMongoCacheDependency

这是为了启用MongoYii版本的缓存

此类的示例用法如下

$cache = Yii::app()->cache;
$cache->set(
	'12', 
	'dfgdfgf', 
	30,
	new EMongoCacheDependency('t', [
		[],
		'limit' => 5
	])
);
var_dump($cache->get('12'));

当缓存未失效时将返回dfgdfgf,但如果您使缓存失效,则根据文档将返回false

因此,如果我运行

$cache = Yii::app()->cache;
Yii::app()->mongodb->t->insert(['g' => 1]);
var_dump($cache->get('12'));

我会得到返回值为false

此缓存类的构造函数接受两个参数,一个是集合名称,另一个是查询。

查询参数的第一个(0)索引始终是find()查询,这实际上是类如何解析查询参数的方式。

$query = array();
if(isset($this->query[0])){
	$query = $this->query[0];
}
	
$cursor = $this->getDbConnection()->{$this->collection}->find($query);
	
if(isset($this->query['sort'])){
	$cursor->sort($this->query['sort']);
}
	
if(isset($this->query['skip'])){
	$cursor->limit($this->query['skip']);
}
	
if(isset($this->query['limit'])){
	$cursor->limit($this->query['limit']);
}

目前,此类的查询参数仅接受上述显示的部分,目前不允许您直接获取游标。

注意:不要将游标放入此类中,它不会以PHP MongoDB驱动程序可以使用的方式保存到您的数据存储中。相反,您将被告知MongoCursor没有被其父类正确初始化。

版本控制

本项目使用语义版本控制2.0.0

许可证

此扩展在BSD 3条款下授权。简单来说:用它做什么都行。