上周又接到一个研究性质的任务,最近比较忙碌,所以这两天才开始分析代码,由于数据表相关的内容会比较多,所以本文将分成以下几部分。
- 商店的数据关系
- 一对多(hasMany)关系的处理
- 从属(belongsTo)关系的处理
- 多对多(manyToMany)关系的处理
- 一对一(oneToOne)关系的处理
- 总结
本文使用的测试环境如下:
- php 5.2.6
- apache 2.2.8
- mysql 5.0.67
- symfony 1.2.4-DEV
一. 商店的数据关系
我们用一个例子来对symfony中的表关联进行分析,我们假设商店有如下的数据关系。
图1.1

这里涉及到4类对象实体,分别是City(城市),Shop(商店),Shopinfo(商店信息), Tag(标签)。其中又涉及到了三种数据关系,一个城市可以有多家商店(一对多),一个商店拥有一个商店信息(一对一),一个商店可以拥有很多特点(多对多),这里我们用Tag来表示特点(特性)。
首先我们设计数据库结构config/schema.yml
图1.2

然后我们自动生成相关的库文件
$ ./symfony propel:build-all
首先我们来分析City和Shop之间的一对多/从属关系.
二.一对多(hasMany)关系的处理
我们先来看propel自动生成的BaseCity对象
图1.3

其他和关联没有关系的表这里不做过多描述, 我们重点介绍下面几个方法.
clearShops();
清空当前已经获得的关联商店, 实际上这个清空操作并没有真正意义上的进行清空或者删除,只是清空了当前取得的关联对象集合.
示例2.1(通过测试):
$mc = new Shop;
$mc->setName('MC in Beijing');
$city->addShop($mc);
$city->initShops();
$wc = new Shop;
$wc->setName('WC in Beijing');
$city->addShop($wc);
$city->save();
initShops();
清空当前已经获得的关联商店, 实际上这个清空操作并没有真正意义上的进行清空或者删除,只是清空了当前取得的关联对象集合. 该方法与clearShops()类似, 但该方法将Shop集合初始化为一个空的数组而clearShops()将Shop集合设置为null.
示例2.2(通过测试):
$mc = new Shop;
$mc->setName('MC in Beijing');
$city->addShop($mc);
$city->initShops();
$wc = new Shop;
$wc->setName('WC in Beijing');
$city->addShop($wc);
$city->save();
getShops($criteria, $con);
根据条件$criteria返回city下相关联的shop对象集合.
示例2.3(通过测试):
//我们用下面代码获得某城市下全部的KFC(肯德基)店铺
$c = new Criteria;
$c->add(ShopPeer::NAME, '%KFC%', Criteris::LIKE);
foreach($city->getShops($c) as $shop){
echo $shop->getId();
}
countShops($criteria, $con);
countShops()方法用来返回满组织定条件的关联Shop个数
示例2.4(通过测试):
//我们用下面代码获得某城市下全部的KFC(肯德基)的个数
$c = new Criteria;
$c->add(ShopPeer::NAME, '%KFC%', Criteris::LIKE);
echo $city->getName() . ' has ' . $city->countShops($c) . ' KFC shops.';
addShop($shop);
给city添加一个关联的shop, 参数$shop是一个shop对象
示例2.5(通过测试):
//我们用下面代码在某城市新加一个KFC店铺
$shop = new Shop;
$shop->setName('KFC 人民大街店');
$city->addShop($shop);
$city->save()
clearAllReferences($deep);
清空当前已经获得的关联对象, 实际上这个清空操作并没有真正意义上的进行清空或者删除,只是清空了当前已经取得的关联对象集合. 由于city只和shop进行了关联, 所以对于city对象来说其实该方法的效果和clearShops()是相同的.
示例2.6:
$mc = new Shop;
$mc->setName('MC in Beijing');
$city->addShop($mc);
$city->clearAllReferences();
$wc = new Shop;
$wc->setName('WC in Beijing');
$city->addShop($wc);
$city->save();
三.从属(belongsTo)关系的处理
下图为BaseShop对象的结构
图3.1 BaseShop

前面很多方法和BaseCity都是一样的, 而与BaseCity不同并与city相关的只有以下方法.
setCity(City $city)
指定所属city, 参数$city是city对象
示例3.1(通过测试):
//添加Changchun和KFC并同时创建关联
$city = new City;
$city->setName('Changchun');
$shop->new Shop;
$shop->setName('KFC in Changchun');
$shop->setCity($city);
$shop->save();
getCity($con)
返回所属城市,返回值为city对象.
示例3.2(通过测试):
// 输出店铺所在城市名
echo $shop->getCity()->getName();
四. 多对多(manyToMany)关系的处理
我们继续看例子, Shop对象和Tag对象通过Shop_Tag对象进行多对多的关联, 下面我们来看一下这三个对象都生成了哪些方法.
图4.1

图4.2

在symfony中多对多关系相对要复杂一些, 整个过程涉及到3种对象, 其中涉及到的方法也要多一些.
其中Shop对象的相关方法有
clearShopTags();
参考示例2.1
initShopTags();
参考示例2.2
getShopTags($criteria)
返回所有满足条件的相关联的ShopTag对象
示例4.1(通过测试):
foreach($shop->getShopTags() as $shopTag){
echo $shopTag->getTag()->getName();
}
countShopTags($criteria);
参考示例2.4
addShopTag($shoptag);
添加一个ShopTag关联, 参数$shoptag是ShopTag对象
我并没有想明白这个方法是用来作什么的, 按照正常的逻辑创建一个多对多关联应该是示例化一个新的ShopTag而不应该在Shop中addShopTag().
示例4.2(通过测试):
$shopTag = new ShopTag;
$shopTag->setTagId(2);
$shop->addShopTag($shopTag);
$shop->save();
getShopTagsJoinTag($criteria);
返回所有符合查询条件的ShopTag关联对象并同时取出关联的Tag对象.
在使用getShopTagsJoinTags()的时候你会发现用法其实和getShopTags()是一样的, 但使用getShopTagJoinTags()的之后再使用shopTag的getTag()方法实际上就不进行数据库查询了. 下面的例子要比getShopTags()的例子少很多次查询.
示例4.3(通过测试):
foreach($shop->getShopTagsJoinTag() as $shopTag){
echo $shopTag->getTag()->getName();
}
clearAllReferences($deep);
参考示例2.6
Tag对象的相关方法有
clearShopTags();
参考示例2.1
initShopTags();
参考示例2.2
getShopTags($criteria)
参考示例4.1
countShopTags($criteria);
参考示例2.4
addShopTag($shoptag);
参考示例4.2
getShopTagsJoinShop($criteria);
参考示例4.3
clearAllReferences($deep);
参考示例2.6
ShopTag对象的相关方法有
getShop();
返回关联的Shop对象
示例4.4(通过测试):
// 输出与tag关联的全部shop的name
foreach($tag->getShopTags() as $shopTag){
echo $shopTag->getShop()->getName();
}
setShop($shop);
设置关联Shop, $shop参数是Shop对象
getTag();
返回关联的Tag对象
示例4.5(通过测试):
// 输出与shop关联的全部tag的name
foreach($shop->getShopTags() as $shopTag){
echo $shopTag->getTag()->getName();
}
setTag($tag);
设置关联Tag, $tag参数是Tag对象
clearAllReferences($deep);
参考示例2.6
五.一对一(oneToOne)关系的处理
首先, 我们来看一下propel自动生成的Shopinfo对象, Shop对象在图3.1中已经有介绍.
图5.1 BaseShopinfo

经过几天的探索才成功的找到了进行一对一关联的方法.我们必须要将shopinfo中的外键设定为shopinfo的主键才可以被propel处理.
下面这几种都是错误的关联设计.
shop:
..id:
..shopinfo_id:
..name:
shopinfo:
..id:
..add:
..tel:
shop:
..id:
..name:
shopinfo:
..id:
..shop_id:
..add:
..tel:
shop:
..id:
..shopinfo_id:
..name:
shopinfo:
..id:
..shop_id:
..add:
..tel:
正确的一对一关联设计应该是
shop:
..id:
..name:
shopinfo:
..shop_id:
..add:
..tel:
根据图3.1和图5.1, Shop对象拥有方法setShopinfo()和getShopinfo()方法来操作唯一的关联店铺信息, 而Shopinfo对象也拥有setShop()和getShop()方法来操作唯一的关联店铺.
示例5.1:
//给商店添加店铺信息
$shop->setShopinfo(new Shopinfo);
$shop->getShopinfo()->setAddress('a Place');
$shop->getShopinfo()->setTel('010-12345678');
$shop->save();
示例5.2:
// 修改店铺的店铺信息
$shop->getShopinfo()->setTel('010-87654321');
$shop->save();
六.总结
到这里为止我们就把symfony中propel对数据关联的操作介绍的差不多了, 可能还有一些方面没有详细的介绍也只能考各位自己钻研了. 介于本人能力有限, 本文难免会有错误和遗漏, 希望大家可以指出, 感慨一下, 这篇文章从策划到结束一共用了4天.
附件是本文进行测试的项目文件, 内无symfony库, 请将symfony/lib目录cp到lib目录下并重命名为symfony.
附件: references.tgz
(本文完)