yii2tech/embedded

此包已被弃用,不再维护。没有建议的替代包。

为 Yii2 提供嵌入式(嵌套)模型的支持

资助包维护!
klimov-paul
Patreon

安装次数: 149 497

依赖: 5

建议者: 0

安全: 0

星标: 79

关注者: 16

分支: 15

开放问题: 0

类型:yii2-extension

1.0.3 2018-08-23 14:50 UTC

This package is auto-updated.

Last update: 2022-01-10 10:44:52 UTC


README

12951949

嵌入式(嵌套)模型扩展 for Yii 2


本扩展为 Yii2 提供嵌入式(嵌套)模型的用法支持。特别是它允许在 MongoDBElasticSearch 中处理子文档,以及在关系数据库中处理复杂的 JSON 属性。

有关许可证信息,请参阅 LICENSE-文件。

Latest Stable Version Total Downloads Build Status

安装

安装此扩展的最佳方式是通过 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());
    }
}