sam-it/yii2-virtual-fields

为 Yii2 AR 实现虚拟字段

v4.2.1 2024-07-16 13:05 UTC

This package is auto-updated.

Last update: 2024-09-16 13:28:35 UTC


README

为 Yii2 AR 实现虚拟字段

为了使用这个库,需要更改 Yii2 ORM 的两个部分。

  • 模型定义
  • 查询实现

配置

ActiveQuery 的更改很简单,可以使用特性或行为来实现。如果您没有子类化 ActiveQuery,则可以选择动态地附加行为。

use SamIT\Yii2\VirtualFields\VirtualFieldQueryBehavior;
use SamIT\Yii2\VirtualFields\VirtualFieldBehavior;
use yii\db\ActiveRecord;
use yii\db\ActiveQuery;
class Author extends ActiveRecord 
{
    /**
     * Attach the behavior after constructing the query object  
     * @return ActiveQuery
     */
    public static function find()
    {
        $query = parent::find();
        $query->attachBehavior(VirtualFieldQueryBehavior::class, VirtualFieldQueryBehavior::class);
        return $query;
    }
    
    public function getPosts(): ActiveQuery
    {
        return $this->hasMany(Post::class, ['author_id' => 'id']);
    }

    public function behaviors() 
    {
        return [
            VirtualFieldBehavior::class => [
                'class' => VirtualFieldBehavior::class,
                'virtualFields' => [
                    'postCount' => [
                        VirtualFieldBehavior::LAZY => function(Author $author) { return $author->getPosts()->count(); },
                        VirtualFieldBehavior::CAST => VirtualFieldBehavior::CAST_INT,
                        VirtualFieldBehavior::GREEDY => Post::find()
                            ->andWhere('[[author_id]] = [[author]].[[id]]')
                            ->limit(1)
                            ->select('count(*)')
                    ],
                    'postCount2' => [
                        VirtualFieldBehavior::LAZY => function(Author $author) { return $author->getPosts()->count(); },
                        VirtualFieldBehavior::CAST => VirtualFieldBehavior::CAST_INT,
                        // Sometimes you might want to defer loading of your greedy definition in such cases you may supply a closure.
                        // This closure will be called only once
                        VirtualFieldBehavior::GREEDY => static fn() => Post::find()
                            ->andWhere('[[author_id]] = [[author]].[[id]]')
                            ->limit(1)
                            ->select('count(*)')
                    ]       
                ]
            ]
        ];
    }

}

由于 Yii 使用 DI 容器来创建对象,因此也可以通过在 DI 容器中定义它来全局添加行为。虚拟字段允许您将模型属性定义为 SQL 片段。这种实现的优势在于它支持懒惰加载和贪婪加载。

用法

然后库会处理所有事情

    Author::findByPk(1)->postCount; // Lazy loaded 
    Author::find()->withField('postCount')->one()->postCount; // Greedy loaded

如果没有实现懒惰加载并且使用懒惰加载的属性,将会抛出异常。如果没有实现贪婪加载并且添加了字段到选择中,将会抛出正常的 Yii2 / SQL 异常。

我们发现这些类在减少查询数量(在 Author 上实现 getPostCount() 并不是最佳实践)方面非常有帮助。

为了最大化兼容性和最小化问题,我们选择不使用连接,因为它们可能会影响记录的数量。在许多情况下,生成的查询计划可能不是最优的。

我们选择不重载 ActiveQuery::select() 来支持虚拟字段。这样做的原因是它会改变 * 的语义;默认情况下,* 不会包括所有虚拟字段。