web-dev-qa-db-ja.com

Yii2:ActiveRecordでデフォルトの属性値を設定する方法は?

これは些細な質問のように思えるかもしれませんが、私が考えることができる明白な解決策はすべて、独自の欠陥を持っています。

必要なのは、検証の前後に読み取り可能になり、検索に使用される派生クラスに干渉しない方法で、新しいレコードにのみデフォルトのActiveRecord属性値を設定できるようにすることです。

(new MyModel)->attrがデフォルトのattr値を返すように、クラスをインスタンス化したらすぐにデフォルト値を設定して準備する必要があります。

以下に、可能性とそれらが抱える問題の一部を示します。

  • A)MyModelinit()メソッドをオーバーライドし、isNewRecordがtrueの場合にデフォルト値を割り当てます。

    public function init() {
        if ($this->isNewRecord) {
            $this->attr = 'defaultValue';
        }
        parent::init();
    }
    

    問題:検索。 MySearchModelのデフォルト属性を明示的に設定解除しない限り(忘れやすいので非常にエラーが発生しやすい)、派生MySearchModelクラスのsearch()を呼び出す前に値を設定します。検索に干渉します(attr属性は既に設定されているため、検索は誤った結果を返します)。 Yii1.1では、これはunsetAttributes()を呼び出す前に search() を呼び出すことで解決されましたが、Yii2にはそのようなメソッドはありません。

  • B)MyModelbeforeSave() メソッドを次のようにオーバーライドします。

    public function beforeSave($insert) {
        if ($insert) {
            $this->attr = 'defaultValue';
        }
        return parent::beforeSave();
    }
    

    問題:未保存のレコードに属性が設定されていません。 (new MyModel)->attrnullです。さらに悪いことに、beforeSave()after検証と呼ばれるため、この値に依存する他の検証ルールでさえアクセスできません。

  • C)検証中に値を使用できるようにするには、代わりに beforeValidate() メソッドをオーバーライドしてデフォルトを設定します。そのような値:

    public function beforeValidate() {
        if ($this->isNewRecord) {
            $this->attr = 'defaultValue';
        }
        return parent::beforeValidate();
    }
    

    問題:属性は未保存(未検証)のレコードにまだ設定されていません。デフォルト値を取得したい場合は、少なくとも$model->validate()を呼び出す必要があります。

  • D)rules()DefaultValidator を使用して、検証中にデフォルトの属性値を設定します。

    public function rules() {
        return [
            [
                'attr', 'default',
                'value' => 'defaultValue',
                'on' => 'insert', // instantiate model with this scenario
            ],
            // ...
        ];
    }
    

    問題:B)およびC)と同じ。レコードを実際に保存または検証するまで、値は設定されません。

それではisがデフォルトの属性値を設定する正しい方法でしょうか?概説された問題のない他の方法はありますか?

12
mae

これはYiiの肥大化した多目的ActiveRecordsとのハングアップです

私の謙虚な意見では、フォームモデル、アクティブレコード、および検索モデルは、個別のクラス/サブクラスに分割する方が良いでしょう

検索モデルとフォームモデルを分割してみませんか?

abstract class Creature extends ActiveRecord {
    ...
}

class CreatureForm extends Creature {

    public function init() {
        parent::init();
        if ($this->isNewRecord) {
            $this->number_of_legs = 4;
        }
    }
}

class CreatureSearch extends Creature {

    public function search() {
        ...
    }
}

このアプローチの利点は

  • 多数のifやスイッチに頼ることなく、さまざまな検証に簡単に対応し、ケースをセットアップして表示できます。
  • 親クラスに共通のコードを保持して、繰り返しを避けることができます
  • 他のサブクラスにどのように影響するかを心配することなく、各サブクラスに変更を加えることができます
  • 個々のクラスは、正しく機能するために兄弟/子供の存在を知る必要はありません。

実際、最新のプロジェクトでは、関連するActiveRecordからまったく拡張しない検索モデルを使用しています

5
Arth

これを行うには2つの方法があります。

$model => new Model();

$modelには、データベーステーブルのすべてのデフォルト属性があります。

または、ルールで次を使用できます。

[['field_name'], 'default', 'value'=> $defaultValue],

$modelは常に、指定したデフォルト値で作成されます。

コアバリデーターの完全なリストはこちらでご覧いただけます http://www.yiiframework.com/doc-2.0/guide-tutorial-core-validators.html

17
Coz

私はそれが答えられていることを知っていますが、私のアプローチを追加します。 ApplicationモデルとApplicationSearchモデルがあります。アプリケーションモデルでは、現在のインスタンスのチェックでinitを追加します。 ApplicationSearchの場合、初期化をスキップします。

    public function init()
    { 
        if(!$this instanceof ApplicationSearch)  
        {
            $this->id = hash('sha256',  123);
        }

        parent::init();
    }

また、以下にコメントする@maeのように、現在のインスタンスに検索メソッドが存在するかどうかを確認できます。名前検索のメソッドを非検索ベースモデルに追加しなかったと仮定すると、コードは次のようになります。

    public function init()
    { 
        // no search method is available in Gii generated Non search class
        if(!method_exists($this,'search'))  
        {
            $this->id = hash('sha256',  123);
        }

        parent::init();
    }
6
Stefano Mtangoo

私はあなたの質問を何度か読みましたが、いくつかの矛盾があると思います。
検証前および検証中にデフォルトを読み取り可能にしてから、init()またはbeforeSave()を試します。したがって、モデルのデフォルト値を設定して、ライフサイクルの一部にできるだけ長く存在し、派生クラスに干渉しないようにするには、オブジェクトを初期化した後に設定するだけです。

すべてのデフォルトが設定されている別個のメソッドを準備し、明示的に呼び出すことができます。

$model = new Model;
$model->setDefaultValues();

または、静的メソッドを作成して、すべてのデフォルト値が設定されたモデルを作成し、そのインスタンスを返すことができます。

$model = Model::createNew();

または、コンストラクタにデフォルト値を渡すことができます。

$model = new Model([
    'attribute1' => 'value1',
    'attribute2' => 'value2',
]);

これは、属性を直接設定する場合と大差ありません。

$model = new Model;
$model->attribute1 = 'value1';
$model->attribute2 = 'value2';

すべては、モデルをコントローラーに対してどれだけ透過的にしたいかによって異なります。

このように、直接初期化を除くライフサイクル全体に属性が設定され、派生検索モデルに干渉しません。

2
Bizley

次のようにモデルの__construct()メソッドをオーバーライドするだけです:

class MyModel extends \yii\db\ActiveRecord {

    function __construct(array $config = [])
       {
           parent::__construct($config);
           $this->attr = 'defaultValue';
       }
    ...
}