行動によるYii2の多対多の保存

Yii2を使用しなければならなかった場合、多対多の関係を維持する必要がある状況にあった可能性があります。

ネットワークにこのタイプの接続を操作するための動作がまだないことが明らかになった場合、必要なコードは「after save」イベントで記述され、リポジトリに送信される「well、it works」と分けられました。

個人的には、このイベントの調整に満足していませんでした。 私は公式のYii2ビルドにはない非常に魔法のような振る舞いを書くことにしました。

設置


Composerを介してインストールします。
php composer require --prefer-dist voskobovich/yii2-many-many-behavior "~3.0" 

または、「require」セクションでプロジェクトをcomposer.jsonに追加します。
 "voskobovich/yii2-many-many-behavior": "~3.0" 

私たちは実施します:
 php composer update 

GitHubのソース。

使い方は?


例として、一般的なタイプのコミュニケーションである出版物とカテゴリーを取り上げます。

振る舞いを出版物に結び付けます。
 class Post extends ActiveRecord { ... public function rules() { return [ [['category_ids'], 'each', 'rule' => ['integer']], ... ]; } public function behaviors() { return [ [ 'class' => \voskobovich\behaviors\ManyToManyBehavior::className(), 'relations' => [ 'category_ids' => 'categories', ], ], ]; } public function getCategories() { return $this->hasMany(Category::className(), ['id' => 'category_id']) ->viaTable('{{%post_has_category}}', ['post_id' => 'id']); } public static function listAll($keyField = 'id', $valueField = 'name', $asArray = true) { $query = static::find(); if ($asArray) { $query->select([$keyField, $valueField])->asArray(); } return ArrayHelper::map($query->all(), $keyField, $valueField); } ... } 

この動作により、モデルに新しいcategory_ids属性が作成されます。 フォームまたはAPIから取得したプライマリカテゴリキーの配列を受け入れます。

一度に複数の関係で動作するように動作を構成できます。 たとえば、出版物には、カテゴリ、タグ、ユーザー、画像などとの関係があります。
 'relations' => [ 'category_ids' => 'categories', 'user_ids' => 'users', 'tag_ids' => 'tags', ... ] 

ビヘイビアーによって作成されたすべての属性は、検証ルールで言及する必要があります。 意味のあるルールを書き、安全なグループに指定しないでください。これで完了です。

次に、カテゴリを選択するためのビューにフィールドを作成します。
 <?= $form->field($model, 'category_ids')->dropDownList(Category::listAll(), ['multiple' => true]) ?> 

私はプロジェクトで長い間listAll()メソッドを使用していますが、今では共有する機会があります。 GridViewフォームおよびフィルターの複数選択を埋めるのに最適です。

これらの操作の後、すべてを出版物に簡単に添付する必要があります。

最適化とセキュリティはどうですか?


主キーのリストを作成する要求は、モデルを選択するときではなく、プロパティの読み取り時にのみ発生します。 彼に連絡するまで、リクエストは消えません。
通信を管理するためのすべてのロジックは、トランザクションにラップされています。

さらにもっと


多くの場合、タスクは標準の「保存/取得」関連モデルを超えています。 このようなタスクの場合、動作に詳細設定が提供されます。

カスタムのゲッターとセッター


多くの場合、さまざまなjsプラグインでは、データをJSONまたは「1,2,3,4」形式の文字列に送信できる必要があります。 動作をカスタマイズします。
 public function behaviors() { return [ [ 'class' => \voskobovich\behaviors\ManyToManyBehavior::className(), 'relations' => [ 'category_ids' => [ 'categories', 'fields' => [ 'json' => [ 'get' => function($value) { return JSON::encode($value); }, 'set' => function($value) { return JSON::decode($value); }, ], 'string' => [ 'get' => function($value) { return implode(',', $value); }, 'set' => function($value) { return explode(',', $value); }, ], ], ] ], ], ]; } 

この構成では、モデルに3つの新しいcategory_idscategory_ids_jsonおよびcategory_ids_string属性が含まれます。 構成からわかるように、送信データの形式を変更できるだけでなく、属性に含まれるデータを処理することもできます。 たとえば、文字列またはJSONを主キーの配列に解析します。
ドキュメントで開きます

スパンテーブルフィールド値の管理


多くの場合、接続には主キーだけでなく、追加情報も含まれます。 例:作成日またはソート順。 この場合、動作も構成できます。
 public function behaviors() { return [ [ 'class' => \voskobovich\behaviors\ManyToManyBehavior::className(), 'relations' => [ 'category_ids' => [ 'categories', 'viaTableValues' => [ 'status_key' => PostHasCategory::STATUS_ACTIVE, 'created_at' => function() { return new \yii\db\Expression('NOW()'); }, 'is_main' => function($model, $relationName, $attributeName, $relatedPk) { //      return array_search($relatedPk, $model->category_ids) === 0; }, ], ] ], ], ]; } 

ドキュメントで開きます

孤立モデルのデフォルト値の設定


タイトルは奇妙に聞こえますが、それは正しいことです。
実際には、動作は多対多の関係だけでなく、1対多の関係でも機能することができます。
最初のケースでは、リンクテーブルのレコードは単に削除され、新しいレコードが代わりに書き込まれます。
2番目のタイプの通信では、最初に関連モデルを孤児にする(アンティー)必要があり、次にそれらを保護する(タイ)必要があることが理解されています。
その結果、一部のモデルは孤立したままである場合があり、一種の「アーカイブ」に配置する必要があります。 すべての孤立レコードの所有者を設定するために、 デフォルトのパラメーターが作成されました。 指定しない場合、接続フィールドのエントリはヌルのままになります。
 public function behaviors() { return [ [ 'class' => \voskobovich\behaviors\ManyToManyBehavior::className(), 'relations' => [ 'category_ids' => [ 'categories', 'default' => 17, ] ], ], ]; } 

ドキュメントで開きます

スパニングテーブルからの削除条件


多くの場合、同じ構造の異なるタイプのレコードがリンクテーブルに保存されます。
例:テーブルproduct_has_attachmentには、商品の写真と価格があります。 添付ファイルの各タイプには独自の接続があります。
しかし、製品に新しい価格を追加するとどうなりますか? この製品に関連付けられたproduct_has_attachmentテーブルのすべてのエントリが破棄され 、古い価格と新しい価格が代わりに書き込まれます。
しかし...しかし...価格表だけでなく、写真もあったから...地獄!
これを防ぐには、動作を設定する必要があります。
 class Product extends ActiveRecord { ... public function behaviors() { return [ [ 'class' => \voskobovich\behaviors\ManyToManyBehavior::className(), 'relations' => [ 'image_ids' => [ 'images', 'viaTableValues' => [ 'type_key' => ProductHasAttachment::TYPE_IMAGE, ], 'customDeleteCondition' => [ 'type_key' => ProductHasAttachment::TYPE_IMAGE, ], ], 'priceList_ids' => [ 'priceLists', 'viaTableValues' => [ 'type_key' => ProductHasAttachment::TYPE_PRICE_LIST, ], 'customDeleteCondition' => [ 'type_key' => ProductHasAttachment::TYPE_PRICE_LIST, ], ] ], ], ]; } public function getImages() { return $this->hasMany(Attachment::className(), ['id' => 'attachment_id']) ->viaTable('{{%product_has_attachment}}', ['product_id' => 'id'], function ($query) { $query->andWhere([ 'type_key' => ProductHasAttachment::TYPE_IMAGE, ]); return $query; }); } public function getPriceLists() { return $this->hasMany(Attachment::className(), ['id' => 'attachment_id']) ->viaTable('{{%product_has_attachment}}', ['product_id' => 'id'], function ($query) { $query->andWhere([ 'type_key' => ProductHasAttachment::TYPE_PRICE_LIST, ]); return $query; }); } ... } 

したがって、新しい価格表を追加すると、価格表のリストのみが影響を受け、写真は変更されません。
ドキュメントで開きます

この記事は、機能的な動作の一部のみを反映しています。
より正確な情報については、 ドキュメントをご覧になることをお勧めします
さらに、私は記事をREADMEリポジトリよりも頻繁に更新しません。

私の行動が接続の操作をより簡単にすることを心から願っています。
もしそうなら、スターをgithubに置いて友達に勧めてください。彼のことを聞いていない人がいて、「自転車の松葉杖を作り続けている」からです。

Source: https://habr.com/ru/post/J244709/


All Articles