Propel ORM:完全递归地水合具有外键关系的通用模型对象,用于 JSON 序列化
我是 Propel ORM(以及一般的 ORM)的新手,并试图了解如何正确、干净地解决 MySQL 数据库中的外键关系。
从 3 个简单的 MySQL 表开始:
Hero:
- id
- 名称
- 描述
Skill:
- id
- 名称
- 等级
Hero_skill:
- id
- Hero_id
- Skill_id
其中 Hero_skill 具有预期的外键属性,并生成所有 Propel 类。
在 Propel 中,如何通过正确引用其所有技能以通用方式来完全“水化”“英雄”对象?
如何使用特定函数完成此操作的简单示例:
function getAllTheHeroes(){
$query = PropelQuery::from("Hero")
$heroes = $query->find(); //hero model objects
foreach($heroes as $hero){
$heroSkills = $hero->getHeroSkills(); //hero_skill model objects
foreach($heroSkills as $heroSkill){
$skill = $heroSkill->getSkill(); //skill model object.
}
echo $hero->exportTo('JSON'); //export to JSON string (new in Propel 1.6)
}
}
因此,上面的代码获取所有英雄,迭代并获取 Hero_id 的所有 Hero_skill 记录的集合,然后迭代并获取 Skill_id 的技能记录。重要的是,它将所有这些已解析的关系作为引用打包到 Hero 对象中,因此调用 getWHATEVER() 使用该数据“水合”该对象,所有这一切使得当每个英雄导出到 JSON 时,它都有一个列表该英雄的所有技能记录,而不仅仅是 ID。这可以按预期工作,并且只要您编写自定义函数来处理每种对象类型就可以了。来自此的 JSON 类似于:
[{
"Id":1,
"Name":"Hero McHeroington",
"Description":"Awesome.",
"HeroSkills":
{
"HeroSkill_0":
{
"Id":1,
"HeroId":1,
"SkillId":2,
"Skill":
{
"Id":2,
"Name":"Interpretive Dance",
"Rank":5
}
},
"HeroSkill_1":
{
"Id":2,
"HeroId":1,
"SkillId":4,
"Skill":
{
"Id":4,
"Name":"Pottery",
"Rank":2
}
},
"HeroSkill_2":
{
"Id":3,
"HeroId":1,
"SkillId":5,
"Skill":
{
"Id":5,
"Name":"Walking",
"Rank":1
}
}
}
},{
"Id":2,
"Name":"Squire McTinyPants",
"Description":"Chipper.",
"HeroSkills":
{
"HeroSkill_0":
{
"Id":4,
"HeroId":2,
"SkillId":2,
"Skill":
{
"Id":2,
"Name":"Interpretive Dance",
"Rank":10
}
},
"HeroSkill_1":
{
"Id":5,
"HeroId":2,
"SkillId":3,
"Skill":
{
"Id":3,
"Name":"Peeping",
"Rank":6
}
},
"HeroSkill_2":
{
"Id":6,
"HeroId":2,
"SkillId":6,
"Skill":
{
"Id":6,
"Name":"Skipping",
"Rank":4
}
}
}
}]
Blah Blah。
我想做的是更通用的事情:
function getModel($byClassName){
$pq = PropelQuery::from($byClassName)
$recs = $pq->find(); // <- collection of records of unknown type
foreach ($recs as $rec){
//Somehow retrieve all of the 'getFOREIGNKEY' Propel generated helper functions and call them all in sequence,
// then iterate down into any of these logical children objects and repeat the process to fully resolve all Foreign Key relationships within this Object
// and all this Objects 'children'
// AND not get stuck in infinite loops due to Many-To-Many relationships from the 'getWHATEVER' calls running in circles.
//Or some other method entirely to accomplish the same thing that i am just not getting...
echo $rec->exportTo('JSON'); //export to JSON string (new in Propel 1.6)
}
}
我正在深入研究 Propel 文档和源代码,但我仍然没有完全理解整个 Peer/TableMap/RelationMap/etc 模型,而且我正在接近开箱即用的解决方案,但不断陷入死胡同。
但这似乎并不是我想要在这里实现的一个非常独特的概念,所以我希望这里有一位 Propel 大师可以快速指出我在这方面的落后之处。
谢谢!
编辑:(回应 wimvds 评论) 通用方法的目标是允许我在数据库级别配置关系,然后推进 PHP 类,并将它们用作对象,而不必担心底层数据存储。
使用特定方法,我必须在数据库中定义这些关系,然后编写自定义“水合”函数(如上所列)以从数据存储中正确检索对象。这种方法的哲学问题是复制,因为如果我更改数据库中的关系,然后重新运行 propel-gen 那么我还必须更新我的水合函数以反映结构的变化。似乎所有的部分都应该在那里,以允许跳过最后一步并删除复制。
例如,对于上面的小型特定情况水化函数,我所做的就是根据我脑海中的知识调用 getWHATEVER() 辅助函数,即 Hero_skill 与英雄和技能相关,并且当我获得我想要的英雄时查看hero_skill并获取对应的技能列表。由于外键,Propel 也知道这些关系,并且当使用 joinWith() 时,它甚至知道递归(将递归对象引用设置为字符串值“*RECURSION”),因此 Propel 的所有部分都已就位为了智能地自动水合这些对象,同时优雅地解决/忽略递归,我只是找不到正确的语法来实现这一点。
即:获取一个对象,解析它的 FK 关系,水合所有“子”对象(FK 相关表中的行)并排除递归对象。
我想我正在寻找一种完全抽象数据存储的方法,利用 Propel 基于数据库 schema.xml 定义的类生成能力,因此不必自定义编写一堆可能会发生变化的辅助函数,如果数据结构变化。因此允许我根据对象而不是数据库记录进行编码,这不是 ORM 的全部意义吗?
我希望能澄清一点,谢谢!
I'm new to Propel ORM (and ORMs in general) and trying to get my bearings a bit here regarding properly and cleanly resolving Foreign Key relationships in a MySQL database.
Starting with 3 simple MySQL tables:
hero:
- id
- name
- description
skill:
- id
- name
- rank
hero_skill:
- id
- hero_id
- skill_id
Where hero_skill has the expected foreign key properties, and with all the Propel classes generate.
How do I, in Propel, completely 'hydrate' a 'Hero' object with the proper references to all their Skills in a generic way?
Simple Example of how it can be done with a specific function:
function getAllTheHeroes(){
$query = PropelQuery::from("Hero")
$heroes = $query->find(); //hero model objects
foreach($heroes as $hero){
$heroSkills = $hero->getHeroSkills(); //hero_skill model objects
foreach($heroSkills as $heroSkill){
$skill = $heroSkill->getSkill(); //skill model object.
}
echo $hero->exportTo('JSON'); //export to JSON string (new in Propel 1.6)
}
}
So the above code gets all the heroes, iterates and gets a collection of all the hero_skill records for the hero_id, then iterates and gets skill records for the skill_id. The important thing is that it packs all these resolved relations as references into the Hero object, so the calls to getWHATEVER() 'hydrate' the object with that data, all this so that when each hero is exported to JSON, it has a list of all the skill records for this Hero, rather than just the ids. This works as expected and is fine AS LONG AS YOU WRITE CUSTOM FUNCTIONS TO HANDLE EACH OBJECT TYPE. The JSON from this is something akin to:
[{
"Id":1,
"Name":"Hero McHeroington",
"Description":"Awesome.",
"HeroSkills":
{
"HeroSkill_0":
{
"Id":1,
"HeroId":1,
"SkillId":2,
"Skill":
{
"Id":2,
"Name":"Interpretive Dance",
"Rank":5
}
},
"HeroSkill_1":
{
"Id":2,
"HeroId":1,
"SkillId":4,
"Skill":
{
"Id":4,
"Name":"Pottery",
"Rank":2
}
},
"HeroSkill_2":
{
"Id":3,
"HeroId":1,
"SkillId":5,
"Skill":
{
"Id":5,
"Name":"Walking",
"Rank":1
}
}
}
},{
"Id":2,
"Name":"Squire McTinyPants",
"Description":"Chipper.",
"HeroSkills":
{
"HeroSkill_0":
{
"Id":4,
"HeroId":2,
"SkillId":2,
"Skill":
{
"Id":2,
"Name":"Interpretive Dance",
"Rank":10
}
},
"HeroSkill_1":
{
"Id":5,
"HeroId":2,
"SkillId":3,
"Skill":
{
"Id":3,
"Name":"Peeping",
"Rank":6
}
},
"HeroSkill_2":
{
"Id":6,
"HeroId":2,
"SkillId":6,
"Skill":
{
"Id":6,
"Name":"Skipping",
"Rank":4
}
}
}
}]
Blah Blah.
What I would like to do is something more generic:
function getModel($byClassName){
$pq = PropelQuery::from($byClassName)
$recs = $pq->find(); // <- collection of records of unknown type
foreach ($recs as $rec){
//Somehow retrieve all of the 'getFOREIGNKEY' Propel generated helper functions and call them all in sequence,
// then iterate down into any of these logical children objects and repeat the process to fully resolve all Foreign Key relationships within this Object
// and all this Objects 'children'
// AND not get stuck in infinite loops due to Many-To-Many relationships from the 'getWHATEVER' calls running in circles.
//Or some other method entirely to accomplish the same thing that i am just not getting...
echo $rec->exportTo('JSON'); //export to JSON string (new in Propel 1.6)
}
}
I'm digging through the Propel Docs and Source, and I still haven't wrapped my head around the whole Peer/TableMap/RelationMap/etc model, and I'm getting close to a solution out of the box, but keep hitting dead ends.
BUT this does not seem like a terribly unique concept that I'm trying to make happen here, so I'm hoping there is a Propel guru on here who can quickly point out where I'm being retarded about this.
Thanks!
EDIT: (In response to wimvds comment)
The goal of the generic approach is to allow me to configure the relationships at the database level, then propel-gen the PHP classes, and use them as Objects without having to worry about the underlying datastore.
With a specific approach, I'd have to define those relationships in the DB, then write a custom 'hydration' function (as listed above) to properly retrieve the object from the datastore. The philosophical problem with that approach is replication, in that if I change a relationship in the database, then rerun propel-gen THEN I also have to update my hydration functions to mirror that change in structure. It seems like all the pieces should be there to allow for skipping that last step and remove the replication.
For example, with the small specific-case hydration function above, All I'm doing is calling the getWHATEVER() helper functions based on the knowledge in my head that hero_skill relates to both hero and skill, and when I get a hero I want to check hero_skill and get the corresponding skill list. Propel knows about these relations as well due to the Foreign Keys, and when using joinWith() it is even aware of recursion (sets recursive object refs to a string value of '*RECURSION'), so the pieces are all in place for Propel to intelligently auto-hydrate these objects while gracefully resolving/ignoring recursion, I just can't find the proper syntax to make that happen.
ie: get an Object, resolve it's FK relations, hydrate all 'child' Objects (rows in related tables by FK) and exclude recursive Objects.
I guess what I'm looking for is a way to completely abstract the datastore, take advantage of Propel's class generation ability based on database schema.xml definitions and thus not have to custom write a bunch of helper functions that may be subject to change if data structure changes. Thus allowing me to code in terms of Objects and not Database records, isn't that the whole point of an ORM?
I hope that clarifies a bit, thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我想您可以尝试使用 isCrossRef 表属性来简化您的查询/水合作用。
请参阅:http://www.propelorm.org/wiki/Documentation /1.5/WhatsNew#多对多关系
I guess that you could give a try to the isCrossRef table attribute to simplify your queries/hydrations.
See : http://www.propelorm.org/wiki/Documentation/1.5/WhatsNew#Many-to-ManyRelationships