voskobovich / yii2-linker-behavior
此行为使您在ActiveRecord模型中维护多对多和一对多关系变得容易。
Requires
- php: >=5.4.0
- yiisoft/yii2: ^2
Requires (Dev)
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模型中维护多对多和一对多关系变得容易。
支持
使用方法
- 在模型中添加行为并配置它
- 在模型中为行为创建的属性添加验证规则
- 在视图中为属性创建表单字段
添加和配置行为
例如,假设您正在处理像 Book
、Author
和 Review
这样的实体。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_ids
和review_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个参数。在我们的例子中,$model
是Book
类(行为的所有者)的实例,$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