MongoDB对象映射(PHP)

8

问题简介:

当我从MongoCursor::getNext()处接收到一个class T对象时,最佳的构建实践是什么?就目前而言,MongoCursorgetNext()函数返回一个array。我希望将该结果用作类型为Tobject

我应该为类型T编写自己的构造函数,接受一个array吗?是否存在一种通用的解决方案?例如,当类型T extends G,而G按常规方式(对于嵌套文档进行递归处理)执行任务时。

我是MongoDB的新手,我想使用一个美观的界面构建自己的通用映射器。

悬赏:

  • 哪些方法、模式可能性比较高,并且从PHP的角度来看,哪些方法最符合MongoDB的概念。

如果您能更详细地解释您想要的答案类型,我们可能可以提供一个,我的意思是除了为您编写代码之外,您想要什么? - Sammaye
你为我提供了一种非常建设性和有用的方法,我已经学到了很多关于你的技巧和实现。谢谢你! 此外,我也愿意接受任何不同的方法,如果有的话,我很乐意去看看。我正在尝试探索这个问题的所有可能性和模式。 - Dyin
1个回答

3

这个答案已经被重新编写了。

大多数数据映射器通过表示一个类或“模型”来工作。如果您希望允许通过单个对象进行多次访问(即$model->find()),通常会被认为该方法实际上不会返回自身的实例,而是一个数组或MongoCursor将类急切地加载到空间中。

这种范式通常与“活动记录”相关联。这是ORM、ODM和框架用于以某种方式与数据库通信的方法,不仅适用于MongoDB,还适用于SQL和任何其他数据库(如Cassandra、CouchDB等等)。

应立即注意的是,即使活动记录提供了很多功能,也不应在整个应用程序中使用它。有时直接使用驱动程序会更有利。出于这个原因,大多数ORM、ODM和框架都提供了快速轻松地直接访问驱动程序的能力。

正如许多人所说,没有轻量级的数据映射器。如果要将返回的数据映射到类,则会消耗资源,最后。这样做的好处是在操作对象时获得的功率。

活动记录非常擅长从PHP内部提供事件和触发器。一个很好的例子是我为Yii制作的ORM:https://github.com/Sammaye/MongoYii 它可以提供以下钩子:

  • afterConstruct
  • beforeFind
  • afterFind
  • beforeValidate
  • afterValidate
  • beforeSave
  • afterSave

应该注意的是,当涉及到像beforeSaveafterSave这样的事件时,MongoDB没有触发器(https://jira.mongodb.org/browse/SERVER-124),因此应用程序应该处理这个问题。除了应用程序需要处理此问题的显而易见的原因外,还可以通过能够调用您的本机PHP函数来操纵在接触数据库之前保存的每个文档,更好地处理保存函数。

大多数数据映射器都是通过使用PHP自己的类CRUD来表示它们的。例如,要创建新记录:

$d=new User();
$d->username='sammaye';
$d->save();

这是一种很好的方法,因为您创建了一个"新的"(https://github.com/Sammaye/MongoYii/blob/master/EMongoDocument.php#L46 显示了我如何在MongoYii中为新记录做准备)类来创建一个"新的"记录。这种语义上非常合适。
更新函数通常通过读取函数访问,您无法更新不知道存在的模型。这带我们进入下一步——填充模型。
为了处理不同的ORM、ODM和框架中的模型填充,它们采用不同的方法。例如,我的MongoYii扩展程序在每个类中使用名为model的工厂方法来返回其自身的新实例,以便我可以调用动态的findfindOne等方法。
一些ORM、ODM和框架将读取函数作为直接的static函数,并将它们自己变成工厂方法,而有些则使用单例模式,然而,我选择不这样做(https://dev59.com/ZW445IYBdhLWcg3w_PK3#4596323)。
大多数情况下,如果不是全部,都会实现某种形式的游标。这用于返回多个模型并直接包装(通常)MongoCursor以替换current()方法以返回一个预填充的模型。
例如,调用:
User::model()->find();

将返回一个EMongoCursor(在MongoYii中),该游标将存储使用User类实例化游标的事实,当像这样调用时:

foreach(User::model() as $k=>$v){
    var_dump($v);
}

在此处调用current()方法:https://github.com/Sammaye/MongoYii/blob/master/EMongoCursor.php#L102,返回一个新的模型实例。
有些ORM、ODM和框架会实现贪婪数组加载。这意味着它们会直接将整个结果作为模型数组加载到RAM中。我个人不喜欢这种方法,因为它浪费资源,并且在需要对旧记录进行更新以添加新功能时也不利于使用活动记录。
在我继续讲述之前,还有一个话题是MongoDB的无模式本质。使用PHP类与MongoDB的问题在于你希望拥有变量性质的MongoDB,同时又希望具备PHP的所有功能。这在SQL中很容易解决,因为它有预定义的模式,您只需要查询即可。 但是,MongoDB没有这样的东西。
这使得MongoDB中的模式处理相当危险。大多数ORM、ODM和框架要求您在现场预定义模式(例如Doctrine 2),并使用私有变量和get/set方法。在MongoYii中,为了使我的生活更轻松、优雅,我决定保留MongoDB的无模式本质,使用魔法来检测(https://github.com/Sammaye/MongoYii/blob/master/EMongoModel.php#L26是我的__gethttps://github.com/Sammaye/MongoYii/blob/master/EMongoModel.php#L47是我的__set),如果类中无法访问该字段,如果该字段在内部_attributes数组中,则返回null。同样,为了设置属性,我只需在内部_attributes变量中进行设置。
至于如何分配此模式,我将内部分配留给用户处理,但是,为了处理来自表单等的属性设置,我使用验证规则(https://github.com/Sammaye/MongoYii/blob/master/EMongoModel.php#L236)调用名为getSafeAttributeNames()的函数,该函数将返回具有验证规则的属性列表。如果它们没有验证规则,则不会设置那些存在于传入的$_POST$_GET数组中的属性。因此,这提供了一种具有模式、又安全的模型结构。
因此,我们已经讨论了如何实际使用根文档,您还可以询问数据映射器如何处理子文档。Doctrine 2和许多其他工具提供了基于完整类的子文档(http://docs.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/embedded-mapping.html),但这可能会非常耗费资源。相反,我决定提供帮助函数,允许在不急于将它们加载到模型中并消耗RAM的情况下灵活使用子文档。基本上,我所做的就是将它们保留为它们自己,并为其中的验证器(https://github.com/Sammaye/MongoYii/blob/master/validators/ESubdocumentValidator.php)提供一个验证器,以进行内部验证。当然,该验证器是自生成的,因此,如果您在验证器中有一个规则,该规则再次使用验证器来发出嵌套子文档的验证,则它将起作用。
因此,我认为这完成了ORM、ODM和框架使用数据映射器的基本讨论。当然,我可能可以写一篇关于此的完整论文,但我认为这是足够好的讨论。

如果您正在扩展MongoCursor,那么为什么不将其扩展为EMongoCursor?在getNext()中,$this->partial是什么?当您授予T实际与数据库通信的权利时,例如:$t->update(),您对此方法有何看法? - Dyin
@Dyin 我强烈建议你不要扩展MongoCursor的c对象,这总会导致不好的结果。$this->partial是一个错误,它实际上是从我的Yii ORM中取出并修改为独立使用的。至于T通信,我默认情况下确实实现了Yii的活动记录,如果你能理解,这里是代码库:https://github.com/Sammaye/MongoYii,这里是更通用的一个:https://github.com/Sammaye/mongoglue - Sammaye
请问您能否通过添加有关设计和方法决策的注释来改进您的答案?这是数据映射器模式的轻量级版本吗?也许更多的使用示例会更好地理解您的方法。 - Dyin
@Dyin 我稍后会重新写答案,编译一个正确的。 - Sammaye
@Dyin 好的,我已经完成了一些东西,请让我知道如果你需要任何直接涵盖的内容。 - Sammaye

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接