sam-it / yii2-virtual-fields
为 Yii2 AR 实现虚拟字段
v4.2.1
2024-07-16 13:05 UTC
Requires
- php: >= 8.1
Requires (Dev)
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()
来支持虚拟字段。这样做的原因是它会改变 *
的语义;默认情况下,*
不会包括所有虚拟字段。