db-active-record.md 52.4 KB
Newer Older
1 2
アクティブレコード
==================
3

4
> Note|注意: この節はまだ執筆中です。
5

6 7 8
[アクティブレコード](http://ja.wikipedia.org/wiki/Active_Record) は、データベースに保存されているデータにアクセスするために、オブジェクト指向のインタフェイスを提供するものです。
アクティブレコードクラスはデータベーステーブルと関連付けられて、アクティブレコードのインスタンスがそのテーブルの行に対応し、アクティブレコードのインスタンスの属性がその行のカラムの値を表現します。
生の SQL 文を書く代りに、アクティブレコードを使って、オブジェクト指向の流儀でデータベーステーブルのデータを操作することが出来ます。
9

10 11
例えば、`Customer``customer` テーブルに関連付けられたアクティブレコードクラスであり、`name``customer` テーブルのカラムであると仮定しましょう。
`customer` テーブルに新しい行を挿入するために次のコードを書くことが出来ます。
12 13 14 15 16 17 18

```php
$customer = new Customer();
$customer->name = 'Qiang';
$customer->save();
```

19
上記のコードは、次のように生の SQL 文を使うのと等価なものですが、生の SQL 文の方は、直感的でなく、間違いも生じやすく、また、DBMS の違いによる互換性の問題も生じ得ます。
20 21 22 23 24 25 26

```php
$db->createCommand('INSERT INTO customer (name) VALUES (:name)', [
    ':name' => 'Qiang',
])->execute();
```

27
下記が、現在 Yii のアクティブレコードによってサポートされているデータベースのリストです。
28

29 30 31
* MySQL 4.1 以降: [[yii\db\ActiveRecord]] による。
* PostgreSQL 7.3 以降: [[yii\db\ActiveRecord]] による。
* SQLite 2 および 3: [[yii\db\ActiveRecord]] による。
32
* Microsoft SQL Server 2008 以降: [[yii\db\ActiveRecord]] による。
33 34 35 36 37 38 39
* Oracle: [[yii\db\ActiveRecord]] による。
* CUBRID 9.3 以降: [[yii\db\ActiveRecord]] による。(cubrid PDO 拡張の [バグ](http://jira.cubrid.org/browse/APIS-658)
  のために、値を引用符で囲む機能が動作しません。そのため、サーバだけでなくクライアントも CUBRID 9.3 が必要になります)
* Sphnix: [[yii\sphinx\ActiveRecord]] による。`yii2-sphinx` エクステンションが必要。
* ElasticSearch: [[yii\elasticsearch\ActiveRecord]] による。`yii2-elasticsearch` エクステンションが必要。
* Redis 2.6.12 以降: [[yii\redis\ActiveRecord]] による。`yii2-redis` エクステンションが必要。
* MongoDB 1.3.0 以降: [[yii\mongodb\ActiveRecord]] による。`yii2-mongodb` エクステンションが必要。
40

41 42 43
ご覧のように、Yii はリレーショナルデータベースだけでなく NoSQL データベースに対してもアクティブレコードのサポートを提供しています。
このチュートリアルでは、主としてリレーショナルデータベースのためのアクティブレコードの使用方法を説明します。
しかし、ここで説明するほとんどの内容は NoSQL データベースのためのアクティブレコードにも適用することが出来るものです。
44 45


46 47
アクティブレコードクラスを宣言する
----------------------------------
48

49
アクティブレコードクラスを宣言するためには、[[yii\db\ActiveRecord]] を拡張して、クラスと関連付けられるデータベーステーブルの名前を返す `tableName` メソッドを実装する必要があります。
50 51 52 53 54 55 56 57 58 59 60 61

```php
namespace app\models;

use yii\db\ActiveRecord;

class Customer extends ActiveRecord
{
    const STATUS_ACTIVE = 'active';
    const STATUS_DELETED = 'deleted';
    
    /**
62
     * @return string アクティブレコードクラスと関連付けられるデータベーステーブルの名前
63 64 65 66 67 68 69 70 71
     */
    public static function tableName()
    {
        return 'customer';
    }
}
```


72 73
カラムのデータにアクセスする
----------------------------
74

75 76 77
アクティブレコードは、対応するデータベーステーブルの行の各カラムをアクティブレコードオブジェクトの属性に割り付けます。
属性は通常のオブジェクトのパブリックなプロパティと同様の振る舞いをします。
属性の名前は対応するから無名と同じであり、大文字と小文字を区別します。
78

79
カラムの値を読み出すために、次の構文を使用することが出来ます。
80 81

```php
82
// "id" と "email" は、$customer アクティブレコードオブジェクトと関連付けられたテーブルのカラム名
83 84 85 86
$id = $customer->id;
$email = $customer->email;
```

87
カラムの値を変更するためには、関連付けられたプロパティに新しい値を代入して、オブジェクトを保存します。
88 89 90 91 92 93

```php
$customer->email = 'jane@example.com';
$customer->save();
```

94 95 96 97 98
> Note|注意: 自明なことですが、カラム名が直接にアクティブレコードクラスの属性名になりますので、データベースの命名スキーマでアンダースコアを使用している場合はアンダースコアを持つ属性名になります。
> 例えば、`user_name` というカラムは、アクティブレコードのオブジェクトでは `$user->user_name` としてアクセスされることになります。
> コードスタイルが気になるのであれば、データベースの命名スキーマも camelCase を使用しなければなりません。
> しかしながら、camelCase の使用は要求されてはいません。Yii は他のどのような命名スタイルでも十分に動作します。

99

100
データベースに接続する
101 102
----------------------

103 104 105
アクティブレコードは、データベースとの間でデータを交換するために [[yii\db\Connection|DB 接続]] を使用します。
既定では、アクティブレコードは `db` [アプリケーションコンポーネント](structure-application-components.md) を接続として使用します。
[データベースの基礎](db-dao.md) で説明したように、次のようにして、アプリケーションの構成情報ファイルの中で `db` コンポーネントを構成することが出来ます。
106 107 108 109 110 111 112 113 114 115 116 117 118 119

```php
return [
    'components' => [
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'mysql:host=localhost;dbname=testdb',
            'username' => 'demo',
            'password' => 'demo',
        ],
    ],
];
```

120
アプリケーションの中で複数のデータベースを使っており、アクティブレコードクラスのために異なる DB 接続を使いたい場合は、[[yii\db\ActiveRecord::getDb()|getDb()]] メソッドをオーバーライドすることが出来ます。
121 122 123 124 125 126 127 128

```php
class Customer extends ActiveRecord
{
    // ...

    public static function getDb()
    {
129
        return \Yii::$app->db2;  // "db2" アプリケーションコンポーネントを使用
130 131 132 133 134
    }
}
```


135 136
データベースにデータを問い合わせる
----------------------------------
137

138
アクティブレコードは、DB クエリを構築してアクティブレコードインスタンスにデータを投入するために、二つの入力メソッドを提供しています。
139 140 141 142

 - [[yii\db\ActiveRecord::find()]]
 - [[yii\db\ActiveRecord::findBySql()]]

143 144 145
この二つのメソッドは [[yii\db\ActiveQuery]] のインスタンスを返します。
 [[yii\db\ActiveQuery]] は [[yii\db\Query]] を拡張したものであり、従って、[[yii\db\Query]] と同じ一連の柔軟かつ強力な DB クエリ構築メソッド、例えば、`where()``join()``orderBy()` 等を提供します。
下記の例は、いくつかの可能性を示すものです。
146 147

```php
148
// *アクティブ* な顧客を全て読み出して、その ID によって並べ替える
149 150 151 152 153
$customers = Customer::find()
    ->where(['status' => Customer::STATUS_ACTIVE])
    ->orderBy('id')
    ->all();

154
// ID が 1 である一人の顧客を返す
155 156 157 158
$customer = Customer::find()
    ->where(['id' => 1])
    ->one();

159
// *アクティブ* な顧客の数を返す
160 161 162 163
$count = Customer::find()
    ->where(['status' => Customer::STATUS_ACTIVE])
    ->count();

164
// 結果を顧客 ID によってインデックスする
165
$customers = Customer::find()->indexBy('id')->all();
166
// $customers 配列は顧客 ID によってインデックスされる
167

168
// 生の SQL 文を使って顧客を読み出す
169 170 171 172
$sql = 'SELECT * FROM customer';
$customers = Customer::findBySql($sql)->all();
```

173
> Tip|ヒント: 上記のコードでは、`Customer::STATUS_ACTIVE` は `Customer` で定義されている定数です。
174
  コードの中で、ハードコードされた文字列や数字ではなく、意味が分かる名前の定数を使用することは良いプラクティスです。
175 176


177 178 179 180
プライマリキーの値または一連のカラムの値に合致するアクティブレコードのインスタンスを返すためのショートカットメソッドが二つ提供されています。
すなわち、`findOne()``findAll()` です。
前者は合致する最初のインスタンスを返し、後者は合致する全てのインスタンスを返します。
例えば、
181 182

```php
183
// ID が 1 である顧客を一人返す
184 185
$customer = Customer::findOne(1);

186
// ID が 1 である *アクティブ* な顧客を一人返す
187 188 189 190 191
$customer = Customer::findOne([
    'id' => 1,
    'status' => Customer::STATUS_ACTIVE,
]);

192
// ID が 1、2、または 3 である顧客を全て返す
193 194
$customers = Customer::findAll([1, 2, 3]);

195
// 状態が「削除済み」である顧客を全て返す
196 197 198 199 200
$customer = Customer::findAll([
    'status' => Customer::STATUS_DELETED,
]);
```

201 202 203 204
> Note: デフォルトでは、`findOne()` も `one()` も、クエリに `LIMIT 1` を追加しません。
  クエリが一つだけまたは少数の行のデータしか返さないことが分かっている場合 (例えば、プライマリキーか何かでクエリをする場合) は、これで十分であり、また、この方が望ましいでしょう。
  しかし、クエリが多数の行のデータを返す可能性がある場合は、パフォーマンスを向上させるために `limit(1)` を呼ぶべきです。
  例えば、`Customer::find()->where(['status' => Customer::STATUS_ACTIVE])->limit(1)->one()` のように。
205 206


207
### データを配列に読み出す
208

209 210
大量のデータを処理する場合には、メモリ使用量を節約するために、データベースから取得したデータを配列に保持したいこともあるでしょう。
これは、`asArray()` を呼ぶことによって実現できます。
211 212

```php
213
// 顧客を `Customer` オブジェクトでなく配列の形式で返す
214 215 216
$customers = Customer::find()
    ->asArray()
    ->all();
217
// $customers の各要素は、「名前-値」のペアの配列
218 219
```

220 221 222 223
このメソッドはメモリを節約してパフォーマンスを向上させますが、低い抽象レイヤに向って一歩を踏み出すものであり、アクティブレコードのレイヤが持ついくつかの機能を失うことになるという点に注意してください。
`asArray` を使ってデータを読み出すことは、[クエリビルダ](db-dao.md) を使って普通のクエリを実行するのと、ほとんど同じことです。
`asArray` を使うと、結果は、型変換の実行を伴わない単純な配列になります。
その結果、アクティブレコードオブジェクトでアクセスする場合には整数になるフィールドが、文字列の値を含むことがあり得ます。
224

225
### データをバッチモードで読み出す
226

227 228 229
[クエリビルダ](db-query-builder.md) において、大量のデータをデータベースから検索する場合に、メモリ使用量を最小化するために *バッチクエリ* を使うことが出来るということを説明しました。
おなじテクニックをアクティブレコードでも使うことが出来ます。
例えば、
230 231

```php
232
// 一度に 10 人の顧客を読み出す
233
foreach (Customer::find()->batch(10) as $customers) {
234
    // $customers は 10 以下の Customer オブジェクトの配列
235
}
236
// 一度に 10 人の顧客を読み出して、一人ずつ反復する
237
foreach (Customer::find()->each(10) as $customer) {
238
    // $customer は Customer オブジェクト
239
}
240
// いーがーローディングをするバッチクエリ
241 242 243 244 245
foreach (Customer::find()->with('orders')->each() as $customer) {
}
```


246 247
データベースのデータを操作する
------------------------------
248

249
アクティブレコードは、一つのアクティブレコードインスタンスに関連付けられたテーブルの一行を挿入、更新または削除するために、次のメソッドを提供しています。
250 251 252 253 254 255

- [[yii\db\ActiveRecord::save()|save()]]
- [[yii\db\ActiveRecord::insert()|insert()]]
- [[yii\db\ActiveRecord::update()|update()]]
- [[yii\db\ActiveRecord::delete()|delete()]]

256 257 258
アクティブレコードは、アクティブレコードクラスと関連付けられたテーブル全体に適用する、次の静的なメソッドを提供しています。
これらのメソッドはテーブル全体に影響を与えますので、使用するときはこの上なく注意深くしなければなりません。
例えば、`deleteAll()` はテーブルの全ての行を削除します。
259 260 261 262 263 264 265

- [[yii\db\ActiveRecord::updateCounters()|updateCounters()]]
- [[yii\db\ActiveRecord::updateAll()|updateAll()]]
- [[yii\db\ActiveRecord::updateAllCounters()|updateAllCounters()]]
- [[yii\db\ActiveRecord::deleteAll()|deleteAll()]]


266
次の例は、これらのメソッドの使用方法を示すものです。
267 268

```php
269
// 新しい customer のレコードを挿入する
270 271 272
$customer = new Customer();
$customer->name = 'James';
$customer->email = 'james@example.com';
273
$customer->save();  // $customer->insert() と等値
274

275
// 既存の customer のレコードを更新する
276 277
$customer = Customer::findOne($id);
$customer->email = 'james@example.com';
278
$customer->save();  // $customer->update() と等値
279

280
// 既存の customer のレコードを削除する
281 282 283
$customer = Customer::findOne($id);
$customer->delete();

284
// いくつかの customer のレコードを削除する
285 286
Customer::deleteAll('age > :age AND gender = :gender', [':age' => 20, ':gender' => 'M']);

287
// すべてのレコードの年齢に 1 を追加する
288 289 290
Customer::updateAllCounters(['age' => 1]);
```

291 292 293 294
> Info|情報: `save()` メソッドは、アクティブレコードインスタンスが新しいものであるか否かに従って、`insert()` または `update()` を呼びます
   (内部的には、[[yii\db\ActiveRecord::isNewRecord]] の値をチェックして判断します)。
  アクティブレコードのインスタンスが `new` 演算子によって作成された場合は、`save()` を呼ぶと、テーブルに新しい行が挿入されます。
  データベースから読み出されたアクティブレコードに対して `save()` を呼ぶと、テーブルの中の対応する行が更新されます。
295 296


297
### データの入力と検証
298

299 300 301 302
アクティブレコードは [[yii\base\Model]] を拡張したものですので、[モデル](structure-models.md) で説明したのと同じデータ入力と検証の機能をサポートしています。
例えば、[[yii\base\Model::rules()|rules()]] メソッドをオーバーライドして検証規則を宣言することが出来ます。
アクティブレコードインスタンスにユーザの入力データを一括代入することも出来ます。
また、[[yii\base\Model::validate()|validate()]] を呼んで、データ検証を実行させることも出来ます。
303

304 305
`save()``insert()` または `update()` を呼ぶと、これらのメソッドが自動的に [[yii\base\Model::validate()|validate()]] を呼びます。
検証が失敗すると、対応するデータ保存操作はキャンセルされます。
306

307
次の例は、アクティブレコードを使ってユーザ入力を収集/検証してデータベースに保存する方法を示すものです。
308 309

```php
310
// 新しいレコードを作成する
311 312
$model = new Customer;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
313
    // ユーザ入力が収集、検証されて、保存された
314 315
}

316
// プライマリキーが $id であるレコードを更新する
317 318 319 320 321
$model = Customer::findOne($id);
if ($model === null) {
    throw new NotFoundHttpException;
}
if ($model->load(Yii::$app->request->post()) && $model->save()) {
322
    // ユーザ入力が収集、検証されて、保存された
323 324 325 326
}
```


327
### デフォルト値を読み出す
328

329 330 331
テーブルのカラムの定義は、デフォルト値を含むことが出来ます。
アクティブレコードのためのウェブフォームに、このデフォルト値を事前に代入しておきたい場合があるでしょう。
そうするためには、フォームを表示する前に、[[yii\db\ActiveRecord::loadDefaultValues()|loadDefaultValues()]] を呼びます。
332 333 334 335

```php
$customer = new Customer();
$customer->loadDefaultValues();
336
// ... $customer の HTML フォームを表示する ...
337 338
```

339 340
属性に対して何かの初期値を自分自身で設定したい場合は、アクティブレコードクラスの `init()` メソッドをオーバーライドして、そこで値を設定することが出来ます。
例えば、`status` 属性のデフォルト値を設定したい場合は、
341 342 343 344 345

```php
public function init()
{
    parent::init();
346
    $this->status = self::STATUS_ACTIVE;
347 348 349
}
```

350 351
アクティブレコードのライフサイクル
----------------------------------
352

353 354 355
アクティブレコードがデータベースのデータの操作に使われるときのライフサイクルを理解しておくことは重要なことです。
そのライフサイクルは、概して、対応するイベントと関連付けられており、それらのイベントに対して干渉したり反応したりするコードを注入できるようになっています。
これらのイベントは特にアクティブレコードの [ビヘイビア](concept-behaviors.md) を開発するときに役に立ちます。
356

357
アクティブレコードの新しいインスタンスを作成する場合は、次のライフサイクルを経ます。
358

359 360
1. コンストラクタ
2. [[yii\db\ActiveRecord::init()|init()]]: [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] イベントをトリガ
361

362
[[yii\db\ActiveRecord::find()|find()]] メソッドによってデータを検索する場合は、新しくデータを投入されるアクティブレコードの全てが、それぞれ、次のライフサイクルを経ます。
363

364 365 366
1. コンストラクタ
2. [[yii\db\ActiveRecord::init()|init()]]: [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] イベントをトリガ
3. [[yii\db\ActiveRecord::afterFind()|afterFind()]]: [[yii\db\ActiveRecord::EVENT_AFTER_FIND|EVENT_AFTER_FIND]] イベントをトリガ
367

368
[[yii\db\ActiveRecord::save()|save()]] を呼んで、アクティブレコードを挿入または更新する場合は、次のライフサイクルを経ます。
369

370 371 372 373 374
1. [[yii\db\ActiveRecord::beforeValidate()|beforeValidate()]]: [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] イベントをトリガ
2. [[yii\db\ActiveRecord::afterValidate()|afterValidate()]]: [[yii\db\ActiveRecord::EVENT_AFTER_VALIDATE|EVENT_AFTER_VALIDATE]] イベントをトリガ
3. [[yii\db\ActiveRecord::beforeSave()|beforeSave()]]: [[yii\db\ActiveRecord::EVENT_BEFORE_INSERT|EVENT_BEFORE_INSERT]] または [[yii\db\ActiveRecord::EVENT_BEFORE_UPDATE|EVENT_BEFORE_UPDATE]] イベントをトリガ
4. 実際のデータ挿入または更新を実行
5. [[yii\db\ActiveRecord::afterSave()|afterSave()]]: [[yii\db\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] または [[yii\db\ActiveRecord::EVENT_AFTER_UPDATE|EVENT_AFTER_UPDATE]] イベントをトリガ
375

376
最後に、[[yii\db\ActiveRecord::delete()|delete()]] を呼んで、アクティブレコードを削除する場合は、次のライフサイクルを経ます。
377

378 379 380
1. [[yii\db\ActiveRecord::beforeDelete()|beforeDelete()]]: [[yii\db\ActiveRecord::EVENT_BEFORE_DELETE|EVENT_BEFORE_DELETE]] イベントをトリガ
2. 実際のデータ削除を実行
3. [[yii\db\ActiveRecord::afterDelete()|afterDelete()]]: [[yii\db\ActiveRecord::EVENT_AFTER_DELETE|EVENT_AFTER_DELETE]] イベントをトリガ
381 382


383 384
リレーショナルデータを扱う
--------------------------
385

386 387 388
テーブルのリレーショナルデータもアクティブレコードを使ってクエリすることが出来ます
(すなわち、テーブル A のデータを選択すると、テーブル B の関連付けられたデータも一緒に取り込むことが出来ます)。
アクティブレコードのおかげで、返されるリレーショナルデータは、プライマリテーブルと関連付けられたアクティブレコードオブジェクトのプロパティのようにアクセスすることが出来ます。
389

390
例えば、適切なリレーションが宣言されていれば、`$customer->orders` にアクセスすることによって、指定された顧客が発行した注文を表す `Order` オブジェクトの配列を取得することが出来ます。
391

392 393
リレーションを宣言するためには、[[yii\db\ActiveQuery]] オブジェクトを返すゲッターメソッドを定義します。そして、その [[yii\db\ActiveQuery]] オブジェクトは、リレーションのコンテキストに関する情報を持ち、従って関連するレコードだけをクエリするものとします。
例えば、
394 395 396 397 398 399

```php
class Customer extends \yii\db\ActiveRecord
{
    public function getOrders()
    {
400
        // Customer は Order.customer_id -> id によって、複数の Order を持つ
401 402 403 404 405 406 407 408
        return $this->hasMany(Order::className(), ['customer_id' => 'id']);
    }
}

class Order extends \yii\db\ActiveRecord
{
    public function getCustomer()
    {
409
        // Order は Customer.id -> customer_id によって、一つの Customer を持つ
410 411 412 413 414
        return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
    }
}
```

415 416 417
上記の例で使用されている [[yii\db\ActiveRecord::hasMany()]] と [[yii\db\ActiveRecord::hasOne()]] のメソッドは、リレーショナルデータベースにおける多対一と一対一の関係を表現するために使われます。
例えば、顧客 (customer) は複数の注文 (order) を持ち、注文 (order) は一つの顧客 (customer)を持つ、という関係です。
これらのメソッドはともに二つのパラメータを取り、[[yii\db\ActiveQuery]] オブジェクトを返します。
418

419 420 421
 - `$class`: 関連するモデルのクラス名。これは完全修飾のクラス名でなければなりません。
 - `$link`: 二つのテーブルに属するカラム間の関係。これは配列として与えられなければなりません。
   配列のキーは、`$class` と関連付けられるテーブルにあるカラムの名前であり、配列の値はリレーションを宣言しているクラスのテーブルにあるカラムの名前です。
422
   リレーションをテーブルの外部キーに基づいて定義するのが望ましいプラクティスです。
423

424
リレーションを宣言した後は、リレーショナルデータを取得することは、対応するゲッターメソッドで定義されているコンポーネントのプロパティを取得するのと同じように、とても簡単なことになります。
425 426

```php
427
// 顧客の注文を取得する
428
$customer = Customer::findOne(1);
429
$orders = $customer->orders;  // $orders は Order オブジェクトの配列
430 431
```

432
舞台裏では、上記のコードは、各行について一つずつ、次の二つの SQL クエリを実行します。
433 434 435 436 437 438

```sql
SELECT * FROM customer WHERE id=1;
SELECT * FROM order WHERE customer_id=1;
```

439 440 441 442
> Tip|情報: `$customer->orders` という式に再びアクセスした場合は、第二の SQL クエリはもう実行されません。
  第二の SQL クエリは、この式が最初にアクセスされた時だけ実行されます。
  二度目以降のアクセスでは、内部的にキャッシュされている以前に読み出した結果が返されるだけです。
  リレーショナルデータを再クエリしたい場合は、単純に、まず既存の式を未設定状態に戻して (`unset($customer->orders);`) から、再度、`$customer->orders` にアクセスします。
443

444 445 446
場合によっては、リレーショナルクエリにパラメータを渡したいことがあります。
例えば、顧客の注文を全て返す代りに、小計が指定した金額を超える大きな注文だけを返したいことがあるでしょう。
そうするためには、次のようなゲッターメソッドで `bigOrders` リレーションを宣言します。
447 448 449 450 451 452 453 454 455 456 457 458 459

```php
class Customer extends \yii\db\ActiveRecord
{
    public function getBigOrders($threshold = 100)
    {
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])
            ->where('subtotal > :threshold', [':threshold' => $threshold])
            ->orderBy('id');
    }
}
```

460
`hasMany()` が 返す [[yii\db\ActiveQuery]] は、[[yii\db\ActiveQuery]] のメソッドを呼ぶことでクエリをカスタマイズ出来るものであることを覚えておいてください。
461

462 463
上記の宣言によって、`$customer->bigOrders` にアクセスした場合は、小計が 100 以上である注文だけが返されることになります。
異なる閾値を指定するためには、次のコードを使用します。
464 465 466 467 468

```php
$orders = $customer->getBigOrders(200)->all();
```

469 470 471 472
> Note|注意: リレーションメソッドは [[yii\db\ActiveQuery]] のインスタンスを返します。
リレーションを属性 (すなわち、クラスのプロパティ) としてアクセスした場合は、返り値はリレーションのクエリ結果となります。
クエリ結果は、リレーションが複数のレコードを返すものか否かに応じて、[[yii\db\ActiveRecord]] の一つのインスタンス、またはその配列、または null となります。
例えば、`$customer->getOrders()``ActiveQuery` のインスタンスを返し、`$customer->orders``Order` オブジェクトの配列 (またはクエリ結果が無い場合は空の配列) を返します。
473 474


475
中間テーブルを使うリレーション
476
------------------------------
477

478
場合によっては、二つのテーブルが [中間テーブル][] と呼ばれる中間的なテーブルによって関連付けられていることがあります。
479
そのようなリレーションを宣言するために、[[yii\db\ActiveQuery::via()|via()]] または [[yii\db\ActiveQuery::viaTable()|viaTable()]] メソッドを呼んで、[[yii\db\ActiveQuery]] オブジェクトをカスタマイズすることが出来ます。
480

481
例えば、テーブル `order` とテーブル `item` が中間テーブル `order_item` によって関連付けられている場合、`Order` クラスにおいて `items` リレーションを次のように宣言することが出来ます。
482 483 484 485 486 487 488 489 490 491 492 493

```php
class Order extends \yii\db\ActiveRecord
{
    public function getItems()
    {
        return $this->hasMany(Item::className(), ['id' => 'item_id'])
            ->viaTable('order_item', ['order_id' => 'id']);
    }
}
```

494 495
[[yii\db\ActiveQuery::via()|via()]] メソッドは、最初のパラメータとして、結合テーブルの名前ではなく、アクティブレコードクラスで宣言されているリレーションの名前を取ること以外は、[[yii\db\ActiveQuery::viaTable()|viaTable()]] と同じです。
例えば、上記の `items` リレーションは次のように宣言しても等値です。
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512

```php
class Order extends \yii\db\ActiveRecord
{
    public function getOrderItems()
    {
        return $this->hasMany(OrderItem::className(), ['order_id' => 'id']);
    }

    public function getItems()
    {
        return $this->hasMany(Item::className(), ['id' => 'item_id'])
            ->via('orderItems');
    }
}
```

513
[中間テーブル]: https://en.wikipedia.org/wiki/Junction_table "Junction table on Wikipedia"
514 515


516 517
レイジーローディングとイーガーローディング
------------------------------------------
518

519 520 521 522
前に述べたように、関連オブジェクトに最初にアクセスしたときに、アクティブレコードは DB クエリを実行して関連データを読み出し、それを関連オブジェクトに投入します。
同じ関連オブジェクトに再度アクセスしても、クエリは実行されません。
これを *レイジーローディング* と呼びます。
例えば、
523 524

```php
525
// 実行される SQL: SELECT * FROM customer WHERE id=1
526
$customer = Customer::findOne(1);
527
// 実行される SQL: SELECT * FROM order WHERE customer_id=1
528
$orders = $customer->orders;
529
// SQL は実行されない
530 531 532
$orders2 = $customer->orders;
```

533
レイジーローディングは非常に使い勝手が良いものです。しかし、次のシナリオでは、パフォーマンスの問題を生じ得ます。
534 535

```php
536
// 実行される SQL: SELECT * FROM customer WHERE id=1
537 538 539
$customers = Customer::find()->limit(100)->all();

foreach ($customers as $customer) {
540
    // 実行される SQL: SELECT * FROM order WHERE customer_id=...
541
    $orders = $customer->orders;
542
    // ... $orders を処理 ...
543 544 545
}
```

546 547 548
データベースに 100 人以上の顧客が登録されていると仮定した場合、上記のコードで何個の SQL クエリが実行されるでしようか?
101 です。最初の SQL クエリが 100 人の顧客を返します。
次に、100 人の顧客全てについて、それぞれ、顧客の注文を返すための SQL クエリが実行されます。
549

550
上記のパフォーマンスの問題を解決するためには、[[yii\db\ActiveQuery::with()]] を呼んでいわゆる *イーガーローディング* を使うことが出来ます。
551 552

```php
553 554
// 実行される SQL: SELECT * FROM customer LIMIT 100;
//                 SELECT * FROM orders WHERE customer_id IN (1,2,...)
555 556 557 558
$customers = Customer::find()->limit(100)
    ->with('orders')->all();

foreach ($customers as $customer) {
559
    // SQL は実行されない
560
    $orders = $customer->orders;
561
    // ... $orders を処理 ...
562 563 564
}
```

565
ご覧のように、同じ仕事をするのに必要な SQL クエリがたった二つになります。
566

567
> Info|情報: 一般化して言うと、`N` 個のリレーションのうち `M` 個のリレーションが `via()` または `viaTable()` によって定義されている場合、この `N` 個のリレーションをイーガーロードしようとすると、合計で `1+M+N` 個の SQL クエリが実行されます。
568
> 主たるテーブルの行を返すために一つ、`via()` または `viaTable()` の呼び出しに対応する `M` 個の中間テーブルのそれぞれに対して一つずつ、そして、`N` 個の関連テーブルのそれぞれに対して一つずつ、という訳です。
569

570 571
> Note|注意: イーガーローディングで `select()` をカスタマイズしようとする場合は、関連モデルにリンクするカラムを必ず含めてください。
> そうしないと、関連モデルは読み出されません。例えば、
572 573 574

```php
$orders = Order::find()->select(['id', 'amount'])->with('customer')->all();
575
// $orders[0]->customer は常に null になる。この問題を解決するためには、次のようにしなければならない。
576 577 578
$orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all();
```

579 580
場合によっては、リレーショナルクエリをその場でカスタマイズしたいことがあるでしょう。
これは、レイジーローディングでもイーガーローディングでも、可能です。例えば、
581 582 583

```php
$customer = Customer::findOne(1);
584
// レイジーローディング: SELECT * FROM order WHERE customer_id=1 AND subtotal>100
585 586
$orders = $customer->getOrders()->where('subtotal>100')->all();

587 588
// イーガーローディング: SELECT * FROM customer LIMIT 100
//                       SELECT * FROM order WHERE customer_id IN (1,2,...) AND subtotal>100
589 590 591 592 593 594 595 596
$customers = Customer::find()->limit(100)->with([
    'orders' => function($query) {
        $query->andWhere('subtotal>100');
    },
])->all();
```


597 598
逆リレーション
--------------
599

600 601
リレーションは、たいていの場合、ペアで定義することが出来ます。
例えば、`Customer``orders` という名前のリレーションを持ち、`Order``customer` という名前のリレーションを持つ、ということがあります。
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622

```php
class Customer extends ActiveRecord
{
    ....
    public function getOrders()
    {
        return $this->hasMany(Order::className(), ['customer_id' => 'id']);
    }
}

class Order extends ActiveRecord
{
    ....
    public function getCustomer()
    {
        return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
    }
}
```

623 624
次に例示するクエリを実行すると、注文 (order) のリレーションとして取得した顧客 (customer) が、最初にその注文をリレーションとして取得した顧客とは別の Customer オブジェクトになってしまうことに気付くでしょう。
また、`customer->orders` にアクセスすると一個の SQL が実行され、`order->customer` にアクセスするともう一つ別の SQL が実行されるということにも気付くでしょう。
625 626 627 628

```php
// SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
629
// "等しくない" がエコーされる
630 631 632
// SELECT * FROM order WHERE customer_id=1
// SELECT * FROM customer WHERE id=1
if ($customer->orders[0]->customer === $customer) {
633
    echo '等しい';
634
} else {
635
    echo '等しくない';
636 637 638
}
```

639
冗長な最後の SQL 文の実行を避けるためには、次のように、[[yii\db\ActiveQuery::inverseOf()|inverseOf()]] メソッドを呼んで、`customer``oerders` のリレーションに対して逆リレーションを宣言することが出来ます。
640 641 642 643 644 645 646 647 648 649 650 651

```php
class Customer extends ActiveRecord
{
    ....
    public function getOrders()
    {
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer');
    }
}
```

652
こうすると、上記と同じクエリを実行したときに、次の結果を得ることが出来ます。
653 654 655 656

```php
// SELECT * FROM customer WHERE id=1
$customer = Customer::findOne(1);
657
// "等しい" がエコーされる
658 659
// SELECT * FROM order WHERE customer_id=1
if ($customer->orders[0]->customer === $customer) {
660
    echo '等しい';
661
} else {
662
    echo '等しくない';
663 664 665
}
```

666 667
上記では、レイジーローディングにおいて逆リレーションを使う方法を示しました。
逆リレーションはイーガーローディングにも適用されます。
668 669 670 671 672

```php
// SELECT * FROM customer
// SELECT * FROM order WHERE customer_id IN (1, 2, ...)
$customers = Customer::find()->with('orders')->all();
673
// "等しい" がエコーされる
674
if ($customers[0]->orders[0]->customer === $customers[0]) {
675
    echo '等しい';
676
} else {
677
    echo '等しくない';
678 679 680
}
```

681 682
> Note|注意: 逆リレーションはピボットテーブルを含むリレーションに対しては定義することが出来ません。
> つまり、リレーションが [[yii\db\ActiveQuery::via()|via()]] または [[yii\db\ActiveQuery::viaTable()|viaTable()]] によって定義されている場合は、[[yii\db\ActiveQuery::inverseOf()]] を追加で呼ぶことは出来ません。
683 684


685
リレーションを使ってテーブルを結合する <a name="joining-with-relations">
686
--------------------------------------
687

688 689 690
リレーショナルデータベースを扱う場合、複数のテーブルを結合して、JOIN SQL 文にさまざまなクエリ条件とパラメータを指定することは、ごく当り前の仕事です。
その目的を達するために、[[yii\db\ActiveQuery::join()]] を明示的に呼んで JOIN クエリを構築する代りに、既存のリレーション定義を再利用して [[yii\db\ActiveQuery::joinWith()]] を呼ぶことが出来ます。
例えば、
691 692

```php
693
// 全ての注文を検索して、注文を顧客 ID と注文 ID でソートする。同時に "customer" をイーガーロードする。
694
$orders = Order::find()->joinWith('customer')->orderBy('customer.id, order.id')->all();
695
// 書籍を含む全ての注文を検索し、"books" をイーガーロードする。
696 697 698
$orders = Order::find()->innerJoinWith('books')->all();
```

699
上記において、[[yii\db\ActiveQuery::innerJoinWith()|innerJoinWith()]] メソッドは、結合タイプを `INNER JOIN` とする [[yii\db\ActiveQuery::joinWith()|joinWith()]] へのショートカットです。
700

701 702
一個または複数のリレーションを結合することが出来ます。リレーションにクエリ条件をその場で適用することも出来ます。
また、サブリレーションを結合することも出来ます。例えば、
703 704

```php
705 706
// 複数のリレーションを結合
// 書籍を含む注文で、過去 24 時間以内に登録した顧客によって発行された注文を検索する
707 708 709 710 711 712
$orders = Order::find()->innerJoinWith([
    'books',
    'customer' => function ($query) {
        $query->where('customer.created_at > ' . (time() - 24 * 3600));
    }
])->all();
713
// サブリレーションとの結合: 書籍および書籍の著者を結合
714 715 716
$orders = Order::find()->joinWith('books.author')->all();
```

717 718
舞台裏では、Yii は最初に JOIN SQL 文を実行して、その JOIN SQL に適用された条件を満たす主たるモデルを取得します。
そして、次にリレーションごとのクエリを実行して、対応する関連レコードを投入します。
719

720
[[yii\db\ActiveQuery::joinWith()|joinWith()]] と [[yii\db\ActiveQuery::with()|with()]] の違いは、前者が主たるモデルクラスのテーブルと関連モデルクラスのテーブルを結合して主たるモデルを読み出すのに対して、後者は主たるモデルクラスのテーブルに対してだけクエリを実行して主たるモデルを読み出す、という点にあります。
721

722 723 724
この違いによって、[[yii\db\ActiveQuery::joinWith()|joinWith()]] では、JOIN SQL 文だけに指定できるクエリ条件を適用することが出来ます。
例えば、上記の例のように、関連モデルに対する条件によって主たるモデルをフィルタすることが出来ます。
主たるモデルを関連テーブルのカラムを使って並び替えることも出来ます。
725

726 727
[[yii\db\ActiveQuery::joinWith()|joinWith()]] を使うときは、カラム名の曖昧さを解決することについて、あなたが責任を負わなければなりません。
上記の例では、order テーブルと item テーブルがともに `id` という名前のカラムを持っているため、`item.id``order.id` を使って、`id` カラムの参照の曖昧さを解決しています。
728

729 730
既定では、リレーションを結合すると、リレーションがイーガーロードされることにもなります。
この既定の動作は、指定されたリレーションをイーガーロードするかどうかを規定する `$eagerLoading` パラメータを渡して、変更することが出来ます。
731

732 733 734
また、既定では、[[yii\db\ActiveQuery::joinWith()|joinWith()]] は関連テーブルを結合するのに `LEFT JOIN` を使います。
結合タイプをカスタマイズするために `$joinType` パラメータを渡すことが出来ます。
`INNER JOIN` タイプのためのショートカットとして、[[yii\db\ActiveQuery::innerJoinWith()|innerJoinWith()]] を使うことが出来ます。
735

736
下記に、いくつかの例を追加します。
737 738

```php
739
// 書籍を含む注文を全て検索するが、"books" はイーガーロードしない。
740
$orders = Order::find()->innerJoinWith('books', false)->all();
741
// これも上と等値
742 743 744
$orders = Order::find()->joinWith('books', false, 'INNER JOIN')->all();
```

745 746
二つのテーブルを結合するとき、場合によっては、JOIN クエリの ON の部分で何らかの追加条件を指定する必要があります。
これは、次のように、[[yii\db\ActiveQuery::onCondition()]] メソッドを呼んで実現することが出来ます。
747 748 749 750 751 752 753 754 755 756 757

```php
class User extends ActiveRecord
{
    public function getBooks()
    {
        return $this->hasMany(Item::className(), ['owner_id' => 'id'])->onCondition(['category_id' => 1]);
    }
}
```

758 759
上記においては、[[yii\db\ActiveRecord::hasMany()|hasMany()]] メソッドが [[yii\db\ActiveQuery]] のインスタンスを返しています。
そして、それに対して [[yii\db\ActiveQuery::onCondition()|onCondition()]] が呼ばれて、`category_id` が 1 である品目だけが返されるべきことを指定しています。
760

761 762
[[yii\db\ActiveQuery::joinWith()|joinWith()]] を使ってクエリを実行すると、指定された ON 条件が対応する JOIN クエリの ON の部分に挿入されます。
例えば、
763 764 765 766 767 768 769

```php
// SELECT user.* FROM user LEFT JOIN item ON item.owner_id=user.id AND category_id=1
// SELECT * FROM item WHERE owner_id IN (...) AND category_id=1
$users = User::find()->joinWith('books')->all();
```

770 771
[[yii\db\ActiveQuery::with()]] を使ってイーガーロードする場合や、レイジーロードする場合には、JOIN クエリは使われないため、ON 条件が対応する SQL 文の WHERE の部分に挿入されることに注意してください。
例えば、
772 773 774 775 776 777 778 779 780

```php
// SELECT * FROM user WHERE id=10
$user = User::findOne(10);
// SELECT * FROM item WHERE owner_id=10 AND category_id=1
$books = $user->books;
```


781 782
関連付けを扱う
--------------
783

784
アクティブレコードは、二つのアクティブレコードオブジェクト間の関連付けを確立および破棄するために、次の二つのメソッドを提供しています。
785 786 787 788

- [[yii\db\ActiveRecord::link()|link()]]
- [[yii\db\ActiveRecord::unlink()|unlink()]]

789
例えば、顧客と新しい注文があると仮定したとき、次のコードを使って、その注文をその顧客のものとすることが出来ます。
790 791 792 793 794 795 796 797

```php
$customer = Customer::findOne(1);
$order = new Order();
$order->subtotal = 100;
$customer->link('orders', $order);
```

798
上記の [[yii\db\ActiveRecord::link()|link()]] の呼び出しは、注文の `customer_id``$customer` のプライマリキーの値を設定し、[[yii\db\ActiveRecord::save()|save()]] を呼んで注文をデータベースに保存します。
799 800


801 802
DBMS 間のリレーション
---------------------
803

804 805
アクティブレコードは、異なる DBMS に属するエンティティ間、例えば、リレーショナルデータベースのテーブルと MongoDB のコレクションの間に、リレーションを確立することを可能にしています。
そのようなリレーションでも、何も特別なコードは必要ありません。
806 807

```php
808
// リレーショナルデータベースのアクティブレコード
809 810 811 812 813 814 815 816 817
class Customer extends \yii\db\ActiveRecord
{
    public static function tableName()
    {
        return 'customer';
    }

    public function getComments()
    {
818
        // リレーショナルデータベースに保存されている Customer は、MongoDB コレクションに保存されている複数の Comment を持つ
819 820 821 822
        return $this->hasMany(Comment::className(), ['customer_id' => 'id']);
    }
}

823
// MongoDb のアクティブレコード
824 825 826 827 828 829 830 831 832
class Comment extends \yii\mongodb\ActiveRecord
{
    public static function collectionName()
    {
        return 'comment';
    }

    public function getCustomer()
    {
833
        // MongoDB コレクションに保存されている Comment は、リレーショナルデータベースに保存されている一つの Customer を持つ
834 835 836 837 838
        return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
    }
}
```

839
アクティブレコードの全ての機能、例えば、イーガーローディングやレイジーローディング、関連付けの確立や破棄などが、DBMS 間のリレーションでも利用可能です。
840

841 842
> Note|注意: DBMS ごとのアクティブレコードの実装には、DBMS 固有のメソッドや機能が含まれる場合があり、そういうものは DBMS 間のリレーションには適用できないということを忘れないでください。
  例えば、[[yii\db\ActiveQuery::joinWith()]] の使用が MongoDB コレクションに対するリレーションでは動作しないことは明白です。
843 844


845 846
スコープ
--------
847

848 849
[[yii\db\ActiveRecord::find()|find()]] または [[yii\db\ActiveRecord::findBySql()|findBySql()]] を呼ぶと、[[yii\db\ActiveQuery|ActiveQuery]] のインスタンスが返されます。
そして、追加のクエリメソッド、例えば、[[yii\db\ActiveQuery::where()|where()]] や [[yii\db\ActiveQuery::orderBy()|orderBy()]] を呼んで、クエリ条件をさらに指定することが出来ます。
850

851 852 853 854
別々の場所で同じ一連のクエリメソッドを呼びたいということがあり得ます。
そのような場合には、いわゆる *スコープ* を定義することを検討すべきです。
スコープは、本質的には、カスタムクエリクラスの中で定義されたメソッドであり、クエリオブジェクトを修正する一連のメソッドを呼ぶものです。
スコープを定義しておくと、通常のクエリメソッドを呼ぶ代りに、スコープを使うことが出来るようになります。
855

856 857 858
スコープを定義するためには二つのステップが必要です。
最初に、モデルのためのカスタムクエリクラスを作成して、このクラスの中に必要なスコープメソッドを定義します。
例えば、`Comment` モデルのために `CommentQuery` クラスを作成して、次のように、`active()` というスコープメソッドを定義します。
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874

```php
namespace app\models;

use yii\db\ActiveQuery;

class CommentQuery extends ActiveQuery
{
    public function active($state = true)
    {
        $this->andWhere(['active' => $state]);
        return $this;
    }
}
```

875
重要な点は、以下の通りです。
876

877 878 879
1. クラスは `yii\db\ActiveQuery` (または、`yii\mongodb\ActiveQuery` などの、その他の `ActiveQuery`) を拡張したものにしなければなりません。
2. メソッドは `public` で、メソッドチェーンが出来るように `$this` を返さなければなりません。メソッドはパラメータを取ることが出来ます。
3. クエリ条件を修正する方法については、[[yii\db\ActiveQuery]] のメソッド群を参照するのが非常に役に立ちます。
880

881 882
次に、[[yii\db\ActiveRecord::find()]] をオーバーライドして、通常の [[yii\db\ActiveQuery|ActiveQuery]] の代りに、カスタムクエリクラスを使うようにします。
上記の例のためには、次のコードを書く必要があります。
883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901

```php
namespace app\models;

use yii\db\ActiveRecord;

class Comment extends ActiveRecord
{
    /**
     * @inheritdoc
     * @return CommentQuery
     */
    public static function find()
    {
        return new CommentQuery(get_called_class());
    }
}
```

902
以上です。これで、カスタムスコープメソッドを使用することが出来ます。
903 904 905 906 907 908

```php
$comments = Comment::find()->active()->all();
$inactiveComments = Comment::find()->active(false)->all();
```

909
リレーションを定義するときにもスコープを使用することが出来ます。例えば、
910 911 912 913 914 915 916 917 918 919 920 921

```php
class Post extends \yii\db\ActiveRecord
{
    public function getActiveComments()
    {
        return $this->hasMany(Comment::className(), ['post_id' => 'id'])->active();

    }
}
```

922
または、リレーショナルクエリを実行するときに、その場でスコープを使うことも出来ます。
923 924 925 926 927 928 929 930 931

```php
$posts = Post::find()->with([
    'comments' => function($q) {
        $q->active();
    }
])->all();
```

932
### デフォルトスコープ
933

934 935 936 937
あなたが Yii 1.1 を前に使ったことがあれば、*デフォルトスコープ* と呼ばれる概念を知っているかも知れません。
デフォルトスコープは、全てのクエリに適用されるスコープです。
デフォルトスコープは、[[yii\db\ActiveRecord::find()]] をオーバライドすることによって、簡単に定義することが出来ます。
例えば、
938 939 940 941 942 943 944 945

```php
public static function find()
{
    return parent::find()->where(['deleted' => false]);
}
```

946
ただし、すべてのクエリにおいて、デフォルトの条件を上書きしないために、[[yii\db\ActiveQuery::where()|where()]] を使わず、[[yii\db\ActiveQuery::andWhere()|andWhere()]] または [[yii\db\ActiveQuery::orWhere()|orWhere()]] を使うべきであることに注意してください。
947 948


949 950
トランザクション操作
--------------------
951

952 953 954
アクティブレコードを扱う際には、二つの方法でトランザクション操作を処理することができます。
最初の方法は、"[データベースの基礎](db-dao.md)" の「トランザクション」の項で説明したように、全てを手作業でやる方法です。
もう一つの方法として、`transactions` メソッドを実装して、モデルのシナリオごとに、どの操作をトランザクションで囲むかを指定することが出来ます。
955 956 957 958 959 960 961 962 963

```php
class Post extends \yii\db\ActiveRecord
{
    public function transactions()
    {
        return [
            'admin' => self::OP_INSERT,
            'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
964
            // 上は次と等値
965 966 967 968 969 970
            // 'api' => self::OP_ALL,
        ];
    }
}
```

971 972 973
上記において、`admin``api` はモデルのシナリオであり、`OP_` で始まる定数は、これらのシナリオについてトランザクションで囲まれるべき操作を示しています。
サポートされている操作は、`OP_INSERT``OP_UPDATE`、そして、`OP_DELETE` です。
`OP_ALL` は三つ全てを示します。
974

975
このような自動的なトランザクションは、`beforeSave``afterSave``beforeDelete``afterDelete` によってデータベースに追加の変更を加えており、本体の変更と追加の変更の両方が成功した場合にだけデータベースにコミットしたい、というときに取り分けて有用です。
976

977 978
楽観的ロック
------------
979

980 981
楽観的ロックは、複数のユーザが編集のために同一のレコードにアクセスすることを許容しつつ、発生しうる衝突を回避するものです。
例えば、ユーザが (別のユーザが先にデータを修正したために) 陳腐化したデータに対してレコードの保存を試みた場合は、[[\yii\db\StaleObjectException]] 例外が投げられて、更新または削除はスキップされます。
982

983
楽観的ロックは、`update()``delete()` メソッドだけでサポートされ、既定では使用されません。
984

985
楽観的ロックを使用するためには、
986

987 988 989 990
1. 各行のバージョン番号を保存するカラムを作成します。カラムのタイプは `BIGINT DEFAULT 0` でなければなりません。
   `optimisticLock()` メソッドをオーバーライドして、このカラムの名前を返すようにします。
2. ユーザ入力を収集するウェブフォームに、更新されるレコードのロックバージョンを保持する隠しフィールドを追加します。
3. データ更新を行うコントローラアクションにおいて、[[\yii\db\StaleObjectException]] 例外を捕捉して、衝突を解決するために必要なビジネスロジック (例えば、変更をマージしたり、データの陳腐化を知らせたり) を実装します。
991

992
ダーティな属性
993 994
--------------

995 996 997
属性は、データベースからロードされた後、または最後のデータ保存の後に値が変更されると、ダーティであると見なされます。
そして、`save()``update()``insert()` などを呼んでレコードデータを保存するときは、ダーティな属性だけがデータベースに保存されます。
ダーティな属性が無い場合は、保存すべきものは無いことになり、クエリは何も発行されません。
998

999 1000 1001 1002
参照
----

以下も参照してください。
1003

1004
- [モデル](structure-models.md)
1005
- [[yii\db\ActiveRecord]]