阿江 2017-10-01 15:40:42 6448次浏览 1条回复 5 0 0

说明

学习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>&nbsp;&nbsp;".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>&nbsp;&nbsp;".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>&nbsp;&nbsp;".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
*/

(全文完)

您需要登录后才可以回复。登录 | 立即注册