Yii2文档–活动记录2

  • 查询关联的数据

使用 AR 方法也可以查询数据表的关联数据(如,选出表A的数据可以拉出表B的关联数据)。 有了 AR, 返回的关联数据连接就像连接关联主表的 AR 对象的属性一样。
建立关联关系后,通过 $customer->orders可以获取 一个 Order 对象的数组,该数组代表当前客户对象的订单集。

class Customer extends \yii\db\ActiveRecord
{
    public function getOrders(){
        // 客户和订单通过 Order.customer_id -> id 关联建立一对多关系
        return $this->hasMany(Order::className(), ['customer_id' => 'id']);
    }
}
class Order extends \yii\db\ActiveRecord
{
    // 订单和客户通过 Customer.id -> customer_id 关联建立一对一关系
    public function getCustomer(){
        return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
    }
}
// 取得客户的订单
//SELECT * FROM customer WHERE id=1;
$customer = Customer::findOne(1);  
// $orders 是 Order 对象数组,SELECT * FROM order WHERE customer_id=1;
$orders = $customer->orders; 

再次用表达式 $customer->orders将不会执行第二次 SQL 查询, SQL 查询只在该表达式第一次使用时执行。 数据库访问只返回缓存在内部前一次取回的结果集,如果你想查询新的 关联数据,先要注销现有结果集:unset($customer->orders);
有时候需要在关联查询中传递参数,如不需要返回客户全部订单, 只需要返回购买金额超过设定值的大订单, 通过以下getter方法声明一个关联数据 bigOrders

class Customer extends \yii\db\ActiveRecord
{
    public function getBigOrders($threshold = 100)
    {
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])
            ->where('subtotal > :threshold', [':threshold' => $threshold])
            ->orderBy('id');
    }
}
$orders = $customer->getBigOrders(200)->all();//返回总额大于200的订单

Note: 关联查询返回的是 yii\db\ActiveQuery 的实例,如果像特性(如类属性)那样连接关联数据, 返回的结果是关联查询的结果,即 yii\db\ActiveRecord 的实例, 或者是数组,或者是 null ,取决于关联关系的多样性。如,$customer->getOrders() 返回 ActiveQuery 实例,而$customer->orders 返回Order 对象数组 (如果查询结果为空则返回空数组)。

  • 中间关联表

举例而言,如果 order 表和 item 表通过中间表 order_item 关联起来, 可以在 Order 类声明 items 关联关系取代中间表:

class Order extends \yii\db\ActiveRecord
{
    public function getItems(){
        return $this->hasMany(Item::className(), ['id' => 'item_id'])
            ->viaTable('order_item', ['order_id' => 'id']);
    }
}
class Order extends \yii\db\ActiveRecord
{
    public function getOrderItems(){
        return $this->hasMany(OrderItem::className(), ['order_id' => 'id']);
    }
    public function getItems(){
        return $this->hasMany(Item::className(), ['id' => 'item_id'])
            ->via('orderItems');
    }
}
  • 延迟加载和即时加载(又称惰性加载与贪婪加载)

如前所述,当你第一次连接关联对象时, AR 将执行一个数据库查询 来检索请求数据并填充到关联对象的相应属性。 如果再次连接相同的关联对象,不再执行任何查询语句,这种数据库查询的执行方法称为“延迟加载”。

// SQL executed: SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// SQL executed: SELECT * FROM order WHERE customer_id=1
$orders = $customer->orders;
// 没有 SQL 语句被执行
$orders2 = $customer->orders; //取回上次查询的缓存数据

假设数据库查出的客户超过100个,以上代码将执行多少条 SQL 语句? 101 条!第一条 SQL 查询语句取回100个客户,然后, 每个客户要执行一条 SQL 查询语句以取回该客户的所有订单。

为解决以上性能问题,可以通过调用 yii\db\ActiveQuery::with() 方法使用即时加载解决。

// SQL executed: SELECT * FROM customer LIMIT 100;
//               SELECT * FROM orders WHERE customer_id IN (1,2,...)
$customers = Customer::find()->limit(100)->with('orders')->all();
foreach ($customers as $customer) {
    // 没有 SQL 语句被执行
    $orders = $customer->orders;
    // ...处理 $orders...
}
  • 逆关系
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']);
    }
}

如果我们执行以下查询,可以发现订单的 customer 和 找到这些订单的客户对象并不是同一个。连接 customer->orders 将触发一条 SQL 语句 而连接一个订单的 customer 将触发另一条 SQL 语句。

// SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// 输出 "不相同"
// SELECT * FROM order WHERE customer_id=1
// SELECT * FROM customer WHERE id=1
if ($customer->orders[0]->customer === $customer) {
    echo '相同';
} else {
    echo '不相同';
}

为避免多余执行的后一条语句,我们可以为 customer或 orders 关联关系定义相反的关联关系,通过调用 yii\db\ActiveQuery::inverseOf() 方法可以实现。

class Customer extends ActiveRecord
{
    ....
    public function getOrders(){
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer');
    }
}
// SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
// 输出相同
// SELECT * FROM order WHERE customer_id=1
if ($customer->orders[0]->customer === $customer) {
    echo '相同';
} else {
    echo '不相同';
}
  • 关联表操作

例如,给定一个customer和order对象,我们可以通过下面的代码使得customer对象拥有order对象:

$customer = Customer::findOne(1);
$order = new Order();
$order->subtotal = 100;
$customer->link('orders', $order)
  • 作用域

当你调用yii\db\ActiveRecord::find() 或 yii\db\ActiveRecord::findBySql()方法时,将会返回一个yii\db\ActiveQuery实例。之后,你可以调用其他查询方法,如 yii\db\ActiveQuery::where(),yii\db\ActiveQuery::orderBy(), 进一步的指定查询条件。
有时候你可能需要在不同的地方使用相同的查询方法。如果出现这种情况,你应该考虑定义所谓的作用域。作用域是本质上要求一组的查询方法来修改查询对象的自定义查询类中定义的方法。 之后你就可以像使用普通方法一样使用作用域。

namespace app\models;
use yii\db\ActiveQuery;
class CommentQuery extends ActiveQuery
{
    public function active($state = true){
        $this->andWhere(['active' => $state]);
        return $this;
    }
}
namespace app\models;
use yii\db\ActiveRecord;
class Comment extends ActiveRecord
{
    /**
     * @inheritdoc
     * @return CommentQuery
     */
    public static function find(){
        return new CommentQuery(get_called_class());
    }
}
$comments = Comment::find()->active()->all();
$inactiveComments = Comment::find()->active(false)->all();
class Post extends \yii\db\ActiveRecord
{
    public function getActiveComments(){
        return $this->hasMany(Comment::className(), ['post_id' => 'id'])->active();

    }
}
  • 默认作用域

一个默认的作用域可以作用于所有查询。你可以很容易的通过重写yii\db\ActiveRecord::find()方法来定义一个默认作用域

public static function find()
{
    return parent::find()->where(['deleted' => false]);
}

注意,你之后所有的查询都不能用 yii\db\ActiveQuery::where(),但是可以用 yii\db\ActiveQuery::andWhere() 和 yii\db\ActiveQuery::orWhere(),他们不会覆盖掉默认作用域。(译注:如果你要使用默认作用域,就不能在 xxx::find()后使用where()方法,你必须使用andXXX()或者orXXX()系的方法,否则默认作用域不会起效果,至于原因,打开where()方法的代码一看便知)

发表在 yii

留下评论