[Yii2笔记]014 AR处理关联数据(Working with Relational Data) [ 技术分享 ]
说明
学习Yii Framework 2(易2框架)的过程是漫长的,也是充满乐趣的,以下是我学习Yii2框架时对官网英文资料(请参见原文网址)的翻译和代码实现,提供了较完整的代码,供你参考。不妥之处,请多多指正!
原文网址:
http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#relational-data
10、Working with Relational Data(处理关联数据)
本文主题:AR处理关联数据(Working with Relational Data)
10、Working with Relational Data(处理关联数据)
除了与独立的数据表对应而外,Active Record也可以与关联数据(related data)很好的协作,使用它们可以通过主键数据就能访问。例如 :用户数据与订单数据是相关联的,一个客户可以有一个或多个订单。要恰当的描述这种关联关系,你使用表达式$customer->orders就可以获取客户的订单,这将以Order的Active Record实例的数组形式返回客户的订单时信息。
声明关系(Declaring Relations)
//hasMany 要通过Active Record使用关联数据,你首先需要在Active Record类中声明关系。这项工作只需要为每一个相关联的AR声明一个关联方法(relation method)即可,如下所示:
class Customer extends ActiveRecord
{
// ...
public function getOrders()
{
//键名可以使用嵌套数组,如:authInfo.id,但健值只能是一级字段,如customer_id
return $this->hasMany(Order::className(), ['customer_id' => 'id']);
}
}
class Order extends ActiveRecord
{
// ...
public function getCustomer()
{
return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
}
}
在上例中,我们为Customer类声明了一个orders关联,为Order类声明了一个customer关联。 每个关联方法必须被命名为getXyz,我们将xyz(第一个字母小写)称为关联名,注意:关联名是大小写敏感的。
当声明一个关联时,你可以定义以下信息: 关联的多样性属性:调用hasMany()或hasOne()来定义。在上例中,你可以在关联声明中看到:一个客户可以有多个订单,一个订单只能有一个客户。 关联的AR类名称:它将作为hasMany()或hasOne()的第一个参数。一个推荐的做法是调用Xyz::className()来获取类名称,这样你可以获得IDE自动完成的支持,在编译阶段期实现错误检测。 两种类型数据的连接关系:定义两种类型数据相关联的字段名称,数组值 是主键(由正在声明关系的AR类表示),数组键名是关联数据所对应的列。 要记住这种关系也很简单,正如你在上例中所看到的,你可以在关联AR后面直接写上属性于它的相关列。你看到的customer_id是Order的列,id是Customer的列。
获取关联数据(Accessing Relational Data)
声明关联后,你可以使用关联名来获取关联数据。你只需要象获取对象属性一样使用关联方法,正因如此,我们也称之为关联属性,例如:
// SELECT * FROM `customer` WHERE `id` = 123
$customer = Customer::findOne(123);
// SELECT * FROM `order` WHERE `customer_id` = 123
// $orders is an array of Order objects
$orders = $customer->orders;
信息:当你使用getter方法getXyz()声明一个关联名xyz时,你就可以象对象属性一样获取xyz,注意:关联名是区分大小写的。
如果一个关联属性使用hasMany()来声明,获取关联属性将返回一个AR实例的数组;如果关联属性使用hasOne()来声明,获取关联属性将返回相关联的一个AR实例,如果没有找到则返回null。 当你第一次获取关联属性时,如下例所示,一个SQL语句将被执行。如果相同的属性被再次获取时,无需再次执行SQL语句,之前的结果将直接被返回。要强制执行SQL语句,你要先释放掉关联属性:unset($customer->orders)。
注意:关联属性的概念与对象属性的特征有些相似,但它们是有很大区别的。普通对象属性的值类型是由getter方法所定义的,而关联方法返回的是一个yii\db\ActiveQuery实例,获取到的关联属性也将是一个yii\ActiveRecord实例或数组:
$customer->orders; // is an array of `Order` objects
$customer->getOrders(); // returns an ActiveQuery instance
这对于创建自定义查询是非常有用的,在下一章节将详细描述。
关联数据实例:
文件位置:
D:\phpwork\basic\models\AuthorInfo.php
源代码:
class AuthorInfo extends ActiveRecord{
public function attributes()
{
return [
'_id',
'name',
'level',
];
}
public function getArticles()
{
//authInfo.id与authId都可以实现关联查找!棒极了
//表author_info的_id字段与表article_info的authInfo.id字段相对应,一对多的关系
//根据作者查找相关的所有文章记录
return $this->hasMany(ArticleInfo::className(), ['authInfo.id' => '_id']);
//authInfo.id和authId都是ObjectId类型,否则无法查找到匹配的数据
// return $this->hasMany(ArticleInfo::className(), ['authId' => '_id']);
}
}
文件位置:
D:\phpwork\basic\models\ArticleInfo.php
源代码:
class ArticleInfo extends ActiveRecord{
public function attributes()
{
return [
'_id',
'title',
'content',
'authInfo',
'authId',
'createInfo',
'updateInfo',
'isDeleted',
'level',
'click',
];
}
public function getAuthor()
{
//hasOne()一级数据authId是可以使用的
//表article_info的authId字段与表author_info的_id字段相对应,一对一的关系
//根据文章查找一个作者记录
return $this->hasOne(AuthorInfo::className(), ['_id' => 'authId']);
//错误:hasOne()中是无法使用嵌套数据authInfo.id的
//return $this->hasOne(AuthorInfo::className(), ['_id' => 'authInfo.id']);//此种写法报错
}
文件位置:
D:\phpwork\basic\controllers\ArController.php
源代码:
public function actionRelation() {
//查找到一条author记录
$author=AuthorInfo::findOne(['name'=>'abc']);
//查找此author相关的所有文件
$article=$author->articles;//此属性可以全部找出来
echo "<br>Num:".count($article);//2,找到两条匹配记录
//查找一条article记录
$articleInfo=ArticleInfo::findOne(['title'=>'Yii2 is OK!']);
//查找此article相关的一个作者信息
$authorMy=$articleInfo->author;
echo "<br>MyNum:".count($authorMy);//1,找到一条匹配记录
echo "<br>".var_export($authorMy,true);
}
测试结果:
/*
Num:2
MyNum:1
app\models\AuthorInfo::__set_state(array( '_attributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a14599e4017a1c1600002b', )), 'name' => 'gulong', 'level' => '10', ), '_oldAttributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a14599e4017a1c1600002b', )), 'name' => 'gulong', 'level' => '10', ), '_related' => array ( ), '_errors' => NULL, '_validators' => NULL, '_scenario' => 'default', '_events' => array ( ), '_behaviors' => array ( ), ))
*/
动态关联查询(Dynamic Relational Query)
关联方法将会返回yii\db\ActiveQuery的一个实例,所以在执行DB查询之前,你可以使用查询构建方法构建这个查询,例如:
$customer = Customer::findOne(123);
// SELECT * FROM `order` WHERE `customer_id` = 123 AND `subtotal` > 200 ORDER BY `id`
$orders = $customer->getOrders()
->where(['>', 'subtotal', 200])
->orderBy('id')
->all();
动态关联查询mongoDB实例
文件位置:
D:\phpwork\basic\controllers\ArController.php
源代码:
public function actionRelation() {
$author=AuthorInfo::findOne(['name'=>'abc']);
//示意SQL:SELECT * FROM `articles` WHERE `articles.author_id` = `$author.Id` AND `level` > 15 ORDER BY `id` desc
$article=$author->getArticles()//这是一个Active Query实例
->select(['title','level'])
->where(['>','level',15])
->orderBy(['_id'=>SORT_DESC])
->all();
echo "OK<br>".var_export($article,true);
//return $this->render('relation');
}
测试结果:
/*
OK
array ( 0 => app\models\ArticleInfo::__set_state(array( '_attributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a14707e4017aa01a000029', )), 'title' => 'Are you OK?aaabbb', 'level' => 123, ), '_oldAttributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a14707e4017aa01a000029', )), 'title' => 'Are you OK?aaabbb', 'level' => 123, ), '_related' => array ( ), '_errors' => NULL, '_validators' => NULL, '_scenario' => 'default', '_events' => array ( ), '_behaviors' => array ( ), )), )
*/
源代码:
$article=$author->getArticles()
->select(['title','level'])
->all();
测试结果:
/*
OK
array ( 0 => app\models\ArticleInfo::__set_state(array( '_attributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a14707e4017aa01a000029', )), 'title' => 'Are you OK?aaabbb', 'level' => 123, ), '_oldAttributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a14707e4017aa01a000029', )), 'title' => 'Are you OK?aaabbb', 'level' => 123, ), '_related' => array ( ), '_errors' => NULL, '_validators' => NULL, '_scenario' => 'default', '_events' => array ( ), '_behaviors' => array ( ), )), 1 => app\models\ArticleInfo::__set_state(array( '_attributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a41ee6e4017a7c0c000030', )), 'title' => 'Hello Yii2', 'level' => 12, ), '_oldAttributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a41ee6e4017a7c0c000030', )), 'title' => 'Hello Yii2', 'level' => 12, ), '_related' => array ( ), '_errors' => NULL, '_validators' => NULL, '_scenario' => 'default', '_events' => array ( ), '_behaviors' => array ( ), )), )
*/
源代码:
$article=$author->getArticles()
->select(['title','level'])
->orderBy(['_id'=>SORT_DESC])
->all();
测试结果:
/*
OK
array ( 0 => app\models\ArticleInfo::__set_state(array( '_attributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a41ee6e4017a7c0c000030', )), 'title' => 'Hello Yii2', 'level' => 12, ), '_oldAttributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a41ee6e4017a7c0c000030', )), 'title' => 'Hello Yii2', 'level' => 12, ), '_related' => array ( ), '_errors' => NULL, '_validators' => NULL, '_scenario' => 'default', '_events' => array ( ), '_behaviors' => array ( ), )), 1 => app\models\ArticleInfo::__set_state(array( '_attributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a14707e4017aa01a000029', )), 'title' => 'Are you OK?aaabbb', 'level' => 123, ), '_oldAttributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a14707e4017aa01a000029', )), 'title' => 'Are you OK?aaabbb', 'level' => 123, ), '_related' => array ( ), '_errors' => NULL, '_validators' => NULL, '_scenario' => 'default', '_events' => array ( ), '_behaviors' => array ( ), )), )
*/
与获取一个关联属性不同的是,每次你都将通过关联方法执行一个动态关联查询,无论其前是否执行过此动态关联查询,一个SQL语句都将被执行。
有时,你可能想要固化关联查询的参数,这样你在执行关联查询时可以更容易些,例如,你可以定义一个bigOrders关联方法,代码如下:
class Customer extends ActiveRecord
{
public function getBigOrders($threshold = 100)
{
return $this->hasMany(Order::className(), ['customer_id' => 'id'])
->where('subtotal > :threshold', [':threshold' => $threshold])
->orderBy('id');
}
}
然后你可以执行以下查询:
// SELECT * FROM `order` WHERE `customer_id` = 123 AND `subtotal` > 200 ORDER BY `id`
$orders = $customer->getBigOrders(200)->all();
// SELECT * FROM `order` WHERE `customer_id` = 123 AND `subtotal` > 100 ORDER BY `id`
$orders = $customer->bigOrders;
固化关联查询参数的实例
文件位置:
D:\phpwork\basic\models\AuthorInfo.php
源代码:
class AuthorInfo extends ActiveRecord{
......
public function getArticles($level=10)
{
return $this->hasMany(ArticleInfo::className(), ['authInfo.id' => '_id'])
->where(['>','level',$level])
//->asArray()//可以转为数组输出
->orderBy(['_id'=>SORT_DESC]);
}
文件位置:
D:\phpwork\basic\controllers\ArController.php
源代码:
public function actionRelation() {
$author=AuthorInfo::findOne(['name'=>'abc']);
$article=$author->getArticles(15)
->select(['title','level'])
->all();
echo "Count:".count($article)."<br>".var_export($article,true);
}
测试结果:
/*
Count:1
array ( 0 => app\models\ArticleInfo::__set_state(array( '_attributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a14707e4017aa01a000029', )), 'title' => 'Are you OK?aaabbb', 'level' => 123, ), '_oldAttributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a14707e4017aa01a000029', )), 'title' => 'Are you OK?aaabbb', 'level' => 123, ), '_related' => array ( ), '_errors' => NULL, '_validators' => NULL, '_scenario' => 'default', '_events' => array ( ), '_behaviors' => array ( ), )), )
*/
源代码:
->asArray()查询结果
测试结果:
/*
Count:1
array ( 0 => array ( '_id' => MongoId::__set_state(array( '$id' => '58a14707e4017aa01a000029', )), 'title' => 'Are you OK?aaabbb', 'level' => 123, ), )
*/
源代码:
public function actionRelation() {
$author=AuthorInfo::findOne(['name'=>'abc']);
$article=$author->articles;
echo "Count:".count($article)."<br>".var_export($article,true);
}
测试结果:
/*
Count:2
array ( 0 => app\models\ArticleInfo::__set_state(array( '_attributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a41ee6e4017a7c0c000030', )), 'createInfo' => array ( 'time' => 1487150822, 'ip' => '::1', ), 'isDeleted' => false, 'authInfo' => array ( 'id' => MongoId::__set_state(array( '$id' => '58a14587e4017a1c1600002a', )), 'name' => 'abc', 'level' => '18', ), 'title' => 'Hello Yii2', 'content' => 'Hello Yii2Content', 'updateInfo' => array ( 'time' => 1488440017, 'ip' => '::1', ), 'level' => 12, 'click' => 3, 'authId' => MongoId::__set_state(array( '$id' => '58a14587e4017a1c1600002a', )), ), '_oldAttributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a41ee6e4017a7c0c000030', )), 'createInfo' => array ( 'time' => 1487150822, 'ip' => '::1', ), 'isDeleted' => false, 'authInfo' => array ( 'id' => MongoId::__set_state(array( '$id' => '58a14587e4017a1c1600002a', )), 'name' => 'abc', 'level' => '18', ), 'title' => 'Hello Yii2', 'content' => 'Hello Yii2Content', 'updateInfo' => array ( 'time' => 1488440017, 'ip' => '::1', ), 'level' => 12, 'click' => 3, 'authId' => MongoId::__set_state(array( '$id' => '58a14587e4017a1c1600002a', )), ), '_related' => array ( ), '_errors' => NULL, '_validators' => NULL, '_scenario' => 'default', '_events' => array ( ), '_behaviors' => array ( ), )), 1 => app\models\ArticleInfo::__set_state(array( '_attributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a14707e4017aa01a000029', )), 'createInfo' => array ( 'time' => 1486964487, 'ip' => '::1', ), 'isDeleted' => false, 'authInfo' => array ( 'id' => MongoId::__set_state(array( '$id' => '58a14587e4017a1c1600002a', )), 'name' => 'abc', 'level' => '18', ), 'title' => 'Are you OK?aaabbb', 'content' => 'Are you OK?Content', 'updateInfo' => array ( 'time' => 1488440000, 'ip' => '::1', ), 'level' => 123, 'click' => 1, 'authId' => MongoId::__set_state(array( '$id' => '58a14587e4017a1c1600002a', )), ), '_oldAttributes' => array ( '_id' => MongoId::__set_state(array( '$id' => '58a14707e4017aa01a000029', )), 'createInfo' => array ( 'time' => 1486964487, 'ip' => '::1', ), 'isDeleted' => false, 'authInfo' => array ( 'id' => MongoId::__set_state(array( '$id' => '58a14587e4017a1c1600002a', )), 'name' => 'abc', 'level' => '18', ), 'title' => 'Are you OK?aaabbb', 'content' => 'Are you OK?Content', 'updateInfo' => array ( 'time' => 1488440000, 'ip' => '::1', ), 'level' => 123, 'click' => 1, 'authId' => MongoId::__set_state(array( '$id' => '58a14587e4017a1c1600002a', )), ), '_related' => array ( ), '_errors' => NULL, '_validators' => NULL, '_scenario' => 'default', '_events' => array ( ), '_behaviors' => array ( ), )), )
*/
使用Junction表的关联(Relations via a Junction Tabel)
//Junction
在数据库模型中,当两个表之间的关系是多对多时,可以使用junction表。例如,order表和item表之间可以通过一个名为order_item的junction表来关联。一个订单项(order item)与多个产品项相关联,一个产品项(product item)也可以与多个订单项相关联。 当声明此种关系时,你可以使用via()或viaTable()(mongodb不支持viaTable())来定义junction表,via()和viaTable()之间的不同点是:前者使用一个已存在的关系名来定义junction表,后者则直接使用junction表,例如:
class Order extends ActiveRecord
{
public function getOrderItems()
{
return $this->hasMany(OrderItem::className(), ['order_id' => 'id']);
}
public function getItems()
{
return $this->hasMany(Item::className(), ['id' => 'item_id'])
//使用via()
->via('orderItems');
}
}
//等效于:
class Order extends ActiveRecord
{
public function getItems()
{
return $this->hasMany(Item::className(), ['id' => 'item_id'])
//使用viaTable()//mongodb不支持viaTable()
->viaTable('order_item', ['order_id' => 'id']);
}
}
Junction表声明的关联属性使用方法与普通关联属性方法是一样的,例如:
// SELECT * FROM `order` WHERE `id` = 100
$order = Order::findOne(100);
// SELECT * FROM `order_item` WHERE `order_id` = 100
// SELECT * FROM `item` WHERE `item_id` IN (...)
// returns an array of Item objects
$items = $order->items;
Junction表关联实例
文件位置:
D:\phpwork\basic\models\ArticleInfo.php
源代码:
//定义Junction表关联articleTags
public function getArticleTags()
{
return $this->hasMany(ArticleTag::className(), ['articleId' => '_id']);
}
//使用via()调用Junction表关联articleTags//tags,多对多的关联
public function getAllTags()
{
return $this->hasMany(Tags::className(), ['_id' => 'tagId'])
->select(['tag'])
->asArray()
//使用via()
->via('articleTags');
}
文件位置:
D:\phpwork\basic\controllers\ArticleController.php
源代码:
public function actionEditArticle($id='')
{
$model=new Article(['id'=>$id]);
if ($model->load(Yii::$app->request->post())) {
if (Yii::$app->request->isAjax) {
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
return \yii\bootstrap\ActiveForm::validate($model);
}
if($model->validate()){
$errorArr=$model->save();
if($errorArr){
//Data save error!
$this->errorDisplay($errorArr);
}else{
return $this->redirect(['index']);
}
}else{
//Data validate error!
$this->errorDisplay($model->getErrors());
}
}else{
$author=AuthorInfo::authorAll();
if(!$model->initInfo()){
return $this->redirect(['edit-article']);
}
return $this->render('editArticle',['model'=>$model,'author'=>$author]);
}
}
文件位置:
D:\phpwork\basic\models\Article.php
源代码:
public function initInfo()
{
if(!$this->isNewRecord){
$article=ArticleInfo::findOne(['_id'=>$this->id]);
if($article){
$this->title=$article->title;
$this->content=$article->content;
$this->level=$article->level;
$this->authId=ArrayHelper::getValue($article,'authInfo.id','');
//使用Junction表获取文章相关的所有标签
$this->tags=implode(',',ArrayHelper::getColumn($article->allTags,'tag'));
}else{
//如果不存在则自动转成新建
return false;
}
}
if($this->isNewRecord){
//新建记录初始化
}
return true;
}
测试结果:
/*
http://localhost:8081/article/edit-article?id=58a41ee6e4017a7c0c000030
最终获取到的标签:
Yii,HTML,Java
*/
惰性加载和主动加载(Lazy Loading and Eager Loading)
在获取关联数据中,我们说明了你可以象获取普通对象属性一样取得一个AR实例的关联属性,只有当你第一次获取关联属性时才会执行SQL语句。我们将这种关联数据获取方法称为惰性加载(lazy loading),例如:
// SELECT * FROM `customer` WHERE `id` = 123
$customer = Customer::findOne(123);
// SELECT * FROM `order` WHERE `customer_id` = 123
$orders = $customer->orders;
// no SQL executed
$orders2 = $customer->orders;
惰性加载使用起来非常方便,但是,当你要从多个Active Record实例中获取相同的关联属性时,可能会遇到性能问题的困扰,如下例代码所示,将会有多少个SQL语句被执行呢?
// SELECT * FROM `customer` LIMIT 100
$customers = Customer::find()->limit(100)->all();
foreach ($customers as $customer) {
// SELECT * FROM `order` WHERE `customer_id` = ...
$orders = $customer->orders;
}
正如你在代码中的注释所看到的,将会有101个SQL语句将被执行,这是因为在循环中每次你为每一个不同的Customer对象获取orders关联属性时,一个SQL语句都将被执行一遍。
预先加载(eager loading)
在SQL语句中使用with()将关联属性提前加载到数据集中。 要解决这个性能问题,你可以使用被称为预先加载(eager loading)的方法,代码如下:
// SELECT * FROM `customer` LIMIT 100;
// SELECT * FROM `orders` WHERE `customer_id` IN (...)
$customers = Customer::find()
->with('orders')
->limit(100)
->all();
foreach ($customers as $customer) {
// no SQL executed
$orders = $customer->orders;
}
预先加载实例
//with
文件位置:
D:\phpwork\basic\models\AuthorInfo.php
源代码:
public function getArticles(){
//使用预先加载,只能使用为['authId' => '_id'],而不能使用['authInfo.id' => '_id']
return $this->hasMany(ArticleInfo::className(), ['authId' => '_id'])
->orderBy(['_id'=>SORT_DESC])
->select(['title','authId'])
->asArray();
}
文件位置:
D:\phpwork\basic\controllers\AuthorController.php
源代码:
public function actionIndex(){
//->with('articles'),预先加载articles,即将作者所发表的所有文章数据都提前取出来,以备使用。
//预加载的关联属性数据是存放在*yii\db\BaseActiveRecord*_related中的,而常规AR属性的数据是存放在*yii\db\BaseActiveRecord*_attributes中的。
$articles=AuthorInfo::find()->with('articles')->all();
var_dump($articles);
}
测试结果:
/*
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a14587e4017a1c1600002a',
)),
'name' => 'abc',
'level' => '18',
'articles' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a41ee6e4017a7c0c000030',
)),
'title' => 'Hello Yii2',
'authId' =>
MongoId::__set_state(array(
'$id' => '58a14587e4017a1c1600002a',
)),
),
1 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a14707e4017aa01a000029',
)),
'title' => 'Are you OK?aaabbb',
'authId' =>
MongoId::__set_state(array(
'$id' => '58a14587e4017a1c1600002a',
)),
),
),
),
1 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a14599e4017a1c1600002b',
)),
'name' => 'gulong',
'level' => '10',
'articles' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a41f0ae4017a7c0c000031',
)),
'title' => 'Yii2 is OK!',
'authId' =>
MongoId::__set_state(array(
'$id' => '58a14599e4017a1c1600002b',
)),
),
),
),
)
*/
通过调用yii\db\ActiveQuery::with(),你使用一个SQL语句让Active Record带回了前100个客户的订单,这样,你将执行的SQL语句数量从101减到了2。 你可以提前加载一个或多个关联属性,甚至可以提前加载嵌套关联属性(nested relation)。嵌套关联属性是一个定义在关联AR类中的关联属性,例如,Customer使用orders与类Order相关联,Order使用items与类Item相关联,当查询Customer时,你可以使用嵌套关联属性标识"orders.items"预先加载(eagerly load)items。 下面的代码将展示with()的不同用法,我们假定Customer类有两个关联属性orders和country,Order类有一个关联属性items。
// eager loading both "orders" and "country"
$customers = Customer::find()->with('orders', 'country')->all();
// equivalent to the array syntax below
$customers = Customer::find()->with(['orders', 'country'])->all();
// no SQL executed
$orders= $customers[0]->orders;
// no SQL executed
$country = $customers[0]->country;
// eager loading "orders" and the nested relation "orders.items"
$customers = Customer::find()->with('orders.items')->all();
// access the items of the first order of the first customer
// no SQL executed
$items = $customers[0]->orders[0]->items;
嵌套关联属性预先加载实例,->with('articles.allTags')
文件位置:
D:\phpwork\basic\models\ArticleInfo.php
源代码:
public function getArticleTags()
{
return $this->hasMany(ArticleTag::className(), ['articleId' => '_id']);
}
public function getAllTags()
{
return $this->hasMany(Tags::className(), ['_id' => 'tagId'])
->select(['tag'])
->asArray()
//使用via()
->via('articleTags');
}
文件位置:
D:\phpwork\basic\models\AuthorInfo.php
源代码:
public function getArticles()
{
return $this->hasMany(ArticleInfo::className(), ['authId' => '_id'])
->orderBy(['_id'=>SORT_DESC])
->select(['title','authId'])
->asArray();
}
文件位置:
D:\phpwork\basic\controllers\AuthorController.php
源代码:
public function actionIndex(){
//对嵌套关联属性'articles.allTags'进行预先加载:AuthorInfo使用articles与AuthorInfo类相关联,AuthorInfo使用allTags与Tags类相关联
$articles=AuthorInfo::find()->with('articles.allTags')->asArray()->limit(2)->all();
var_dump($articles);
}
测试结果:
/*
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a14587e4017a1c1600002a',
)),
'name' => 'abc',
'level' => '18',
'articles' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a41ee6e4017a7c0c000030',
)),
'title' => 'Hello Yii2',
'authId' =>
MongoId::__set_state(array(
'$id' => '58a14587e4017a1c1600002a',
)),
'articleTags' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58bd3d92e4017a8c0c000029',
)),
'tagId' =>
MongoId::__set_state(array(
'$id' => '58b9284ce4017a900c000029',
)),
'articleId' =>
MongoId::__set_state(array(
'$id' => '58a41ee6e4017a7c0c000030',
)),
),
1 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58bd3d9de4017a8c0c00002a',
)),
'tagId' =>
MongoId::__set_state(array(
'$id' => '58b91929e4017a800c000029',
)),
'articleId' =>
MongoId::__set_state(array(
'$id' => '58a41ee6e4017a7c0c000030',
)),
),
2 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58bd3fcde4017a7c0c00002b',
)),
'tagId' =>
MongoId::__set_state(array(
'$id' => '58b928cae4017a8c0c000029',
)),
'articleId' =>
MongoId::__set_state(array(
'$id' => '58a41ee6e4017a7c0c000030',
)),
),
),
'allTags' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58b91929e4017a800c000029',
)),
'tag' => 'Yii',
),
1 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58b9284ce4017a900c000029',
)),
'tag' => 'HTML',
),
2 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58b928cae4017a8c0c000029',
)),
'tag' => 'Java',
),
),
),
1 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a14707e4017aa01a000029',
)),
'title' => 'Are you OK?aaabbb',
'authId' =>
MongoId::__set_state(array(
'$id' => '58a14587e4017a1c1600002a',
)),
'articleTags' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58bf9939e4017a7c0c00002a',
)),
'tagId' =>
MongoId::__set_state(array(
'$id' => '58b928cae4017a8c0c000029',
)),
'articleId' =>
MongoId::__set_state(array(
'$id' => '58a14707e4017aa01a000029',
)),
),
),
'allTags' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58b928cae4017a8c0c000029',
)),
'tag' => 'Java',
),
),
),
),
),
1 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a14599e4017a1c1600002b',
)),
'name' => 'gulong',
'level' => '10',
'articles' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a41f0ae4017a7c0c000031',
)),
'title' => 'Yii2 is OK!',
'authId' =>
MongoId::__set_state(array(
'$id' => '58a14599e4017a1c1600002b',
)),
'articleTags' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58bf9933e4017a7c0c000029',
)),
'tagId' =>
MongoId::__set_state(array(
'$id' => '58b928cae4017a8c0c000029',
)),
'articleId' =>
MongoId::__set_state(array(
'$id' => '58a41f0ae4017a7c0c000031',
)),
),
),
'allTags' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58b928cae4017a8c0c000029',
)),
'tag' => 'Java',
),
),
),
),
),
)
*/
多个关联属性预先加载实例->with('author','allTags')
文件位置:
D:\phpwork\basic\controllers\ArticleController.php
源代码:
public function actionIndex(){
// select(['title','authId']),记住:将关联属性涉及到的字段都要查找出来!
$articles=ArticleInfo::find()->with('author','allTags')->select(['title','authId'])->limit(2)->asArray()->all();
// with('author','allTags')或with(['author','allTags'])两种写法均可
//$articles=ArticleInfo::find()->with(['author','allTags'])->select(['title','authId'])->limit(2)->asArray()->all();
var_dump($articles);
}
文件位置:
D:\phpwork\basic\models\ArticleInfo.php
源代码:
public function getAuthor()
{
return $this->hasOne(AuthorInfo::className(), ['_id' => 'authId']);
}
public function getArticleTags()
{
return $this->hasMany(ArticleTag::className(), ['articleId' => '_id']);
}
public function getAllTags()
{
return $this->hasMany(Tags::className(), ['_id' => 'tagId'])
->select(['tag'])
->asArray()
//使用via()
->via('articleTags');
}
测试结果:
/*
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a14707e4017aa01a000029',
)),
'title' => 'Are you OK?aaabbb',
'authId' =>
MongoId::__set_state(array(
'$id' => '58a14587e4017a1c1600002a',
)),
'author' =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a14587e4017a1c1600002a',
)),
'name' => 'abc',
'level' => '18',
),
'articleTags' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58bf9939e4017a7c0c00002a',
)),
'tagId' =>
MongoId::__set_state(array(
'$id' => '58b928cae4017a8c0c000029',
)),
'articleId' =>
MongoId::__set_state(array(
'$id' => '58a14707e4017aa01a000029',
)),
),
),
'allTags' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58b928cae4017a8c0c000029',
)),
'tag' => 'Java',
),
),
),
1 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a41ee6e4017a7c0c000030',
)),
'title' => 'Hello Yii2',
'authId' =>
MongoId::__set_state(array(
'$id' => '58a14587e4017a1c1600002a',
)),
'author' =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a14587e4017a1c1600002a',
)),
'name' => 'abc',
'level' => '18',
),
'articleTags' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58bd3d92e4017a8c0c000029',
)),
'tagId' =>
MongoId::__set_state(array(
'$id' => '58b9284ce4017a900c000029',
)),
'articleId' =>
MongoId::__set_state(array(
'$id' => '58a41ee6e4017a7c0c000030',
)),
),
1 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58bd3d9de4017a8c0c00002a',
)),
'tagId' =>
MongoId::__set_state(array(
'$id' => '58b91929e4017a800c000029',
)),
'articleId' =>
MongoId::__set_state(array(
'$id' => '58a41ee6e4017a7c0c000030',
)),
),
2 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58bd3fcde4017a7c0c00002b',
)),
'tagId' =>
MongoId::__set_state(array(
'$id' => '58b928cae4017a8c0c000029',
)),
'articleId' =>
MongoId::__set_state(array(
'$id' => '58a41ee6e4017a7c0c000030',
)),
),
),
'allTags' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58b91929e4017a800c000029',
)),
'tag' => 'Yii',
),
1 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58b9284ce4017a900c000029',
)),
'tag' => 'HTML',
),
2 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58b928cae4017a8c0c000029',
)),
'tag' => 'Java',
),
),
),
)
*/
你可以提前加载更深的嵌套关联属性,例如 a.b.c.d,所有的父级关联属性将被更早的加载,也就是当你使用with()加载a.b.c.d时,你将依次加载a,a.b,a.b.c和a.b.c.d。
信息:通常,使用Junction表预先加载M个关联属性中的N个关联属性,总共将执行N+M+1次SQL语句,注意,嵌套关联属性a.b.c.d被认为是四个关联属性。
当预先加载了一个关联属性时,你可以使用匿名函数自定义对应的关联查询,例如:
// find customers and bring back together their country and active orders
// SELECT * FROM `customer`
// SELECT * FROM `country` WHERE `id` IN (...)
// SELECT * FROM `order` WHERE `customer_id` IN (...) AND `status` = 1
$customers = Customer::find()->with([
'country',
'orders' => function ($query) {
$query->andWhere(['status' => Order::STATUS_ACTIVE]);
},
])->all();
为一个关联属性自定义查询条件时,你需要将关联属性名定义为数组键名,然后使用一个匿名函数作为对应的数组值,匿名函数将接收一个$query 参数,此参数是用于关联属性执行查询的yii\db\ActiveQuery对象。在上例代码中,我们通过追加一个订单状态的查询条件来修改关联查询条件。
注意:当预先加载时,如果你调用了select(),你需要确定关联属性涉及到的字段被选中,否则,关联模型将不能正确被加载,例如:
$orders = Order::find()->select(['id', 'amount'])->with('customer')->all();
// $orders[0]->customer将一直为null,要修正此问题,你应做如下修改:
$orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all();
使用匿名函数增加一个自定义的查询条件:
源代码:
$articles=ArticleInfo::find()
->with([
'author',
//只保留tag='Java'的关联属性
'allTags' => function ($query) {
$query->andWhere(['tag' =>'Java']);
},
])
->select(['title','authId'])->limit(2)->asArray()->all();
var_dump($articles);
测试结果:
/*
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a14707e4017aa01a000029',
)),
'title' => 'Are you OK?aaabbb',
'authId' =>
MongoId::__set_state(array(
'$id' => '58a14587e4017a1c1600002a',
)),
'author' =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a14587e4017a1c1600002a',
)),
'name' => 'abc',
'level' => '18',
),
'articleTags' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58bf9939e4017a7c0c00002a',
)),
'tagId' =>
MongoId::__set_state(array(
'$id' => '58b928cae4017a8c0c000029',
)),
'articleId' =>
MongoId::__set_state(array(
'$id' => '58a14707e4017aa01a000029',
)),
),
),
'allTags' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58b928cae4017a8c0c000029',
)),
'tag' => 'Java',
),
),
),
1 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a41ee6e4017a7c0c000030',
)),
'title' => 'Hello Yii2',
'authId' =>
MongoId::__set_state(array(
'$id' => '58a14587e4017a1c1600002a',
)),
'author' =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58a14587e4017a1c1600002a',
)),
'name' => 'abc',
'level' => '18',
),
'articleTags' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58bd3d92e4017a8c0c000029',
)),
'tagId' =>
MongoId::__set_state(array(
'$id' => '58b9284ce4017a900c000029',
)),
'articleId' =>
MongoId::__set_state(array(
'$id' => '58a41ee6e4017a7c0c000030',
)),
),
1 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58bd3d9de4017a8c0c00002a',
)),
'tagId' =>
MongoId::__set_state(array(
'$id' => '58b91929e4017a800c000029',
)),
'articleId' =>
MongoId::__set_state(array(
'$id' => '58a41ee6e4017a7c0c000030',
)),
),
2 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58bd3fcde4017a7c0c00002b',
)),
'tagId' =>
MongoId::__set_state(array(
'$id' => '58b928cae4017a8c0c000029',
)),
'articleId' =>
MongoId::__set_state(array(
'$id' => '58a41ee6e4017a7c0c000030',
)),
),
),
'allTags' =>
array (
0 =>
array (
'_id' =>
MongoId::__set_state(array(
'$id' => '58b928cae4017a8c0c000029',
)),
'tag' => 'Java',
),
),
),
)
*/
//关联属性联合查询(Joining with Relations) 注意:此处内容仅适用于关系型数据库,例如:MySQL,PostgreSQL等。 关联查询只是描述了当查询主要数据时查找主表列的情况,实际应用中,我们常常需要在关联表中查找,例如,我们想要查找至少有一条订单记录的所有用户,要解决这个问题,我们可以构建一个联合查询,代码如下:
// SELECT `customer`.* FROM `customer`
// LEFT JOIN `order` ON `order`.`customer_id` = `customer`.`id`
// WHERE `order`.`status` = 1
// SELECT * FROM `order` WHERE `customer_id` IN (...)
$customers = Customer::find()
->select('customer.*')
->leftJoin('order', '`order`.`customer_id` = `customer`.`id`')
->where(['order.status' => Order::STATUS_ACTIVE])
->with('orders')
->all();
注意:当构建含有JOIN语句的关联查询时,消除列名称的歧义是很重要的,一个比较好的做法是将表名加在列名之前。
实例,leftjoin实例
文件位置:
D:\phpwork\basic\controllers\BlogController.php
源代码:
public function actionAuthorHaveBlog() {
$authors =\app\models\Author::find()
->select('author.name,author.id')//author.id必须得列出,否则会查询错误
->leftJoin('blog', '`blog`.`authorid` = `author`.`id`')
// ->leftJoin('blog', 'blog.authorid = author.id')//与上句等效
// ->leftJoin('blog', '`blog.authorid` = `author.id`')//此句语法错误
->where(['blog.isDeleted' => 0])
->with('blogs')//定义的关联属性名称为blogs
->all();
return $this->render('haveblog',['authors'=>$authors]);
}
public function actionJoinWith() {
//joinWith实例,与actionAuthorHaveBlog()同效
// http://localhost:8081/blog/join-with
$authors = \app\models\Author::find()
->joinWith('blogs')
//多个关联属性
->joinWith(['authors','user'])
->where(['blog.isDeleted' => 0])
->all();
//查找blog中的所有作者id(发表过文章的作者id,没有发表过的将不查找),在这些id中查找author表中的作者信息。
//$sql="select author.* from author left join blog on blog.authorid=author.id where blog.isDeleted=0";//对筛选出来的结果会自动合并同类项
return $this->render('haveblog',['authors'=>$authors]);
}
文件位置:
D:\phpwork\basic\views\blog\haveblog.php
源代码:
<h1><?= Html::encode($this->title) ?></h1>
<?
foreach ($authors as $index => $item) {
echo "<br>author:".$item['name'];
}
?>
文件位置:
D:\phpwork\basic\models\Author.php
源代码:
<?
class Author extends \yii\db\ActiveRecord{
public function getBlogs(){
return $this->hasMany(Blog::className(),['authorid'=>'id']);
}
测试结果:
http://localhost:8081/blog/author-have-blog
/*
发表过Blog的所有作者
author:jingyong
author:Bomer
author:Johns
author:Plark
*/
不管怎样,一个更好的方法是调用yii\db\ActiveQuery::joinWith()来编写上面的关联声明:
$customers = Customer::find()
->joinWith('orders')
->where(['order.status' => Order::STATUS_ACTIVE])
->all();'
上面两个方法都执行了同样的SQL语句,后一个则更清晰、简洁。
默认的,joinWith()使用LEFT JOIN连接主键表和它的关联表,你可以通过第3个参数定义一个不同的连接类型(例如RIGHT JOIN)。如果连接类型是INNER JOIN,你应直接使用innerJoinWith()。
调用joinWith()默认将预先加载数据(eagerly load),如果你不需要加载相关数据,你可以设置它的第2个参数$eagerLoading 为false。
与with()类似,你可以连接(join with)一个或多个关联属性;你可以随时自定义关联查询;你可以连接嵌套关联属性;你还可以混合使用with()和joinWith()。例如:
$customers = Customer::find()->joinWith([
'orders' => function ($query) {
$query->andWhere(['>', 'subtotal', 100]);
},
])->with('country')
->all();
实例,混合使用with()和joinWith()的实例:
文件位置:
D:\phpwork\basic\controllers\BlogController.php
源代码:
public function actionJoinWith2() {
$authors = \app\models\Author::find()
//建立搜索条件,仅查找authorid>3的记录
->joinWith([
'blogs' => function ($query) {
$query->andWhere(['>', 'authorid', 3]);
},
])
//对于找出来的authorid>3的记录,预先加载作者的所有article。
->with('article')
->all();
return $this->render('haveblog2',['authors'=>$authors]);
}
文件位置:
D:\phpwork\basic\models\Author.php
源代码:
class Author extends \yii\db\ActiveRecord{
public function getBlogs(){
return $this->hasMany(Blog::className(),['authorid'=>'id']);
}
public function getArticle() {
return $this->hasMany(Articlesql::className(),['authorId'=>'id']);
}
文件位置:
D:\phpwork\basic\views\blog\haveblog2.php
源代码:
<h1><?= Html::encode($this->title) ?></h1>
<?
foreach ($authors as $index => $item) {
echo "<br>author:".$item['name']." id:".$item['id'];
echo "<br> ".var_export($item->article,true);
}
?>
测试结果:
<?
http://localhost:8081/blog/join-with2
/*
发表过Blog的所有作者123
author:Johns id:4
array ( 0 => app\models\Articlesql::__set_state(array( '_attributes' => array ( 'id' => '3', 'title' => '3333', 'content' => '3333con', 'authorId' => '4', 'create_at' => '1490582282', 'update_at' => '1490595454', 'isDeleted' => 0, ), '_oldAttributes' => array ( 'id' => '3', 'title' => '3333', 'content' => '3333con', 'authorId' => '4', 'create_at' => '1490582282', 'update_at' => '1490595454', 'isDeleted' => 0, ), '_related' => array ( ), '_errors' => NULL, '_validators' => NULL, '_scenario' => 'default', '_events' => array ( ), '_behaviors' => array ( ), )), )
author:Plark id:5
array ( )
*/
有时,当连接两个表时,你可能需要在JOIN查询的ON中定义一些扩展条件, 这个可以通过调用yii\db\ActiveQuery::onCondition()方法实现,代码如下:
// SELECT `customer`.* FROM `customer`
// LEFT JOIN `order` ON `order`.`customer_id` = `customer`.`id` AND `order`.`status` = 1
//
// SELECT * FROM `order` WHERE `customer_id` IN (...)
$customers = Customer::find()->joinWith([
'orders' => function ($query) {
$query->onCondition(['order.status' => Order::STATUS_ACTIVE]);
},
])->all();
上例查询返回了所有的customer,并且为每个customer返回了所有的订单,注意,这是与前面那个例子是不同的,前面的查询返回了至少有一个订单的所有customer。
实例,//onCondition实例
源代码:
public function actionOnCondition() {
$authors = \app\models\Author::find()
->joinWith([
'blogs' => function ($query) {
$query->onCondition(['blog.isDeleted' =>0]);
},
])
->orderBy('id')
->all();
//查找所有的author,并返回作者的所有的blog,isDeleted=0的blog除外。
//$sql="select author.* from author left join blog on author.id=blog.authorid and blog.isDeleted=0";//对筛选出来的结果会自动合并同类项
return $this->render('oncondition',['authors'=>$authors]);
}
文件位置:
D:\phpwork\basic\views\blog\oncondition.php
源代码:
<h1><?= Html::encode($this->title) ?></h1>
<?
foreach ($authors as $index => $item) {
echo "<br>author:".$item['name']." id:".$item['id'];
echo "<br> ".var_export($item->blogs,true);
}
?>
文件位置:
D:\phpwork\basic\models\Author.php
源代码:
<?
public function getBlogs(){
return $this->hasMany(Blog::className(),['authorid'=>'id']);
}
测试结果:
http://localhost:8081/blog/on-condition
author:Bomer id:3,此记录中有一条blog的isDeleted为1,因不满足筛选条件所以未列出。
/*
author:Bomer id:3
array ( )
author:Johns id:4
array ( 0 => app\models\Blog::__set_state(array( '_attributes' => array ( 'id' => '2', 'title' => 'My blog2', 'content' => 'My blog Content2', 'authorid' => '4', 'create_at' => '1490339466', 'update_at' => '1490347914', 'isDeleted' => 0, ), '_oldAttributes' => array ( 'id' => '2', 'title' => 'My blog2', 'content' => 'My blog Content2', 'authorid' => '4', 'create_at' => '1490339466', 'update_at' => '1490347914', 'isDeleted' => 0, ), '_related' => array ( ), '_errors' => NULL, '_validators' => NULL, '_scenario' => 'default', '_events' => array ( ), '_behaviors' => array ( ), )), )
*/
信息:当yii\db\ActiveQuery定义时使用了onCondition(),如果查询条件包含了JOIN查询,则条件将被加入到ON部分。如果查询未包括JOIN,on-condition部分将自动追加到WHERE查询中,这样它可以只包含关联表的列条件。
//关联表别名(Relation table aliases) 需要注意的是,在查询中使用JOIN,我们需要消除列名的歧义,因此可以为表名定义个别名。我们可以自定义关联查询的方式为它设置一个别名,代码如下:
$query->joinWith([
'orders' => function ($q) {
$q->from(['o' => Order::tableName()]);
},
])
这样看起来非常复杂,牵涉到了关联对象表名的硬代码,也调用了Order::tableName()。从2.0.7版本开始,Yii为此提供了一个简写方式,你现在可以为关联表使用别名进行定义了,代码如下: //与orders关联属性连接,并对结果按orders.id进行排序 $query->joinWith(['orders o'])->orderBy('o.id'); 此格式用于简单的关联关系,当连接一个嵌套关联时,如果你需要为中间表定义一个别名,例如,$query->joinWith(['orders.product']),你需要嵌套调用joinWith,如下例所示:
$query->joinWith(['orders o' => function($q) {
$q->joinWith('product p');
}])
->where('o.amount > 100');
实例,别名实例
文件位置:
D:\phpwork\basic\controllers\BlogController.php
源代码:
class BlogController extends Controller
{
public function actionAs() {
$authors = \app\models\Author::find()
->joinWith('blogs as b')
->where(['b.isDeleted' => 0])
// ->where(['blog.isDeleted' => 0])//此句报错,应使用别名b,而不能使用表名全称blog
->orderBy(['b.id'=>SORT_DESC])
->all();
// $sql="select author.* from author left join blog on blog.authorid=author.id where blog.isDeleted=0";//对筛选出来的结果会自动合并同类项
return $this->render('as',['authors'=>$authors]);
}
文件位置:
D:\phpwork\basic\views\blog\as.php
源代码:
<h1><?= Html::encode($this->title) ?></h1>
<?
foreach ($authors as $index => $item) {
echo "<br>author:".$item['name']." id:".$item['id'];
echo "<br> ".var_export($item->article,true);
}
?>
测试结果:
<?
http://localhost:8081/blog/as
/*
发表过Blog的所有作者joinwith
author:jingyong id:2
array ( 0 => app\models\Articlesql::__set_state(array( '_attributes' => array ( 'id' => '2', 'title' => '2222', 'content' => '2222con', 'authorId' => '2', 'create_at' => '1490582273', 'update_at' => '1490582273', 'isDeleted' => 0, ), '_oldAttributes' => array ( 'id' => '2', 'title' => '2222', 'content' => '2222con', 'authorId' => '2', 'create_at' => '1490582273', 'update_at' => '1490582273', 'isDeleted' => 0, ), '_related' => array ( ), '_errors' => NULL, '_validators' => NULL, '_scenario' => 'default', '_events' => array ( ), '_behaviors' => array ( ), )), )
author:Plark id:5
array ( )
author:Bomer id:3
array ( )
author:Johns id:4
array ( 0 => app\models\Articlesql::__set_state(array( '_attributes' => array ( 'id' => '3', 'title' => '3333', 'content' => '3333con', 'authorId' => '4', 'create_at' => '1490582282', 'update_at' => '1490595454', 'isDeleted' => 0, ), '_oldAttributes' => array ( 'id' => '3', 'title' => '3333', 'content' => '3333con', 'authorId' => '4', 'create_at' => '1490582282', 'update_at' => '1490595454', 'isDeleted' => 0, ), '_related' => array ( ), '_errors' => NULL, '_validators' => NULL, '_scenario' => 'default', '_events' => array ( ), '_behaviors' => array ( ), )), 1 => app\models\Articlesql::__set_state(array( '_attributes' => array ( 'id' => '4', 'title' => '555', 'content' => '5555con', 'authorId' => '4', 'create_at' => '1490582290', 'update_at' => '1490595920', 'isDeleted' => 0, ), '_oldAttributes' => array ( 'id' => '4', 'title' => '555', 'content' => '5555con', 'authorId' => '4', 'create_at' => '1490582290', 'update_at' => '1490595920', 'isDeleted' => 0, ), '_related' => array ( ), '_errors' => NULL, '_validators' => NULL, '_scenario' => 'default', '_events' => array ( ), '_behaviors' => array ( ), )), )
*/
反向关联(Inverse Relations)
关联属性通常在两个AR类之间相互定义,例如,Customer使用orders属性关联到Order,Order使用customer属性关联到Customer。
class Customer extends ActiveRecord
{
public function getOrders()
{
return $this->hasMany(Order::className(), ['customer_id' => 'id']);
}
}
class Order extends ActiveRecord
{
public function getCustomer()
{
return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
}
}
现在考虑以下代码:
// SELECT * FROM `customer` WHERE `id` = 123
$customer = Customer::findOne(123);
// SELECT * FROM `order` WHERE `customer_id` = 123
$order = $customer->orders[0];
// SELECT * FROM `customer` WHERE `id` = 123
$customer2 = $order->customer;
// displays "not the same"
echo $customer2 === $customer ? 'same' : 'not the same';
我们会认为$customer 和$customer2 是相同的,但它们是不同的。实际上它们是处理的同样的客户数据,但它们是不同的对象,当使用$order->customer时,SQL语句将被执行去填充一个新的$customer2 对象。 为了避免上例中最后这个SQL语句的冗余查询,我们可以使用inverseOf()告诉Yii:customer是orders的一个反向关联属性,代码如下:
class Customer extends ActiveRecord
{
public function getOrders()
{
return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer');
}
}
这样修改之后,就可以得到'same'了:
// SELECT * FROM `customer` WHERE `id` = 123
$customer = Customer::findOne(123);
// SELECT * FROM `order` WHERE `customer_id` = 123
$order = $customer->orders[0];
// No SQL will be executed
$customer2 = $order->customer;
// displays "same"
echo $customer2 === $customer ? 'same' : 'not the same';
注意:反向关联不能用于定义Junction表的关联属性,这是因为Junction关联属性定义时使用了via()和viaTable(),你不能更进一步的调用inverseOf()。
反向关联实例:->inverseOf('author')
文件位置:
D:\phpwork\basic\models\AuthorInfo.php
源代码:
public function getArticles(){
return $this->hasMany(ArticleInfo::className(), ['authId' => '_id'])
->orderBy(['_id'=>SORT_DESC])
->inverseOf('author')//不加此句,则运行结果为:'not the same'
->select(['title','authId']);
}
文件位置:
D:\phpwork\basic\controllers\AuthorController.php
源代码:
public function actionArticles($id){
if($id){
$model=AuthorInfo::findOne(['_id'=>$id]);
$article=$model->articles[0];
$model2=$article->author;
echo $model2 === $model ? 'same' : 'not the same';
}
}
测试
http://localhost:8081/author/articles?id=58a14587e4017a1c1600002a
/*
same
*/
(全文完)
阿江
最后登录:2024-03-03
在线时长:186小时21分
- 粉丝94
- 金钱16816
- 威望160
- 积分20276