yii2tech / embedded
为 Yii2 提供嵌入式(嵌套)模型的支持
Requires
- yiisoft/yii2: ~2.0.14
This package is auto-updated.
Last update: 2022-01-10 10:44:52 UTC
README
嵌入式(嵌套)模型扩展 for Yii 2
本扩展为 Yii2 提供嵌入式(嵌套)模型的用法支持。特别是它允许在 MongoDB 和 ElasticSearch 中处理子文档,以及在关系数据库中处理复杂的 JSON 属性。
有关许可证信息,请参阅 LICENSE-文件。
安装
安装此扩展的最佳方式是通过 composer。
运行
php composer.phar require --prefer-dist yii2tech/embedded
或将其添加到您的 composer.json 文件的要求部分。
"yii2tech/embedded": "*"
使用
此扩展允许将复杂模型属性(以数组形式表示)作为嵌套模型(以对象形式表示)进行操作。要使用此功能,目标类应实现 [[\yii2tech\embedded\ContainerInterface]] 接口。这可以通过使用 [[\yii2tech\embedded\ContainerTrait]] 轻松实现。
对于每个嵌入式实体,应提供映射声明。为此,您需要声明一个以 'embedded' 为前缀的方法,该方法应返回 [[Mapping]] 实例。您可以使用 [[hasEmbedded()]] 和 [[hasEmbeddedList()]] 来实现。
对于每个源字段或属性,将声明一个新的虚拟属性,其名称由从声明方法名称中删除 'embedded' 前缀组成。
注意:注意命名冲突:如果您有一个名为 'profile' 的源属性,则其映射声明应具有不同的名称,例如 'profileModel'。
示例
use yii\base\Model; use yii2tech\embedded\ContainerInterface; use yii2tech\embedded\ContainerTrait; class User extends Model implements ContainerInterface { use ContainerTrait; public $profileData = []; public $commentsData = []; public function embedProfile() { return $this->mapEmbedded('profileData', Profile::className()); } public function embedComments() { return $this->mapEmbeddedList('commentsData', Comment::className()); } } $user = new User(); $user->profile->firstName = 'John'; $user->profile->lastName = 'Doe'; $comment = new Comment(); $user->comments[] = $comment;
每个嵌入式映射都可以指定额外的选项。请参阅 [[\yii2tech\embedded\Mapping]] 获取更多详细信息。
处理嵌入式对象
嵌入式功能类似于常规 ActiveRecord 关系功能。它们的声明和处理类似,具有类似的具体内容和限制。所有嵌入式对象都是延迟加载的。这意味着它们将在首次请求时创建。这节省了内存,但可能在某些时候产生意外的结果。默认情况下,一旦嵌入式对象实例化,其源属性将被清除以节省内存使用。您可以通过 [[\yii2tech\embedded\Mapping::$unsetSource]] 控制此行为。
嵌入式对象允许简化嵌套数据处理,但通常它们对自己的源数据含义和全局处理一无所知。例如:嵌套对象不知道其源数据是否来自数据库,也不知道这些数据应该如何保存。此类功能通常由容器对象处理。因此,在某个时刻,您可能需要将数据从嵌入式对象转换回其原始格式,以便进行本地处理,如保存。这可以通过使用方法refreshFromEmbedded()
来完成。
use yii\base\Model; use yii2tech\embedded\ContainerInterface; use yii2tech\embedded\ContainerTrait; class User extends Model implements ContainerInterface { use ContainerTrait; public $profileData = [ 'firstName' => 'Unknown', 'lastName' => 'Unknown', ]; public function embedProfile() { return $this->mapEmbedded('profileData', Profile::className()); } } $user = new User(); var_dump($user->profileData); // outputs array: ['firstName' => 'Unknown', 'lastName' => 'Unknown'] $user->profile->firstName = 'John'; $user->profile->lastName = 'Doe'; var_dump($user->profileData); // outputs empty array $user->refreshFromEmbedded(); var_dump($user->profileData); // outputs array: ['firstName' => 'John', 'lastName' => 'Doe']
在嵌入对象列表(使用[[\yii2tech\embedded\ContainerTrait::mapEmbeddedList()]])时,产生的虚拟字段将不是一个数组,而是一个满足[[\ArrayAccess]]接口的对象。因此,对这种属性的任何操作(即使它看起来像使用数组)都会影响容器对象。例如
use yii\base\Model; use yii2tech\embedded\ContainerInterface; use yii2tech\embedded\ContainerTrait; class User extends Model implements ContainerInterface { use ContainerTrait; public $commentsData = []; public function embedComments() { return $this->mapEmbeddedList('commentsData', Comment::className()); } } $user = new User(); // ... $comments = $user->comments; // not a copy of array - copy of object reference! foreach ($comments as $key => $comment) { if (...) { unset($comments[$key]); // unsets `$user->comments[$key]`! } } $comments = clone $user->comments; // creates a copy of list, but not a copy of contained objects! $comments[0]->title = 'new value'; // actually sets `$user->comments[0]->title`!
验证嵌入式模型
每个嵌入式模型应该声明自己的验证规则,并且通常应该单独进行验证。然而,您可以使用[[\yii2tech\embedded\Validator]]来简化复杂的模型验证。例如
use yii\base\Model; use yii2tech\embedded\ContainerInterface; use yii2tech\embedded\ContainerTrait; class User extends Model implements ContainerInterface { use ContainerTrait; public $contactData; public function embedContact() { return $this->mapEmbedded('contactData', Contact::className()); } public function rules() { return [ ['contact', 'yii2tech\embedded\Validator'], // ... ] } } class Contact extends Model { public $email; public function rules() { return [ ['email', 'required'], ['email', 'email'], ] } } $user = new User(); if ($user->load(Yii::$app->request->post()) && $user->contact->load(Yii::$app->request->post())) { if ($user->validate()) { // automatically validates 'contact' as well // ... } }
注意:请注意,[[\yii2tech\embedded\Validator]]必须为嵌入式模型名称设置,而不是为其源属性设置。不要混淆它们!
您可以通过启用[[\yii2tech\embedded\Validator::$initializedOnly]]来允许跳过未初始化的嵌入式模型的验证,例如,至少请求一次。如果源模型可以在不同的场景中使用,其中一些可能不需要嵌入式模型操作,这将提高性能。然而,在这种情况下,嵌入式源属性值将不会被验证。您应该确保以其他方式对其进行验证,或者它对通过[[\yii\base\Model::load()]]方法进行填充是“不安全的”。
保存嵌入式模型
请注意,嵌入式模型是独立于源模型属性存储的。您需要使用[[\yii2tech\embedded\ContainerInterface::refreshFromEmbedded()]]方法来使用嵌入式模型中的数据填充源模型属性。
此外,请注意,即使嵌入式模型已更改,尝试获取嵌入式源属性的“脏”值也会失败,直到您使用refreshFromEmbedded()
。
$user = User::findOne(1); // declares embedded model 'contactModel' from attribute 'contactData' if ($user->contactModel->load(Yii::$app->request->post())) { var_dump($user->isAttributeChanged('contactData')); // outputs `false` $user->refreshFromEmbedded(); var_dump($user->isAttributeChanged('contactData')); // outputs `true` }
如果您将“嵌入式”功能应用于ActiveRecord类,最佳的数据同步位置是[[\yii\db\BaseActiveRecord::beforeSave()]]方法。例如,将此扩展应用于[[\yii\mongodb\ActiveRecord]]类可能如下所示
use yii2tech\embedded\ContainerInterface; use yii2tech\embedded\ContainerTrait; class ActiveRecord extends \yii\mongodb\ActiveRecord implements ContainerInterface { use ContainerTrait; public function beforeSave($insert) { if (!parent::beforeSave($insert)) { return false; } $this->refreshFromEmbedded(); // populate this model attributes from embedded models' ones, ensuring they are marked as 'changed' before saving return true; } }
预定义模型类
此扩展是通用的,可以应用于具有复杂属性的任何模型。然而,为了简化与常见解决方案的集成,此扩展提供了一些基类。
- [[\yii2tech\embedded\mongodb\ActiveRecord]] - 带嵌入式功能内置的MongoDB ActiveRecord
- [[\yii2tech\embedded\mongodb\ActiveRecordFile]] - 带嵌入式功能内置的MongoDB GridFS ActiveRecord
- [[\yii2tech\embedded\elasticsearch\ActiveRecord]] - 带嵌入式功能内置的ElasticSearch ActiveRecord
提供的ActiveRecord类已经实现了[[\yii2tech\embedded\ContainerInterface]],并在beforeSave()
阶段调用refreshFromEmbedded()
。例如,如果您正在使用MongoDB并希望处理子文档,您只需将扩展从常规[[\yii\mongodb\ActiveRecord]]切换到[[\yii2tech\embedded\mongodb\ActiveRecord]]即可。
class User extends \yii2tech\embedded\mongodb\ActiveRecord { public static function collectionName() { return 'customer'; } public function attributes() { return ['_id', 'name', 'email', 'addressData', 'status']; } public function embedAddress() { return $this->mapEmbedded('addressData', UserAddress::className()); } }