voskobovich/yii2-linker-behavior

此行为使您在ActiveRecord模型中维护多对多和一对多关系变得容易。

安装次数: 241 488

依赖项: 13

建议者: 1

安全: 0

星标: 80

关注者: 11

分支: 19

开放性问题: 11

类型:yii2-behavior

4.1.0 2017-12-01 15:06 UTC

This package is not auto-updated.

Last update: 2024-09-14 19:53:15 UTC


README

这是 Yii2 ManyToMany Behavior 的一个 新版本
它只是 Yii2 ManyToMany Behavior新实现架构

这是什么?

这是 Yii2 ManyToMany Behavior 的一个副本,但使用了不同的架构。

Yii2 ManyToMany Behavior 中,有人抱怨保存多对多关系只有一个逻辑,并不适合所有人。
我做了这样的事情,可以配置更新关系的逻辑,而无需修改代码。

在这个版本中实现了三个逻辑(更新器)类

  • ManyToManyUpdater.php - 删除所有关系并重新写入它们。它的优点是更新只需2个数据库请求。适用于每次更新少量关系的情况。
  • ManyToManySmartUpdater.php - 查找差异并更新关系(删除旧关系,添加新关系)。它需要4个数据库请求,但不会删除现有关系。
  • OneToManyUpdater.php - 与一对多关系的工作逻辑。只需获取主键并将其分配给子元素。

所有这些都允许开发者为自己的项目编写自己的关系更新逻辑。
在仓库中有接口、抽象类等,所有这些都是为了扩展。

目前项目处于RC阶段,需要进一步完善新功能并编写测试。
但总体来说,在这个架构下,Yii2 ManyToMany Behavior 的测试都 顺利通过
所以已经可以使用了。

关于与 Yii2 ManyToMany Behavior 的兼容性?

只更改了组件的配置参数。
只需要仔细阅读 README.md 中的文档,一切都会顺利。

此行为使您在ActiveRecord模型中维护多对多和一对多关系变得容易。

License Latest Stable Version Latest Unstable Version Total Downloads Build Status

支持

GutHub问题.

使用方法

  1. 在模型中添加行为并配置它
  2. 在模型中为行为创建的属性添加验证规则
  3. 在视图中为属性创建表单字段

添加和配置行为

例如,假设您正在处理像 BookAuthorReview 这样的实体。Book 模型具有以下关系

public function getAuthors()
{
    return $this->hasMany(
        Author::className(),
        ['id' => 'author_id']
    )->viaTable(
        '{{%book_has_author}}',
        ['book_id' => 'id']
    );
}

public function getReviews()
{
    return $this->hasMany(Review::className(), ['id' => 'review_id']);
}

在同一个模型中,行为可以配置如下

public function behaviors()
{
    return [
        [
            'class' => \voskobovich\linker\LinkerBehavior::className(),
            'relations' => [
                'author_ids' => 'authors',
		'review_ids' => 'reviews',
            ],
        ],
    ];
}

关系名称不需要以 _ids 结尾,您可以使用任何名称来表示一个关系。尽管如此,建议使用有意义的名称。

添加验证规则

属性是自动创建的。然而,您必须为它们提供验证规则(通常是一个 safe 验证器)

public function rules()
{
    return [
        [['author_ids', 'review_ids'], 'each', 'rule' => ['integer']]
    ];
}

创建表单字段

默认情况下,行为将接受来自多选字段的输入

<?= $form->field($model, 'author_ids')
    ->dropDownList($authorsAsArray, ['multiple' => true]) ?>
...
<?= $form->field($model, 'review_ids')
    ->dropDownList($reviewsAsArray, ['multiple' => true]) ?>

已知问题与限制

  • 不支持复合主键。
  • 使用主模型中的连接更新多对多链接的连接表。
  • 在计算默认值时,请注意此函数只调用一次,即在关系保存之前,然后其结果用于通过一个查询更新所有相关行。
  • 关系通过DAO(即直接操作表)保存。

自定义getter和setter

在《Book》模型中创建的属性如author_idsreview_ids是自动生成的。默认情况下,它们配置为接受来自标准选择输入的数据(见下文)。但是,可以使用自定义的getter和setter函数,这对于与更复杂的客户端脚本交互可能很有用。可以为给定的属性定义许多替代的getter和setter。

//...
'author_ids' => [
    'authors',
    'fields' => [
        'json' => [
            'get' => function($value) {
                //from internal representation (array) to user type
                return JSON::encode($value);
            },
            'set' => function($value) {
                //from user type to internal representation (array)
                return JSON::decode($value);
            },
        ],
        'string' => [
            'get' => function($value) {
                //from internal representation (array) to user type
                return implode(',', $value);
            },
            'set' => function($value) {
                //from user type to internal representation (array)
                return explode(',', $value);
            },
        ],
    ],
]
//...

字段名与属性名用下划线连接。在本例中,访问$model->author_ids将得到ID数组,$model->author_ids_json将返回JSON字符串,$model->author_ids_string将返回以逗号分隔的ID字符串。setter的工作方式类似。

可以省略getter和setter以回退到默认行为(ID数组)。

注意

setter函数接收通过$_REQUEST传入的任何数据,并期望返回相关模型ID的数组。getter函数接收相关模型ID的数组。

兼容性注意

为主要属性(如上例中的author_ids)指定getter和setter仍受支持,但建议不这样做。最佳实践是使用主要属性以ID数组的形式获取和设置值,并创建fields来使用其他getter和setter。

自定义连接表值

要在连接表(除关系所需的列外)设置额外的值,可以使用viaTableAttributesValue

...
'author_ids' => [
    'authors',
    'updater' => [
        'viaTableAttributesValue' => [
            'status_key' => BookHasAuthor::STATUS_ACTIVE,
            'created_at' => function() {
                return new \yii\db\Expression('NOW()');
            },
            'is_main' => function($updater, $relatedPk, $rowCondition) {
                /**
                 * $updater this is a object of current updater that implement UpdaterInterface.
                 * $relatedPk this is a Primary Key of related object.
                 * $rowCondition this is a object of current row state, that implement AssociativeRowCondition.
                 */
                 
                /**
                 * How i can get the Primary Model?
                 */
                $primaryModel = $updater->getBehavior()->owner;
                
                /**
                 * How i can get the Primary Key of Primery Model?
                 */
                $primaryModelPkValue = $primaryModel->getPrimaryKey();
                 
                return array_search($relatedPk, $primaryModel->author_ids) === 0;
            },
        ],
    ]
]
...

为孤儿模型设置默认值

当保存一对多关系时,旧链接被删除并创建新链接。为了删除旧链接,相应的外键列被设置为某个值。默认情况下,它是NULL,但可以配置不同。请注意,您的数据库必须支持您选择的默认值,因此如果您使用NULL作为默认值,该字段必须是可空的。

可以提供如下这样的常量值

...
'review_ids' => [
    'reviews',
    'updater' => [
        'fallbackValue' => 17,
    ]
],
...

也可以显式地将默认值分配给NULL,如下所示:'fallbackValue' => null。另一种选择是提供一个计算默认值的函数

...
'review_ids' => [
    'reviews',
    'updater' => [
        'fallbackValue' => function($model, $relationName, $attributeName) {
            //default value calculation
            //...
            return $fallbackValue;
        },
    ]
],
...

该函数接受3个参数。在我们的例子中,$modelBook类(行为的所有者)的实例,$relationName'reviews'$attributeName'review_ids'

如果在此函数中需要数据库连接,建议从主模型(Book)或次级模型(Review)获取。

function($model, $relationName, $attributeName) {
    //get db connection from primary model (Book)
    $connection = $model::getDb();
    ...
    //OR get db connection from secondary model (Review)
    $secondaryModelClass = $model->getRelation($relationName)->modelClass;
    $connection = $secondaryModelClass::getDb();
    ...
    //further value calculation logic (db query)

多次应用于单个关系的行为

在单个模型中,可以使用此行为对单个关系多次使用。然而,不推荐这样做。

与使用相同连接表的关系一起使用行为

当您在同一个模型中实现多个多对多关系,并且它们使用相同的连接表时,您可能会遇到问题,即您的连接记录无法正确保存。

这是因为每次保存新关系时都会删除旧连接记录。为了避免删除刚刚保存的记录,您需要设置viaTableCondition参数。

此删除条件将与主删除条件合并,并可用于微调您的删除查询。

例如,让我们设想我们为植物实验室开发了一个科学数据库。我们有一个名为“Sample”的模型用于不同的植物样品,一个名为“Attachment”的模型用于相关文件(照片或文档),还有一个名为“sample_attachments”的连接表。我们希望通过在连接表中引入“type”字段,将这些文件划分到“Sample”模型的不同字段中(原料图片、分子结构等)。在这种情况下,生成的“Sample”模型将如下所示

    public function behaviors()
    {
        return [
            'manyToMany' => [
                'class' => LinkerBehavior::className(),
                'relations' => [
                    'rawMaterialPicturesList' => [
                        'rawMaterialPictures',
                        'updater' => [
                            'viaTableAttributesValue' => [
                                'type_key' => 'RAW_MATERIAL_PICTURES',
                            ],
                            'viaTableCondition' => [
                                'type_key' => 'RAW_MATERIAL_PICTURES',
                            ],
                        ]
                    ],
                    'molecularStructureList' => [
                        'molecularStructure',
                        'updater' => [
                            'viaTableAttributesValue' => [
                                'type_key' => 'MOLECULAR_STRUCTURE',
                            ],
                            'viaTableCondition' => [
                                'type_key' => 'MOLECULAR_STRUCTURE',
                            ],
                        ]
                    ],
                ],
            ],
        ];
    }
    
    public function getRawMaterialPictures()
    {
        return $this->hasMany(
            Attachment::className(),
            ['id' => 'related_id']
        )->viaTable(
            'sample_attachments',
            ['current_id' => 'id'],
            function ($query) {
                $query->andWhere([
                    'type_key' => 'RAW_MATERIAL_PICTURES',
                ]);
                return $query;
            }
        );
    }
    
    public function getMolecularStructure()
    {
        return $this->hasMany(
            Attachment::className(),
            ['id' => 'related_id']
        )->viaTable(
            'sample_attachments',
            ['current_id' => 'id'],
            function ($query) {
                $query->andWhere([
                    'type_key' => 'MOLECULAR_STRUCTURE',
                ]);
                return $query;
            }
        );
    }
    

安装

安装此扩展的首选方式是通过composer

运行以下命令之一:

php composer.phar require --prefer-dist voskobovich/yii2-linker-behavior "^4.0"

或者将以下内容添加到您的composer.json文件的require部分:

"voskobovich/yii2-linker-behavior": "^4.0"

代码生态

检查代码

./vendor/bin/phpcs -s --encoding=utf-8 --extensions=php .

自动修复代码格式

./vendor/bin/php-cs-fixer fix