Browse Source

docs/guide-ja/db-* and docs/guide-ja/input-* revised [ci skip] (#16174)

* guide-ja/db revising WIP [ci skip]

* guide-ja/db-dao.md, guide-ja/db-query-builder.md revised [ci skip]

* guide-ja/db-active-record.md revising WIP [ci skip]

* guide-ja/db-active-record.md revising WIP [ci skip]

* guide-ja/db-active-record.md revising WIP [ci skip]

* guide-ja/db-active-record.md revising WIP [ci skip]

* guide-ja/db-active-record.md revised [ci skip]

* guide-ja/db-migrations.md revised [ci skip]

* guide-ja/input-forms.md revised [ci skip]

* guide-ja/input-validation.md revised [ci skip]

* guide-ja/input-file-upload.md revised [ci skip]

* guide-ja/input revised [ci skip]
tags/2.0.16
Nobuo Kihara 7 years ago committed by Alexander Makarov
parent
commit
b9e345be69
  1. 386
      docs/guide-ja/db-active-record.md
  2. 125
      docs/guide-ja/db-dao.md
  3. 179
      docs/guide-ja/db-migrations.md
  4. 210
      docs/guide-ja/db-query-builder.md
  5. 34
      docs/guide-ja/input-file-upload.md
  6. 29
      docs/guide-ja/input-form-javascript.md
  7. 41
      docs/guide-ja/input-forms.md
  8. 7
      docs/guide-ja/input-multiple-models.md
  9. 16
      docs/guide-ja/input-tabular-input.md
  10. 164
      docs/guide-ja/input-validation.md

386
docs/guide-ja/db-active-record.md

@ -1,12 +1,15 @@
アクティブ・レコード
====================
[アクティブ・レコード](http://ja.wikipedia.org/wiki/Active_Record) は、データベースに保存されているデータにアクセスするために、オブジェクト指向のインタフェイスを提供するものです。
[アクティブ・レコード](http://ja.wikipedia.org/wiki/Active_Record) は、データベースに保存されているデータにアクセスするために、
オブジェクト指向のインタフェイスを提供するものです。
アクティブ・レコード・クラスはデータベース・テーブルと関連付けられます。
アクティブ・レコードのインスタンスはそのテーブルの行に対応し、アクティブ・レコードのインスタンスの *属性* がその行にある特定のカラムの値を表現します。
生の SQL 文を書く代りに、アクティブ・レコードの属性にアクセスしたり、アクティブ・レコードのメソッドを呼んだりして、データベース・テーブルに保存さているデータにアクセスしたり、データを操作したりします。
生の SQL 文を書く代りに、アクティブ・レコードの属性にアクセスしたり、アクティブ・レコードのメソッドを呼んだりして、
データベース・テーブルに保存さているデータにアクセスしたり、データを操作したりします。
例えば、`Customer` が `customer` テーブルに関連付けられたアクティブ・レコード・クラスであり、`name` が `customer` テーブルのカラムであると仮定しましょう。
例えば、`Customer` が `customer` テーブルに関連付けられたアクティブ・レコード・クラスであり、
`name``customer` テーブルのカラムであると仮定しましょう。
`customer` テーブルに新しい行を挿入するために次のコードを書くことが出来ます。
```php
@ -89,13 +92,16 @@ class Customer extends ActiveRecord
アクティブ・レコードのインスタンスは [モデル](structure-models.md) であると見なされます。
この理由により、私たちは通常 `app\models` 名前空間 (あるいはモデル・クラスを保管するための他の名前空間) の下にアクティブ・レコード・クラスを置きます。
[[yii\db\ActiveRecord]] は [[yii\base\Model]] から拡張していますので、属性、検証規則、データのシリアル化など、[モデル](structure-models.md) が持つ *全ての* 機能を継承しています。
[[yii\db\ActiveRecord]] は [[yii\base\Model]] から拡張していますので、属性、検証規則、データのシリアル化など、
[モデル](structure-models.md) が持つ *全ての* 機能を継承しています。
## データベースに接続する <span id="db-connection"></span>
デフォルトでは、アクティブ・レコードは、`db` [アプリケーション・コンポーネント](structure-application-components.md) を [[yii\db\Connection|DB 接続]] として使用して、データベースのデータにアクセスしたり操作したりします。
[データベース・アクセス・オブジェクト](db-dao.md) で説明したように、次のようにして、アプリケーションの構成情報ファイルの中で `db` コンポーネントを構成することが出来ます。
デフォルトでは、アクティブ・レコードは、`db` [アプリケーション・コンポーネント](structure-application-components.md) を
[[yii\db\Connection|DB 接続]] として使用して、データベースのデータにアクセスしたり操作したりします。
[データベース・アクセス・オブジェクト](db-dao.md) で説明したように、次のようにして、アプリケーションの構成情報ファイルの中で
`db` コンポーネントを構成することが出来ます。
```php
return [
@ -110,7 +116,8 @@ return [
];
```
`db` コンポーネントとは異なるデータベース接続を使いたい場合は、[[yii\db\ActiveRecord::getDb()|getDb()]] メソッドをオーバーライドしなければなりません。
`db` コンポーネントとは異なるデータベース接続を使いたい場合は、[[yii\db\ActiveRecord::getDb()|getDb()]]
メソッドをオーバーライドしなければなりません。
```php
class Customer extends ActiveRecord
@ -125,6 +132,7 @@ class Customer extends ActiveRecord
}
```
## データをクエリする <span id="querying-data"></span>
アクティブ・レコード・クラスを宣言した後、それを使って対応するデータベース・テーブルからデータをクエリすることが出来ます。
@ -135,7 +143,8 @@ class Customer extends ActiveRecord
3. [クエリ・メソッド](db-query-builder.md#query-methods) を呼んで、アクティブ・レコードのインスタンスの形でデータを取得する。
ご覧のように、このプロセスは [クエリ・ビルダ](db-query-builder.md) による手続きと非常によく似ています。
唯一の違いは、`new` 演算子を使ってクエリ・オブジェクトを生成する代りに、[[yii\db\ActiveQuery]] クラスであるクエリ・オブジェクトを返す [[yii\db\ActiveRecord::find()]] を呼ぶ、という点です。
唯一の違いは、`new` 演算子を使ってクエリ・オブジェクトを生成する代りに、[[yii\db\ActiveQuery]] クラスであるクエリ・オブジェクトを返す
[[yii\db\ActiveRecord::find()]] を呼ぶ、という点です。
以下の例は、アクティブ・クエリを使ってデータをクエリする方法を示すものです。
@ -169,9 +178,11 @@ $customers = Customer::find()
上記において、`$customer` は `Customer` オブジェクトであり、`$customers` は `Customer` オブジェクトの配列です。
全てこれらには `customer` テーブルから取得されたデータが投入されます。
> Info: [[yii\db\ActiveQuery]] は [[yii\db\Query]] から拡張しているため、[クエリ・ビルダ](db-query-builder.md) のセクションで説明されたクエリ構築メソッドとクエリ・メソッドの *全て* を使うことが出来ます。
> Info: [[yii\db\ActiveQuery]] は [[yii\db\Query]] から拡張しているため、[クエリ・ビルダ](db-query-builder.md)
のセクションで説明されたクエリ構築メソッドとクエリ・メソッドの *全て* を使うことが出来ます。
プライマリ・キーの値や一群のカラムの値でクエリをすることはよく行われる仕事ですので、Yii はこの目的のために、二つのショートカット・メソッドを提供しています。
プライマリ・キーの値や一群のカラムの値でクエリをすることはよく行われる仕事ですので、Yii はこの目的のために、
二つのショートカット・メソッドを提供しています。
- [[yii\db\ActiveRecord::findOne()]]: クエリ結果の最初の行を一つのアクティブ・レコード・インスタンスに投入して返す。
- [[yii\db\ActiveRecord::findAll()]]: *全ての* クエリ結果をアクティブ・レコード・インスタンスの配列に投入して返す。
@ -220,12 +231,10 @@ $customers = Customer::findAll([
> // ...
> }
>
> // 検索するカラムを明示的に指定する場合。
> // ここでは、どんなスカラ値または配列を渡しても、単一のレコードを発見する結果になります。
> // 検索するカラムを明示的に指定する場合。ここでは、どんなスカラ値または配列を渡しても、単一のレコードを発見する結果になります。
> $model = Post::findOne(['id' => Yii::$app->request->get('id')]);
>
> // 次のコードを使用してはいけません!
> // 配列形式の条件を挿入されて、任意のカラムの値による検索を実行される可能性があります!
> // 次のコードを使用してはいけません! 任意のカラムの値による検索が可能な配列形式の条件を挿入される可能性があります!
> $model = Post::findOne(Yii::$app->request->get('id'));
> ```
@ -242,15 +251,16 @@ $customers = Customer::findAll([
$sql = 'SELECT * FROM customer WHERE status=:status';
$customers = Customer::findBySql($sql, [':status' => Customer::STATUS_INACTIVE])->all();
```
[[yii\db\ActiveRecord::findBySql()|findBySql()]] を呼んだ後は、追加でクエリ構築メソッドを呼び出してはいけません。呼んでも無視されます。
[[yii\db\ActiveRecord::findBySql()|findBySql()]] を呼んだ後は、追加でクエリ構築メソッドを呼び出してはいけません。
呼んでも無視されます。
## データにアクセスする <span id="accessing-data"></span>
既に述べたように、データベースから取得されたデータはアクティブ・レコードのインスタンスに投入されます。
そして、クエリ結果の各行がアクティブ・レコードの一つのインスタンスに対応します。
アクティブ・レコード・インスタンスの属性にアクセスすることによって、カラムの値にアクセスすることが出来ます。
例えば、
アクティブ・レコード・インスタンスの属性にアクセスすることによって、カラムの値にアクセスすることが出来ます。例えば、
```php
// "id" と "email" は "customer" テーブルのカラム名
@ -263,15 +273,19 @@ $email = $customer->email;
Yii は、関連付けられたテーブルの全てのカラムに対して、アクティブ・レコードの属性を自動的に定義します。
これらの属性は、すべて、再宣言してはいけません。
アクティブ・レコードの属性はテーブルのカラムに従って命名されるため、テーブルのカラム名がアンダースコアで単語を分ける方法で命名されている場合は、`$customer->first_name` のような属性名を使って PHP コードを書くことになります。
アクティブ・レコードの属性はテーブルのカラムに従って命名されるため、
テーブルのカラム名がアンダースコアで単語を分ける方法で命名されている場合は、
`$customer->first_name` のような属性名を使って PHP コードを書くことになります。
コード・スタイルの一貫性が気になるのであれば、テーブルのカラム名を (例えば camelCase を使う名前に) 変更しなければなりません。
### データ変換 <span id="data-transformation"></span>
入力または表示されるデータの形式が、データベースにデータを保存するときに使われるものと異なる場合がよくあります。
例えば、データベースでは顧客の誕生日を UNIX タイムスタンプで保存している (まあ、あまり良い設計ではありませんが) けれども、ほとんどの場合において誕生日を `'YYYY/MM/DD'` という形式の文字列として操作したい、というような場合です。
この目的を達するために、次のように、`Customer` アクティブ・レコード・クラスにおいて *データ変換* メソッドを定義することが出来ます。
例えば、データベースでは顧客の誕生日を UNIX タイムスタンプで保存している (まあ、あまり良い設計ではありませんが)
けれども、ほとんどの場合において誕生日を `'YYYY/MM/DD'` という形式の文字列として操作したい、というような場合です。
この目的を達するために、次のように、`Customer` アクティブ・レコード・クラスにおいて *データ変換*
メソッドを定義することが出来ます。
```php
class Customer extends ActiveRecord
@ -290,7 +304,8 @@ class Customer extends ActiveRecord
}
```
このようにすれば、PHP コードにおいて、`$customer->birthday` にアクセスする代りに、`$customer->birthdayText` にアクセスすれば、顧客の誕生日を `'YYYY/MM/DD'` の形式で入力および表示することが出来ます。
このようにすれば、PHP コードにおいて、`$customer->birthday` にアクセスする代りに、`$customer->birthdayText` にアクセスすれば、
顧客の誕生日を `'YYYY/MM/DD'` の形式で入力および表示することが出来ます。
> Tip: 上記は、一般にデータの変換を達成するための簡単な方法を示すためのものです。
> 日付の値については、Yii は、[DateValidator](tutorial-core-validators.md#date) と DatePicker ウィジェットを使用するという、より良い方法を提供しています。
@ -299,7 +314,8 @@ class Customer extends ActiveRecord
### データを配列に取得する <span id="data-in-arrays"></span>
データをアクティブ・レコード・オブジェクトの形で取得するのは便利であり柔軟ですが、大きなメモリ使用量を要するために、大量のデータを取得しなければならない場合は、必ずしも望ましい方法ではありません。
データをアクティブ・レコード・オブジェクトの形で取得するのは便利であり柔軟ですが、大きなメモリ使用量を要するために、
大量のデータを取得しなければならない場合は、必ずしも望ましい方法ではありません。
そういう場合は、クエリ・メソッドを実行する前に [[yii\db\ActiveQuery::asArray()|asArray()]] を呼ぶことによって、PHP 配列を使ってデータを取得することが出来ます。
```php
@ -310,8 +326,8 @@ $customers = Customer::find()
->all();
```
> Note: このメソッドはメモリを節約してパフォーマンスを向上させますが、低レベルの DB 抽象レイヤに近いものであり、あなたはアクティブ・レコードの機能のほとんどを失うことになります。
非常に重要な違いが、カラムの値のデータ型に現れます。
> Note: このメソッドはメモリを節約してパフォーマンスを向上させますが、低レベルの DB 抽象レイヤに近いものであり、
あなたはアクティブ・レコードの機能のほとんどを失うことになります。非常に重要な違いが、カラムの値のデータ型に現れます。
アクティブ・レコード・インスタンスとしてデータを返す場合、カラムの値は実際のカラムの型に従って自動的に型キャストされます。
一方、配列としてデータを返す場合は、実際のカラムの型に関係なく、カラムの値は文字列になります。
なぜなら、何も処理をしない場合の PDO の結果は文字列だからです。
@ -319,19 +335,20 @@ $customers = Customer::find()
### データをバッチ・モードで取得する <span id="data-in-batches"></span>
[クエリ・ビルダ](db-query-builder.md) において、大量のデータをデータベースから検索する場合に、メモリ使用量を最小化するために *バッチ・クエリ* を使うことが出来るということを説明しました。
おなじテクニックをアクティブ・レコードでも使うことが出来ます。
例えば、
[クエリ・ビルダ](db-query-builder.md) において、大量のデータをデータベースから検索する場合に、メモリ使用量を最小化するために
*バッチ・クエリ* を使うことが出来るということを説明しました。おなじテクニックをアクティブ・レコードでも使うことが出来ます。例えば、
```php
// 一度に 10 人の顧客を読み出す
foreach (Customer::find()->batch(10) as $customers) {
// $customers は 10 以下の Customer オブジェクトの配列
}
// 一度に 10 人の顧客を読み出して、一人ずつ反復する
foreach (Customer::find()->each(10) as $customer) {
// $customer は Customer オブジェクト
}
// イーガー・ローディングをするバッチ・クエリ
foreach (Customer::find()->with('orders')->each() as $customer) {
// $customer は 'orders' リレーションを投入された Customer オブジェクト
@ -349,6 +366,7 @@ foreach (Customer::find()->with('orders')->each() as $customer) {
例えば、
```php
// 新しいデータ行を挿入する
$customer = new Customer();
$customer->name = 'James';
@ -361,11 +379,13 @@ $customer->email = 'james@newexample.com';
$customer->save();
```
[[yii\db\ActiveRecord::save()|save()]] メソッドは、アクティブ・レコード・インスタンスの状態に従って、データ行を挿入するか、または、更新することが出来ます。
インスタンスが `new` 演算子によって新しく作成されたものである場合は、[[yii\db\ActiveRecord::save()|save()]] を呼び出すと、新しい行が挿入されます。
インスタンスがクエリ・メソッドの結果である場合は、[[yii\db\ActiveRecord::save()|save()]] を呼び出すと、そのインスタンスと関連付けられた行が更新されます。
[[yii\db\ActiveRecord::save()|save()]] メソッドは、アクティブ・レコード・インスタンスの状態に従って、データ行を挿入するか、
または、更新することが出来ます。インスタンスが `new` 演算子によって新しく作成されたものである場合は、
[[yii\db\ActiveRecord::save()|save()]] を呼び出すと、新しい行が挿入されます。インスタンスがクエリ・メソッドの結果である場合は、
[[yii\db\ActiveRecord::save()|save()]] を呼び出すと、そのインスタンスと関連付けられた行が更新されます。
アクティブ・レコード・インスタンスの二つの状態は、その [[yii\db\ActiveRecord::isNewRecord|isNewRecord]] プロパティの値をチェックすることによって区別することが出来ます。
アクティブ・レコード・インスタンスの二つの状態は、その [[yii\db\ActiveRecord::isNewRecord|isNewRecord]]
プロパティの値をチェックすることによって区別することが出来ます。
下記のように、このプロパティは [[yii\db\ActiveRecord::save()|save()]] によっても内部的に使用されています。
```php
@ -379,19 +399,22 @@ public function save($runValidation = true, $attributeNames = null)
}
```
> Tip: [[yii\db\ActiveRecord::insert()|insert()]] または [[yii\db\ActiveRecord::update()|update()]] を直接に呼んで、行を挿入または更新することも出来ます。
> Tip: [[yii\db\ActiveRecord::insert()|insert()]] または [[yii\db\ActiveRecord::update()|update()]] を直接に呼んで、
行を挿入または更新することも出来ます。
### データの検証 <span id="data-validation"></span>
[[yii\db\ActiveRecord]] は [[yii\base\Model]] を拡張したものですので、同じ [データ検証](input-validation.md) 機能を共有しています。
[[yii\db\ActiveRecord::rules()|rules()]] メソッドをオーバーライドすることによって検証規則を宣言し、[[yii\db\ActiveRecord::validate()|validate()]] メソッドを呼ぶことによってテータの検証を実行することが出来ます。
[[yii\db\ActiveRecord::rules()|rules()]] メソッドをオーバーライドすることによって検証規則を宣言し、
[[yii\db\ActiveRecord::validate()|validate()]] メソッドを呼ぶことによってテータの検証を実行することが出来ます。
[[yii\db\ActiveRecord::save()|save()]] を呼ぶと、デフォルトでは [[yii\db\ActiveRecord::validate()|validate()]] が自動的に呼ばれます。
検証が通った時だけ、実際にデータが保存されます。
検証が通らなかった時は単に `false` が返され、[[yii\db\ActiveRecord::errors|errors]] プロパティをチェックして検証エラー・メッセージを取得することが出来ます。
> Tip: データが検証を必要としないことが確実である場合 (例えば、データが信頼できるソースに由来するものである場合) は、検証をスキップするために `save(false)` を呼ぶことが出来ます。
> Tip: データが検証を必要としないことが確実である場合 (例えば、データが信頼できるソースに由来するものである場合) は、
検証をスキップするために `save(false)` を呼ぶことが出来ます。
### 一括代入 <span id="massive-assignment"></span>
@ -415,8 +438,7 @@ $customer->save();
### カウンタを更新する <span id="updating-counters"></span>
データベース・テーブルのあるカラムの値を増加・減少させるのは、よくある仕事です。
私たちはそのようなカラムをカウンタ・カラムと呼んでいます。
データベース・テーブルのあるカラムの値を増加・減少させるのは、よくある仕事です。私たちはそのようなカラムをカウンタ・カラムと呼んでいます。
[[yii\db\ActiveRecord::updateCounters()|updateCounters()]] を使って一つまたは複数のカウンタ・カラムを更新することが出来ます。
例えば、
@ -435,19 +457,22 @@ $post->updateCounters(['view_count' => 1]);
[[yii\db\ActiveRecord::save()|save()]] を呼んでアクティブ・レコード・インスタンスを保存すると、*ダーティな属性* だけが保存されます。
属性は、DB からロードされた後、または、最後に保存された後にその値が変更されると、*ダーティ* であると見なされます。
ただし、データ検証は、アクティブ・レコード・インスタンスがダーティな属性を持っているかどうかに関係なく実施されることに注意してください。
ただし、データ検証は、アクティブ・レコード・インスタンスがダーティな属性を持っているかどうかに関係なく実施される
ことに注意してください。
アクティブ・レコードはダーティな属性のリストを自動的に保守します。
そうするために、一つ前のバージョンの属性値を保持して、最新のバージョンと比較します。
[[yii\db\ActiveRecord::getDirtyAttributes()]] を呼ぶと、現在ダーティである属性を取得することが出来ます。
また、[[yii\db\ActiveRecord::markAttributeDirty()]] を呼んで、ある属性をダーティであると明示的にマークすることも出来ます。
最新の修正を受ける前の属性値を知りたい場合は、[[yii\db\ActiveRecord::getOldAttributes()|getOldAttributes()]] または [[yii\db\ActiveRecord::getOldAttribute()|getOldAttribute()]] を呼ぶことが出来ます。
最新の修正を受ける前の属性値を知りたい場合は、[[yii\db\ActiveRecord::getOldAttributes()|getOldAttributes()]]
または [[yii\db\ActiveRecord::getOldAttribute()|getOldAttribute()]] を呼ぶことが出来ます。
> Note: 新旧の値は `===` 演算子を使って比較されるため、同じ値を持っていても型が違うとダーティであると見なされます。
> このことは、モデルが HTML フォームからユーザの入力を受け取るときにしばしば生じます。
> HTML フォームでは全ての値が文字列として表現されるからです。
> 入力値が正しい型、例えば整数値となることを保証するために、`['attributeName', 'filter', 'filter' => 'intval']` のように [検証フィルタ](input-validation.md#data-filtering) を適用することが出来ます。
> 入力値が正しい型、例えば整数値となることを保証するために、`['attributeName', 'filter', 'filter' => 'intval']` のように
> [検証フィルタ](input-validation.md#data-filtering) を適用することが出来ます。
> このフィルタは、[intval()](http://php.net/manual/ja/function.intval.php), [floatval()](http://php.net/manual/ja/function.floatval.php),
> [boolval](http://php.net/manual/ja/function.boolval.php) など、PHP の全てのタイプキャスト関数で動作します。
@ -455,7 +480,8 @@ $post->updateCounters(['view_count' => 1]);
あなたのテーブルのカラムの中には、データベースでデフォルト値が定義されているものがあるかも知れません。
そして、場合によっては、アクティブ・レコード・インスタンスのウェブ・フォームに、そういうデフォルト値をあらかじめ投入したいことがあるでしょう。
同じデフォルト値を繰り返して書くことを避けるために、[[yii\db\ActiveRecord::loadDefaultValues()|loadDefaultValues()]] を呼んで、DB で定義されたデフォルト値を対応するアクティブ・レコードの属性に投入することが出来ます。
同じデフォルト値を繰り返して書くことを避けるために、[[yii\db\ActiveRecord::loadDefaultValues()|loadDefaultValues()]]
を呼んで、DB で定義されたデフォルト値を対応するアクティブ・レコードの属性に投入することが出来ます。
```php
$customer = new Customer();
@ -468,8 +494,7 @@ $customer->loadDefaultValues();
[[yii\db\ActiveRecord]] は、クエリの結果を投入されるときに、[データベース・テーブル・スキーマ](db-dao.md#database-schema)
からの情報を使って、自動的な型キャストを実行します。これによって、整数として宣言されているテーブルカラムから取得されるデータを
アクティブ・レコードのインスタンスでも PHP の integer として投入し、
真偽値として宣言されているデータを boolean として投入することが出来るようになっています。
アクティブ・レコードのインスタンスでも PHP の integer として投入し、真偽値として宣言されているデータを boolean として投入することが出来るようになっています。
しかしながら、型キャストのメカニズムには、いくつかの制約があります。
* 浮動小数点数値は変換されず、文字列として表されます。そうしないと精度が失われるおそれがあるからです。
@ -479,11 +504,12 @@ $customer->loadDefaultValues();
属性の型キャストは、アクティブ・レコードのインスタンスにクエリの結果から値を投入するときだけしか実行されないことに注意してください。
HTTP リクエストから値をロードしたり、プロパティにアクセスして直接に値を設定したりするときには、自動的な変換は行われません。
また、アクティブ・レコードのデータ保存のための SQL 文を準備する際にもテーブル・スキーマが使用されて、値が正しい型でクエリにバインドされることを保証します。
また、アクティブ・レコードのデータ保存のための SQL 文を準備する際にもテーブル・スキーマが使用されて、
値が正しい型でクエリにバインドされることを保証します。
しかし、アクティブ・レコードのインスタンスの属性値は保存の過程において変換されることはありません。
> Tip: アクティブ・レコードの検証や保存の際の属性型キャストを楽にするために
[[yii\behaviors\AttributeTypecastBehavior]] を使うことが出来ます。
[[yii\behaviors\AttributeTypecastBehavior]] を使うことが出来ます。
2.0.14 以降、Yii のアクティブ・レコードは、JSON や多次元配列のような複雑な型をサポートしています。
@ -498,8 +524,7 @@ HTTP リクエストから値をロードしたり、プロパティにアクセ
#### PostgreSQL における配列
データが取得された後、配列カラムの値は PgSQL 記法から自動的に [[yii\db\ArrayExpression|ArrayExpression]] オブジェクトにデコードされます。
このオブジェクトは PHP の `ArrayAccess` インタフェイスを実装しているため、これを配列として使うこと事が出来ます。
また、`->getValue()` を呼んで配列そのものを取得することも出来ます。
このオブジェクトは PHP の `ArrayAccess` インタフェイスを実装しているため、これを配列として使うこと事が出来ます。また、`->getValue()` を呼んで配列そのものを取得することも出来ます。
アクティブ・レコードは、属性値を配列カラムに保存するために [[yii\db\ArrayExpression|ArrayExpression]]
オブジェクトを生成します。このオブジェクトが [クエリ・ビルダ](db-query-builder.md) のレベルで配列を表す PgSQL 文字列にエンコードされます。
@ -516,14 +541,16 @@ $query->andWhere(['=', 'json', new ArrayExpression(['foo' => 'bar'])
### 複数の行を更新する <span id="updating-multiple-rows"></span>
上述のメソッドは、すべて、個別のアクティブ・レコード・インスタンスに対して作用し、個別のテーブル行を挿入したり更新したりするものです。
複数の行を同時に更新するためには、代りに、スタティックなメソッドである [[yii\db\ActiveRecord::updateAll()|updateAll()]] を呼ばなければなりません。
複数の行を同時に更新するためには、代りに、スタティックなメソッドである [[yii\db\ActiveRecord::updateAll()|updateAll()]]
を呼ばなければなりません。
```php
// UPDATE `customer` SET `status` = 1 WHERE `email` LIKE `%@example.com`
Customer::updateAll(['status' => Customer::STATUS_ACTIVE], ['like', 'email', '@example.com']);
```
同様に、[[yii\db\ActiveRecord::updateAllCounters()|updateAllCounters()]] を呼んで、複数の行のカウンタカラムを同時に更新することが出来ます。
同様に、[[yii\db\ActiveRecord::updateAllCounters()|updateAllCounters()]] を呼んで、
複数の行のカウンタカラムを同時に更新することが出来ます。
```php
// UPDATE `customer` SET `age` = `age` + 1
@ -533,7 +560,8 @@ Customer::updateAllCounters(['age' => 1]);
## データを削除する <span id="deleting-data"></span>
一行のデータを削除するためには、最初にその行に対応するアクティブ・レコード・インスタンスを取得して、次に [[yii\db\ActiveRecord::delete()]] メソッドを呼びます。
一行のデータを削除するためには、最初にその行に対応するアクティブ・レコード・インスタンスを取得して、
次に [[yii\db\ActiveRecord::delete()]] メソッドを呼びます。
```php
$customer = Customer::findOne(123);
@ -558,7 +586,8 @@ Customer::deleteAll(['status' => Customer::STATUS_INACTIVE]);
また、ライフサイクルの中でトリガされる特定のアクティブ・レコード・イベントに反応して、あなたのカスタム・コードを挿入することも出来ます。
これらのイベントが特に役に立つのは、アクティブ・レコードのライフサイクルをカスタマイズする必要のあるアクティブ・レコード・[ビヘイビア](concept-behaviors.md) を開発する際です。
次に、さまざまなアクティブ・レコードのライフサイクルと、そのライフサイクルに含まれるメソッドやイベントを要約します。
次に、さまざまなアクティブ・レコードのライフサイクルと、
そのライフサイクルに含まれるメソッドやイベントを要約します。
### 新しいインスタンスのライフサイクル <span id="new-instance-life-cycle"></span>
@ -571,7 +600,8 @@ Customer::deleteAll(['status' => Customer::STATUS_INACTIVE]);
### データをクエリする際のライフサイクル <span id="querying-data-life-cycle"></span>
[クエリ・メソッド](#querying-data) のどれか一つによってデータをクエリする場合は、新しくデータを投入されるアクティブ・レコードは次のライフサイクルを経ます。
[クエリ・メソッド](#querying-data) のどれか一つによってデータをクエリする場合は、
新しくデータを投入されるアクティブ・レコードは次のライフサイクルを経ます。
1. クラスのコンストラクタ。
2. [[yii\db\ActiveRecord::init()|init()]]: [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] イベントをトリガ。
@ -580,26 +610,36 @@ Customer::deleteAll(['status' => Customer::STATUS_INACTIVE]);
### データを保存する際のライフサイクル <span id="saving-data-life-cycle"></span>
[[yii\db\ActiveRecord::save()|save()]] を呼んでアクティブ・レコード・インスタンスを挿入または更新する場合は、次のライフサイクルを経ます。
[[yii\db\ActiveRecord::save()|save()]] を呼んでアクティブ・レコード・インスタンスを挿入または更新する場合は、
次のライフサイクルを経ます。
1. [[yii\db\ActiveRecord::beforeValidate()|beforeValidate()]]: [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] イベントをトリガ。
このメソッドが `false` を返すか、[[yii\base\ModelEvent::isValid]] が `false` であった場合、残りのステップはスキップされる。
このメソッドが `false` を返すか、[[yii\base\ModelEvent::isValid]] が `false` であった場合、
残りのステップはスキップされる。
2. データ検証を実行。データ検証が失敗した場合、3 より後のステップはスキップされる。
3. [[yii\db\ActiveRecord::afterValidate()|afterValidate()]]: [[yii\db\ActiveRecord::EVENT_AFTER_VALIDATE|EVENT_AFTER_VALIDATE]] イベントをトリガ。
4. [[yii\db\ActiveRecord::beforeSave()|beforeSave()]]: [[yii\db\ActiveRecord::EVENT_BEFORE_INSERT|EVENT_BEFORE_INSERT]] または [[yii\db\ActiveRecord::EVENT_BEFORE_UPDATE|EVENT_BEFORE_UPDATE]] イベントをトリガ。
このメソッドが `false` を返すか、[[yii\base\ModelEvent::isValid]] が `false` であった場合、残りのステップはスキップされる。
3. [[yii\db\ActiveRecord::afterValidate()|afterValidate()]]: [[yii\db\ActiveRecord::EVENT_AFTER_VALIDATE|EVENT_AFTER_VALIDATE]]
イベントをトリガ。
4. [[yii\db\ActiveRecord::beforeSave()|beforeSave()]]: [[yii\db\ActiveRecord::EVENT_BEFORE_INSERT|EVENT_BEFORE_INSERT]]
または [[yii\db\ActiveRecord::EVENT_BEFORE_UPDATE|EVENT_BEFORE_UPDATE]] イベントをトリガ。
このメソッドが `false` を返すか、[[yii\base\ModelEvent::isValid]] が `false` であった場合、
残りのステップはスキップされる。
5. 実際のデータの挿入または更新を実行。
6. [[yii\db\ActiveRecord::afterSave()|afterSave()]]: [[yii\db\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] または [[yii\db\ActiveRecord::EVENT_AFTER_UPDATE|EVENT_AFTER_UPDATE]] イベントをトリガ。
6. [[yii\db\ActiveRecord::afterSave()|afterSave()]]: [[yii\db\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]]
または [[yii\db\ActiveRecord::EVENT_AFTER_UPDATE|EVENT_AFTER_UPDATE]]
イベントをトリガ。
### データを削除する際のライフサイクル <span id="deleting-data-life-cycle"></span>
[[yii\db\ActiveRecord::delete()|delete()]] を呼んでアクティブ・レコード・インスタンスを削除する際は、次のライフサイクルを経ます。
[[yii\db\ActiveRecord::delete()|delete()]] を呼んでアクティブ・レコード・インスタンスを削除する際は、
次のライフサイクルを経ます。
1. [[yii\db\ActiveRecord::beforeDelete()|beforeDelete()]]: [[yii\db\ActiveRecord::EVENT_BEFORE_DELETE|EVENT_BEFORE_DELETE]] イベントをトリガ。
このメソッドが `false` を返すか、[[yii\base\ModelEvent::isValid]] が `false` であった場合は、残りのステップはスキップされる。
1. [[yii\db\ActiveRecord::beforeDelete()|beforeDelete()]]: [[yii\db\ActiveRecord::EVENT_BEFORE_DELETE|EVENT_BEFORE_DELETE]]
イベントをトリガ。このメソッドが `false` を返すか、[[yii\base\ModelEvent::isValid]] が `false` であった場合は、
残りのステップはスキップされる。
2. 実際のデータの削除を実行。
3. [[yii\db\ActiveRecord::afterDelete()|afterDelete()]]: [[yii\db\ActiveRecord::EVENT_AFTER_DELETE|EVENT_AFTER_DELETE]] イベントをトリガ。
3. [[yii\db\ActiveRecord::afterDelete()|afterDelete()]]: [[yii\db\ActiveRecord::EVENT_AFTER_DELETE|EVENT_AFTER_DELETE]]
イベントをトリガ。
> Note: 次のメソッドを呼んだ場合は、いずれの場合も、上記のライフサイクルのどれかを開始させることはありません。
@ -648,12 +688,12 @@ try {
}
```
> Note: 上記のコードでは、PHP 5.x と PHP 7.x との互換性のために、二つの
> catch ブロックを持っています。`\Exception` は PHP 7.0 以降では、
> [`\Throwable` インタフェイス](http://php.net/manual/ja/class.throwable.php) を実装しています。
> Note: 上記のコードでは、PHP 5.x と PHP 7.x との互換性のために、二つの catch ブロックを持っています。
> `\Exception` は PHP 7.0 以降では、[`\Throwable` インタフェイス](http://php.net/manual/ja/class.throwable.php) を実装しています。
> 従って、あなたのアプリケーションが PHP 7.0 以上しか使わない場合は、`\Exception` の部分を省略することが出来ます。
第二の方法は、トランザクションのサポートが必要な DB 操作を [[yii\db\ActiveRecord::transactions()]] メソッドに列挙するという方法です。
第二の方法は、トランザクションのサポートが必要な DB 操作を [[yii\db\ActiveRecord::transactions()]]
メソッドに列挙するという方法です。
```php
class Post extends \yii\db\ActiveRecord
@ -670,7 +710,8 @@ class Post extends \yii\db\ActiveRecord
}
```
[[yii\db\ActiveRecord::transactions()]] メソッドが返す配列では、キーは [シナリオ](structure-models.md#scenarios) の名前であり、値はトランザクションで囲まれるべき操作でなくてはなりません。
[[yii\db\ActiveRecord::transactions()]] メソッドが返す配列では、キーは [シナリオ](structure-models.md#scenarios) の名前であり、
値はトランザクションで囲まれるべき操作でなくてはなりません。
いろいろな DB 操作を参照するのには、次の定数を使わなければなりません。
* [[yii\db\ActiveRecord::OP_INSERT|OP_INSERT]]: [[yii\db\ActiveRecord::insert()|insert()]] によって実行される挿入の操作。
@ -683,16 +724,19 @@ class Post extends \yii\db\ActiveRecord
このメソッドを使って生成されたトランザクションは、[[yii\db\ActiveRecord::beforeSave()|beforeSave()]] を呼ぶ前に開始され、
[[yii\db\ActiveRecord::afterSave()|afterSave()]] を実行した後にコミットされます。
## 楽観的ロック <span id="optimistic-locks"></span>
楽観的ロックは、一つのデータ行が複数のユーザによって更新されるときに発生しうる衝突を回避するための方法です。
例えば、ユーザ A と ユーザ B が 同時に同じ wiki 記事を編集しており、ユーザ A が自分の編集結果を保存した後に、ユーザ B も自分の編集結果を保存しようとして「保存」ボタンをクリックする場合を考えてください。
ユーザ B は、実際には古くなったバージョンの記事に対する操作をしようとしていますので、彼が記事を保存するのを防止し、彼に何らかのヒントメッセージを表示する方法があることが望まれます。
例えば、ユーザ A と ユーザ B が 同時に同じ wiki 記事を編集しており、ユーザ A が自分の編集結果を保存した後に、
ユーザ B も自分の編集結果を保存しようとして「保存」ボタンをクリックする場合を考えてください。
ユーザ B は、実際には古くなったバージョンの記事に対する操作をしようとしていますので、彼が記事を保存するのを防止し、
彼に何らかのヒント・メッセージを表示する方法があることが望まれます。
楽観的ロックは、あるカラムを使って各行のバージョン番号を記録するという方法によって、上記の問題を解決します。
古くなったバージョン番号とともに行を保存しようとすると、[[yii\db\StaleObjectException]] 例外が投げられて、行が保存されるのが防止されます。
楽観的ロックは、 [[yii\db\ActiveRecord::update()]] または [[yii\db\ActiveRecord::delete()]] メソッドを使って既存の行を更新または削除しようとする場合にだけサポートされます。
古くなったバージョン番号とともに行を保存しようとすると、[[yii\db\StaleObjectException]] 例外が投げられて、
行が保存されるのが防止されます。
楽観的ロックは、 [[yii\db\ActiveRecord::update()]] または [[yii\db\ActiveRecord::delete()]]
メソッドを使って既存の行を更新または削除しようとする場合にだけサポートされます。
楽観的ロックを使用するためには、次のようにします。
@ -701,9 +745,11 @@ class Post extends \yii\db\ActiveRecord
2. [[yii\db\ActiveRecord::optimisticLock()]] メソッドをオーバーライドして、このカラムの名前を返すようにします。
3. ユーザ入力を収集するウェブフォームに、更新されるレコードの現在のバージョン番号を保持する隠しフィールドを追加します。
バージョン属性が入力の検証規則を持っており、検証が成功することを確かめてください。
4. アクティブ・レコードを使って行の更新を行うコントローラ・アクションにおいて、[[\yii\db\StaleObjectException]] 例外を捕捉して、衝突を解決するために必要なビジネス・ロジック (例えば、変更をマージしたり、データの陳腐化を知らせたり) を実装します。
4. アクティブ・レコードを使って行の更新を行うコントローラ・アクションにおいて、[[\yii\db\StaleObjectException]] 例外を捕捉して、
衝突を解決するために必要なビジネス・ロジック (例えば、変更をマージしたり、データの陳腐化を知らせたり) を実装します。
例えば、バージョン番号のカラムが `version` と名付けられているとすると、次のようなコードによって楽観的ロックを実装することが出来ます。
例えば、バージョン番号のカラムが `version` と名付けられているとすると、
次のようなコードによって楽観的ロックを実装することが出来ます。
```php
// ------ ビューのコード -------
@ -739,7 +785,8 @@ public function actionUpdate($id)
## リレーショナル・データを扱う <span id="relational-data"></span>
個々のデータベース・テーブルを扱うだけでなく、アクティブ・レコードは関連したテーブルのデータも一緒に読み出して、主たるデータを通して簡単にアクセス出来るようにすることが出来ます。
個々のデータベース・テーブルを扱うだけでなく、アクティブ・レコードは関連したテーブルのデータも一緒に読み出して、
主たるデータを通して簡単にアクセス出来るようにすることが出来ます。
例えば、一人の顧客は一つまたは複数の注文を発することがあり得ますので、顧客のデータは注文のデータと関連を持っていることになります。
このリレーションが適切に宣言されていれば、`$customer->orders` という式を使って顧客の注文情報にアクセスすることが出来ます。
`$customer->orders` は、顧客の注文情報を `Order` アクティブ・レコード・インスタンスの配列として返してくれます。
@ -772,29 +819,35 @@ class Order extends ActiveRecord
}
```
上記のコードでは、`Customer` クラスのために `orders` リレーションを宣言し、`Order` クラスのために `customer` リレーションを宣言しています。
上記のコードでは、`Customer` クラスのために `orders` リレーションを宣言し、`Order` クラスのために `customer`
リレーションを宣言しています。
各リレーション・メソッドは `getXyz` という名前にしなければなりません。
ここで `xyz` (最初の文字は小文字です) が *リレーション名* と呼ばれます。
各リレーション・メソッドは `getXyz` という名前にしなければなりません。ここで `xyz` (最初の文字は小文字です) が *リレーション名* と呼ばれます。
リレーション名は *大文字と小文字を区別する* ことに注意してください。
リレーションを宣言する際には、次の情報を指定しなければなりません。
- リレーションの多重性: [[yii\db\ActiveRecord::hasMany()|hasMany()]] または [[yii\db\ActiveRecord::hasOne()|hasOne()]] のどちらかを呼ぶことによって指定されます。
- リレーションの多重性: [[yii\db\ActiveRecord::hasMany()|hasMany()]] または [[yii\db\ActiveRecord::hasOne()|hasOne()]]
のどちらかを呼ぶことによって指定されます。
上記の例では、リレーションの宣言において、顧客は複数の注文を持ち得るが、一方、注文は一人の顧客しか持たない、ということが容易に読み取れます。
- 関連するアクティブ・レコード・クラスの名前: [[yii\db\ActiveRecord::hasMany()|hasMany()]] または [[yii\db\ActiveRecord::hasOne()|hasOne()]] の最初のパラメータとして指定されます。
- 関連するアクティブ・レコード・クラスの名前: [[yii\db\ActiveRecord::hasMany()|hasMany()]] または [[yii\db\ActiveRecord::hasOne()|hasOne()]]
の最初のパラメータとして指定されます。
クラス名を取得するのに `Xyz::className()` を呼ぶのが推奨されるプラクティスです。
そうすれば、IDE の自動補完のサポートを得ることことが出来るだけでなく、コンパイル段階でエラーを検出することが出来ます。
- 二つのデータタイプ間のリンク: 二つのデータタイプの関連付けに用いられるカラムを指定します。
配列の値は主たるデータ (リレーションを宣言しているアクティブ・レコード・クラスによって表されるデータ) のカラムであり、配列のキーは関連するデータのカラムです。
- 二つの型のデータ間のリンク: 二つの型のデータの関連付けに用いられるカラムを指定します。
配列の値は主たるデータ (リレーションを宣言しているアクティブ・レコード・クラスによって表されるデータ) のカラムであり、
配列のキーは関連するデータのカラムです。
これを記憶するための簡単な規則は、上の例で見るように、関連するアクティブ・レコードを書いた直後に、それに属するカラムを
続けて書く、ということです。ご覧のように、`customer_id` は `Order` のプロパティであり、
`id` は`Customer` のプロパティです。
### リレーショナル・データにアクセスする <span id="accessing-relational-data"></span>
リレーションを宣言した後は、リレーション名を通じてリレーショナル・データにアクセスすることが出来ます。
これは、リレーション・メソッドによって定義されるオブジェクト・[プロパティ](concept-properties.md) にアクセスするのと同様です。
このため、これを *リレーション・プロパティ* と呼びます。
例えば、
このため、これを *リレーション・プロパティ* と呼びます。例えば、
```php
// SELECT * FROM `customer` WHERE `id` = 123
@ -805,31 +858,36 @@ $customer = Customer::findOne(123);
$orders = $customer->orders;
```
> Info: `xyz` という名前のリレーションを getter メソッド `getXyz()` によって宣言すると、`xyz` を [オブジェクト・プロパティ](concept-properties.md) のようにアクセスすることが出来るようになります。
名前は大文字と小文字を区別することに注意してください。
> Info: `xyz` という名前のリレーションを getter メソッド `getXyz()` によって宣言すると、`xyz` を
[オブジェクト・プロパティ](concept-properties.md) のようにアクセスすることが出来るようになります。名前は大文字と小文字を区別することに注意してください。
リレーションが [[yii\db\ActiveRecord::hasMany()|hasMany()]] によって宣言されている場合は、このリレーション・プロパティにアクセスすると、関連付けられたアクティブ・レコード・インスタンスの配列が返されます。
リレーションが [[yii\db\ActiveRecord::hasOne()|hasOne()]] によって宣言されている場合は、このリレーション・プロパティにアクセスすると、関連付けられたアクティブ・レコード・インスタンスか、関連付けられたデータが見つからないときは `null` が返されます。
リレーションが [[yii\db\ActiveRecord::hasMany()|hasMany()]] によって宣言されている場合は、
このリレーション・プロパティにアクセスすると、関連付けられたアクティブ・レコード・インスタンスの配列が返されます。
リレーションが [[yii\db\ActiveRecord::hasOne()|hasOne()]] によって宣言されている場合は、
このリレーション・プロパティにアクセスすると、関連付けられたアクティブ・レコード・インスタンスか、関連付けられたデータが見つからないときは `null` が返されます。
リレーション・プロパティに最初にアクセスしたときは、上記の例で示されているように、SQL 文が実行されます。
その同じプロパティに再びアクセスしたときは、SQL 文を再実行することなく、以前の結果が返されます。
SQL 文の再実行を強制するためには、まず、リレーション・プロパティの割り当てを解除 (unset) しなければなりません : `unset($customer->orders)`
SQL 文の再実行を強制するためには、まず、リレーション・プロパティの割り当てを解除 (unset) しなければなりません :
`unset($customer->orders)`
> Note: リレーション・プロパティの概念は [オブジェクト・プロパティ](concept-properties.md) の機能と同一であるように見えますが、一つ、重要な相違点があります。
> 通常のオブジェクト・プロパティでは、プロパティの値はそれを定義する getter メソッドと同じ型を持ちます。
> しかし、リレーション・プロパティにアクセスすると [[yii\db\ActiveRecord]] のインスタンスまたはその配列が返されるのに対して、リレーション・メソッドは [[yii\db\ActiveQuery]] のインスタンスを返します。
> しかし、リレーション・プロパティにアクセスすると [[yii\db\ActiveRecord]] のインスタンスまたはその配列が返されるのに対して、
> リレーション・メソッドは [[yii\db\ActiveQuery]] のインスタンスを返します。
>
> ```php
> $customer->orders; // `Order` オブジェクトの配列
> $customer->getOrders(); // ActiveQuery のインスタンス
> ```
>
> このことは、次の項で説明するように、カスタマイズしたクエリを作成するのに役に立ちます。
> このことは、次のセクションで説明するように、カスタマイズしたクエリを作成するのに役に立ちます。
### 動的なリレーショナル・クエリ <span id="dynamic-relational-query"></span>
リレーション・メソッドは [[yii\db\ActiveQuery]] のインスタンスを返すため、DB クエリを実行する前に、クエリ構築メソッドを使ってこのクエリを更に修正することが出来ます。
例えば、
リレーション・メソッドは [[yii\db\ActiveQuery]] のインスタンスを返すため、DB クエリを実行する前に、
クエリ構築メソッドを使ってこのクエリを更に修正することが出来ます。例えば、
```php
$customer = Customer::findOne(123);
@ -841,7 +899,8 @@ $orders = $customer->getOrders()
->all();
```
リレーション・プロパティにアクセスする場合と違って、リレーション・メソッドによって動的なリレーショナル・クエリを実行する場合は、同じ動的なリレーショナル・クエリが以前に実行されたことがあっても、毎回、SQL 文が実行されます。
リレーション・プロパティにアクセスする場合と違って、リレーション・メソッドによって動的なリレーショナル・クエリを実行する場合は、
同じ動的なリレーショナル・クエリが以前に実行されたことがあっても、毎回、SQL 文が実行されます。
さらに進んで、もっと簡単に動的なリレーショナル・クエリを実行できるように、リレーションの宣言をパラメータ化したい場合もあるでしょう。
例えば、`bigOrders` リレーションを下記のように宣言することが出来ます。
@ -871,13 +930,15 @@ $orders = $customer->bigOrders;
### 中間テーブルによるリレーション <span id="junction-table"></span>
データベースの設計において、二つの関連するテーブル間の多重性が多対多である場合は、通常、[中間テーブル](https://en.wikipedia.org/wiki/Junction_table) が導入されます。
データベースの設計において、二つの関連するテーブル間の多重性が多対多である場合は、通常、
[中間テーブル](https://en.wikipedia.org/wiki/Junction_table) が導入されます。
例えば、`order` テーブルと `item` テーブルは、`order_item` と言う名前の中間テーブルによって関連付けることが出来ます。
このようにすれば、一つの注文を複数の商品に対応させ、また、一つの商品を複数の注文に対応させることが出来ます。
このようなリレーションを宣言するときは、[[yii\db\ActiveQuery::via()|via()]] または [[yii\db\ActiveQuery::viaTable()|viaTable()]] のどちらかを呼んで中間テーブルを指定します。
[[yii\db\ActiveQuery::via()|via()]] と [[yii\db\ActiveQuery::viaTable()|viaTable()]] の違いは、前者が既存のリレーション名の形式で中間テーブルを指定するのに対して、後者は中間テーブルを直接に指定する、という点です。
例えば、
このようなリレーションを宣言するときは、[[yii\db\ActiveQuery::via()|via()]] または [[yii\db\ActiveQuery::viaTable()|viaTable()]]
のどちらかを呼んで中間テーブルを指定します。
[[yii\db\ActiveQuery::via()|via()]] と [[yii\db\ActiveQuery::viaTable()|viaTable()]] の違いは、
前者が既存のリレーション名の形式で中間テーブルを指定するのに対して、後者は中間テーブルを直接に指定する、という点です。例えば、
```php
class Order extends ActiveRecord
@ -908,8 +969,7 @@ class Order extends ActiveRecord
}
```
中間テーブルを使って宣言されたリレーションの使い方は、通常のリレーションと同じです。
例えば、
中間テーブルを使って宣言されたリレーションの使い方は、通常のリレーションと同じです。例えば、
```php
// SELECT * FROM `order` WHERE `id` = 100
@ -927,8 +987,7 @@ $items = $order->items;
さらに、[[yii\db\ActiveQuery::via()|via()]] を使ってリレーション定義を連鎖させ、複数のテーブルを経由するリレーションを定義することも可能です。
上記の例で考えましょう。そこには `Customer`(顧客)、`Order`(注文) そして `Item`(品目) というクラスがあります。
`Customer` クラスに、発注された全ての注文によって購入された全ての品目を列挙するリレーションを追加して、
それに `getPurchasedItems()` という名前を付けることが出来ます。
リレーション定義の連鎖が次のコード・サンプルで示されています。
それに `getPurchasedItems()` という名前を付けることが出来ます。リレーション定義の連鎖が次のコード・サンプルで示されています。
```php
class Customer extends ActiveRecord
@ -960,10 +1019,10 @@ class Customer extends ActiveRecord
### レイジー・ローディングとイーガー・ローディング <span id="lazy-eager-loading"></span>
[リレーショナル・データにアクセスする](#accessing-relational-data) において、通常のオブジェクト・プロパティにアクセスするのと同じようにして、アクティブ・レコード・インスタンスのリレーション・プロパティにアクセスすることが出来ることを説明しました。
[リレーショナル・データにアクセスする](#accessing-relational-data) において、通常のオブジェクト・プロパティにアクセスするのと同じようにして、
アクティブ・レコード・インスタンスのリレーション・プロパティにアクセスすることが出来ることを説明しました。
SQL 文は、リレーション・プロパティに最初にアクセスするときにだけ実行されます。
このようなリレーショナル・データのアクセス方法を *レイジー・ローディング* と呼びます。
例えば、
このようなリレーショナル・データのアクセス方法を *レイジー・ローディング* と呼びます。例えば、
```php
// SELECT * FROM `customer` WHERE `id` = 123
@ -991,7 +1050,8 @@ foreach ($customers as $customer) {
```
上のコードのコメントから判るように、実行される SQL 文は 101 にもなります。
これは、for ループの中で、異なる `Customer` オブジェクトの `orders` リレーションにアクセスするたびに、SQL 文が一つ実行されることになるからです。
これは、for ループの中で、異なる `Customer` オブジェクトの `orders` リレーションにアクセスするたびに、
SQL 文が一つ実行されることになるからです。
このパフォーマンスの問題を解決するために、次に示すように、いわゆる *イーガー・ローディング* の手法を使うことが出来ます。
@ -1012,8 +1072,7 @@ foreach ($customers as $customer) {
[[yii\db\ActiveQuery::with()]] を呼ぶことによって、最初の 100 人の顧客の注文をたった一つの SQL 文で返すように、アクティブ・レコードに指示をしています。
結果として、実行される SQL 文の数は 101 から 2 に減ります。
イーガー・ローディングは、一つだけでなく、複数のリレーションに対しても使うことが出来ます。
さらには、*ネストされたリレーション* でさえ、イーガー・ロードすることが出来ます。
イーガー・ローディングは、一つだけでなく、複数のリレーションに対しても使うことが出来ます。さらには、*ネストされたリレーション* でさえ、イーガー・ロードすることが出来ます。
ネストされたリレーションというのは、関連するアクティブ・レコードの中で宣言されているリレーションです。
例えば、`Cutomer` が `orders` リレーションによって `Order` と関連しており、`Order` が `items` リレーションによって `Item` と関連している場合です。
`Customer` に対するクエリを実行するときに、ネストされたリレーションの記法である `orders.items` を使って、`items` をイーガー・ロードすることが出来ます。
@ -1042,7 +1101,8 @@ $items = $customers[0]->orders[0]->items;
このとき、すべての親リレーションもイーガー・ロードされます。
つまり、`a.b.c.d` を使って [[yii\db\ActiveQuery::with()|with()]] を呼ぶと、`a`、`a.b`、`a.b.c` そして `a.b.c.d` をイーガー・ロードすることになります。
> Info: 一般化して言うと、`N` 個のリレーションのうち `M` 個のリレーションが [中間テーブル](#junction-table) によって定義されている場合、この `N` 個のリレーションをイーガー・ロードしようとすると、合計で `1+M+N` 個の SQL クエリが実行されます。
> Info: 一般化して言うと、`N` 個のリレーションのうち `M` 個のリレーションが [中間テーブル](#junction-table) によって定義されている場合、
この `N` 個のリレーションをイーガー・ロードしようとすると、合計で `1+M+N` 個の SQL クエリが実行されます。
ネストされたリレーション `a.b.c.d` は 4 個のリレーションとして数えられることに注意してください。
リレーションをイーガー・ロードするときに、対応するリレーショナル・クエリを無名関数を使ってカスタマイズすることが出来ます。
@ -1062,23 +1122,25 @@ $customers = Customer::find()->with([
```
リレーションのためのリレーショナル・クエリをカスタマイズするときは、リレーション名を配列のキーとし、対応する値に無名関数を使わなければなりません。
無名関数が受け取る `$query` パラメータは、リレーションのためのリレーショナル・クエリを実行するのに使用される [[yii\db\ActiveQuery]] オブジェクトを表します。
無名関数が受け取る `$query` パラメータは、リレーションのためのリレーショナル・クエリを実行するのに使用される
[[yii\db\ActiveQuery]] オブジェクトを表します。
上のコード例では、注文の状態に関する条件を追加して、リレーショナル・クエリを修正しています。
> Note: リレーションをイーガー・ロードするときに [[yii\db\Query::select()|select()]] を呼ぶ場合は、リレーションの宣言で参照されているカラムが選択されるように注意しなければなりません。
> そうしないと、リレーションのモデルが正しくロードされないことがあります
> 例えば、
> Note: リレーションをイーガー・ロードするときに [[yii\db\Query::select()|select()]] を呼ぶ場合は、
> リレーションの宣言で参照されているカラムが選択されるように注意しなければなりません
> そうしないと、リレーションのモデルが正しくロードされないことがあります。例えば、
>
> ```php
> $orders = Order::find()->select(['id', 'amount'])->with('customer')->all();
> // この場合、$orders[0]->customer は常に `null` になります。
> // 問題を修正するためには、次のようにしなければなりません。
> // この場合、$orders[0]->customer は常に `null` になります。問題を修正するためには、次のようにしなければなりません。
> $orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all();
> ```
### リレーションを使ってテーブルを結合する <a name="joining-with-relations">
> Note: この項で説明されていることは、MySQL、PostgreSQL など、リレーショナル・データベースに対してのみ適用されます。
> Note: この項で説明されていることは、MySQL、PostgreSQL など、
リレーショナル・データベースに対してのみ適用されます。
ここまで説明してきたリレーショナル・クエリは、主たるデータを検索する際に主テーブルのカラムだけを参照するものでした。
現実には、関連するテーブルのカラムを参照しなければならない場合がよくあります。
@ -1111,8 +1173,7 @@ $customers = Customer::find()
->all();
```
どちらの方法でも、実行される SQL 文のセットは同じです。
けれども、後者の方がはるかに明快で簡潔です。
どちらの方法でも、実行される SQL 文のセットは同じです。けれども、後者の方がはるかに明快で簡潔です。
デフォルトでは、[[yii\db\ActiveQuery::joinWith()|joinWith()]] は `LEFT JOIN` を使って、関連するテーブルを主テーブルに結合します。
第三のパラメータ `$joinType` によって異なる結合タイプ (例えば `RIGHT JOIN`) を指定することが出来ます。
@ -1125,9 +1186,9 @@ $customers = Customer::find()
リレーションのデータを取得するのには `JOIN` クエリの結果は**使われません**。
その場合でも、やはり、[イーガー・ローディング](#lazy-eager-loading) のセクションで説明したように、結合されたリレーションごとに追加のクエリが実行されます。
[[yii\db\ActiveQuery::with()|with()]] と同じように、一つまたは複数のリレーションを結合したり、リレーションクエリをその場でカスタマイズしたり、ネストされたリレーションを結合したりすることが出来ます。
また、[[yii\db\ActiveQuery::with()|with()]] と [[yii\db\ActiveQuery::joinWith()|joinWith()]] を混ぜて使用することも出来ます。
例えば、
[[yii\db\ActiveQuery::with()|with()]] と同じように、一つまたは複数のリレーションを結合したり、
リレーションクエリをその場でカスタマイズしたり、ネストされたリレーションを結合したりすることが出来ます。
また、[[yii\db\ActiveQuery::with()|with()]] と [[yii\db\ActiveQuery::joinWith()|joinWith()]] を混ぜて使用することも出来ます。例えば、
```php
$customers = Customer::find()->joinWith([
@ -1156,14 +1217,14 @@ $customers = Customer::find()->joinWith([
上記のクエリは *全ての* 顧客を返し、各顧客について全てのアクティブな注文を返します。
これは、少なくとも一つのアクティブな注文を持つ顧客を全て返す、という以前の例とは異なっていることに注意してください。
> Info: [[yii\db\ActiveQuery]] が [[yii\db\ActiveQuery::onCondition()|onCondition()]] によって条件を指定された場合、クエリが JOIN 句を含む場合は、条件は `ON` の部分に置かれます。
> Info: [[yii\db\ActiveQuery]] が [[yii\db\ActiveQuery::onCondition()|onCondition()]] によって条件を指定された場合、
クエリが JOIN 句を含む場合は、条件は `ON` の部分に置かれます。
クエリが JOIN 句を含まない場合は、条件は自動的に `WHERE` の部分に追加されます。
このようにして、リレーションのテーブルのカラムを含む条件だけが `ON` の部分に置かれます。
#### リレーションのテーブルのエイリアス <span id="relation-table-aliases"></span>
前に注意したように、クエリに JOIN を使うときは、カラム名の曖昧さを解消する必要があります。
そのために、テーブルにエイリアスを定義することがよくあります。
前に注意したように、クエリに JOIN を使うときは、カラム名の曖昧さを解消する必要があります。そのために、テーブルにエイリアスを定義することがよくあります。
リレーションのテーブルのためにエイリアスを設定することは、リレーショナル・クエリを次のようにカスタマイズすることによっても可能です。
```php
@ -1175,8 +1236,7 @@ $query->joinWith([
```
しかし、これでは非常に複雑ですし、リレーションオブジェクトのテーブル名をハードコーディングしたり、`Order::tableName()` を呼んだりしなければなりません。
バージョン 2.0.7 以降、Yii はこれに対するショートカットを提供しています。
今では、次のようにしてリレーションのテーブルのエイリアスを定義して使うことが出来ます。
バージョン 2.0.7 以降、Yii はこれに対するショートカットを提供しています。今では、次のようにしてリレーションのテーブルのエイリアスを定義して使うことが出来ます。
```php
// orders リレーションを JOIN し、結果を orders.id でソートする
@ -1237,7 +1297,19 @@ echo $customer2 === $customer ? '同じ' : '異なる';
実際、二つは同じ顧客データを含んでいますが、オブジェクトとしては異なります。
`$order->customer` にアクセスするときに追加の SQL 文が実行されて、新しいオブジェクトである `$customer2` にデータが投入されます。
上記の例において、冗長な最後の SQL 文の実行を避けるためには、下に示すように、[[yii\db\ActiveQuery::inverseOf()|inverseOf()]] メソッドを呼ぶことによって、`customer` が `orders`*逆リレーション* であることを Yii に教えておかなければなりません。
上記の例において、冗長な最後の SQL 文の実行を避けるためには、下に示すように、
[[yii\db\ActiveQuery::inverseOf()|inverseOf()]]メソッドを呼ぶことによって、`customer` が
`orders`*逆リレーション* であることを Yii に教えておかなければなりません。
```php
class Customer extends ActiveRecord
{
public function getOrders()
{
return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer');
}
}
```
このようにリレーションの宣言を修正すると、次の結果を得ることが出来ます。
@ -1256,7 +1328,8 @@ echo $customer2 === $customer ? '同じ' : '異なる';
```
> Note: 逆リレーションは [中間テーブル](#junction-table) を含むリレーションについては宣言することが出来ません。
つまり、リレーションが [[yii\db\ActiveQuery::via()|via()]] または [[yii\db\ActiveQuery::viaTable()|viaTable()]] によって定義されている場合は、[[yii\db\ActiveQuery::inverseOf()|inverseOf()]] を追加で呼んではいけません。
つまり、リレーションが [[yii\db\ActiveQuery::via()|via()]] または [[yii\db\ActiveQuery::viaTable()|viaTable()]]
によって定義されている場合は、[[yii\db\ActiveQuery::inverseOf()|inverseOf()]] を追加で呼んではいけません。
## リレーションを保存する <span id="saving-relations"></span>
@ -1287,13 +1360,16 @@ $order->subtotal = 100;
$order->link('customer', $customer);
```
[[yii\db\ActiveRecord::link()|link()]] メソッドは、リレーション名と、リレーションを確立する対象のアクティブ・レコード・インスタンスを指定することを要求します。
[[yii\db\ActiveRecord::link()|link()]] メソッドは、リレーション名と、リレーションを確立する対象の
アクティブ・レコード・インスタンスを指定することを要求します。
このメソッドは、二つのアクティブ・レコード・インスタンスをリンクする属性の値を修正して、それをデータベースに書き込みます。
上記の例では、`Order` インスタンスの `customer_id` 属性を `Customer` インスタンスの `id` 属性の値になるようにセットして、それをデータベースに保存します。
上記の例では、`Order` インスタンスの `customer_id` 属性を `Customer` インスタンスの `id`
属性の値になるようにセットして、それをデータベースに保存します。
> Note: 二つの新規作成されたアクティブ・レコード・インスタンスをリンクすることは出来ません。
[[yii\db\ActiveRecord::link()|link()]] を使用することの利点は、リレーションが [中間テーブル](#junction-table) によって定義されている場合に、さらに明白になります。
[[yii\db\ActiveRecord::link()|link()]] を使用することの利点は、リレーションが [中間テーブル](#junction-table)
によって定義されている場合に、さらに明白になります。
例えば、一つの `Order` インスタンスと一つの`Item` インスタンスをリンクするのに、次のコードを使うことが出来ます。
```php
@ -1302,30 +1378,32 @@ $order->link('items', $item);
上記のコードによって、`order_item` 中間テーブルに、注文と商品を関連付けるための行が自動的に挿入されます。
> Info: [[yii\db\ActiveRecord::link()|link()]] メソッドは、影響を受けるアクティブ・レコード・インスタンスを保存する際に、データ検証を実行しません。
> Info: [[yii\db\ActiveRecord::link()|link()]] メソッドは、
影響を受けるアクティブ・レコード・インスタンスを保存する際に、データ検証を実行しません。
このメソッドを呼ぶ前にすべての入力値を検証することはあなたの責任です。
[[yii\db\ActiveRecord::link()|link()]] の逆の操作が [[yii\db\ActiveRecord::unlink()|unlink()]] です。
これは、既存の二つのアクティブ・レコード・インスタンスのリレーションを破棄します。
例えば、
これは、既存の二つのアクティブ・レコード・インスタンスのリレーションを破棄します。例えば、
```php
$customer = Customer::find()->with('orders')->where(['id' => 123])->one();
$customer->unlink('orders', $customer->orders[0]);
```
デフォルトでは、[[yii\db\ActiveRecord::unlink()|unlink()]] メソッドは、既存のリレーションを指定している外部キーの値を `null` に設定します。
デフォルトでは、[[yii\db\ActiveRecord::unlink()|unlink()]] メソッドは、
既存のリレーションを指定している外部キーの値を `null` に設定します。
ただし、`$delete` パラメータを `true` にしてメソッドに渡して、その外部キーを含むテーブル行を削除するという方法を選ぶことも出来ます。
リレーションに中間テーブルが含まれている場合は、[[yii\db\ActiveRecord::unlink()|unlink()]] を呼ぶと、中間テーブルにある外部キーがクリアされるか、または、`$delete` が `true` であるときは、中間テーブルにある対応する行が削除されるかします。
リレーションに中間テーブルが含まれている場合は、[[yii\db\ActiveRecord::unlink()|unlink()]] を呼ぶと、
中間テーブルにある外部キーがクリアされるか、または、`$delete` が `true` であるときは、
中間テーブルにある対応する行が削除されるかします。
## DBMS 間のリレーション <span id="cross-database-relations"></span>
アクティブ・レコードは、異なるデータベースをバックエンドに持つアクティブ・レコードの間でリレーションを宣言することを可能にしています。
データベースは異なるタイプ (例えば、MySQL と PostgreSQL、または、MS SQL と MongoDB) であってもよく、別のサーバで動作していても構いません。
同じ構文を使ってリレーショナル・クエリを実行することが出来ます。
例えば、
同じ構文を使ってリレーショナル・クエリを実行することが出来ます。例えば、
```php
// Customer はリレーショナル・データベース (例えば MySQL) の "customer" テーブルと関連付けられている
@ -1364,15 +1442,14 @@ $customers = Customer::find()->with('comments')->all();
このセクションで説明されたリレーショナル・クエリ機能のほとんどを使用することが出来ます。
> Note: [[yii\db\ActiveQuery::joinWith()]] の使用は、データベース間の JOIN クエリをサポートしているデータベースに限定されます。
この理由により、上記の例では `joinWith` メソッドは使用することが出来ません。
MongoDB は JOIN をサポートしていないからです。
この理由により、上記の例では `joinWith` メソッドは使用することが出来ません。MongoDB は JOIN をサポートしていないからです。
## クエリ・クラスをカスタマイズする <span id="customizing-query-classes"></span>
デフォルトでは、全てのアクティブ・レコードのクエリは [[yii\db\ActiveQuery]] によってサポートされます。
カスタマイズされたクエリ・クラスをアクティブ・レコードで使用するためには、[[yii\db\ActiveRecord::find()]] メソッドをオーバーライドして、カスタマイズされたクエリ・クラスのインスタンスを返すようにしなければなりません。
例えば、
カスタマイズされたクエリ・クラスをアクティブ・レコードで使用するためには、[[yii\db\ActiveRecord::find()]]
メソッドをオーバーライドして、カスタマイズされたクエリ・クラスのインスタンスを返すようにしなければなりません。例えば、
```php
// file Comment.php
@ -1389,10 +1466,10 @@ class Comment extends ActiveRecord
}
```
このようにすると、`Comment` のクエリを実行したり (例えば `find()``findOne()` を呼んだり)、`Comment` とのリレーションを定義したり (例えば `hasOne()` を定義したり) する際には、いつでも、`AcctiveQuery` の代りに `CommentQuery` のインスタンスを使用することになります。
このようにすると、`Comment` のクエリを実行したり (例えば `find()``findOne()` を呼んだり)、`Comment` とのリレーションを定義したり (例えば `hasOne()` を定義したり)
する際には、いつでも、`AcctiveQuery` の代りに `CommentQuery` のインスタンスを使用することになります。
さて、`CommentQuery` クラスを定義しなければならない訳ですが、このクラスをさまざまな創造的方法でカスタマイズして、
あなたのクエリ構築作業を楽しいものにすることが出来ます。例えば、
さて、`CommentQuery` クラスを定義しなければならない訳ですが、このクラスをさまざまな創造的方法でカスタマイズして、あなたのクエリ構築作業を楽しいものにすることが出来ます。例えば、
```php
// file CommentQuery.php
@ -1419,9 +1496,8 @@ class CommentQuery extends ActiveQuery
```
> Note: 新しいクエリ構築メソッドを定義するときには、通常は、既存のどの条件も上書きしないように、
[[yii\db\ActiveQuery::onCondition()|onCondition()]] ではなく、
[[yii\db\ActiveQuery::andOnCondition()|andOnCondition()]] または [[yii\db\ActiveQuery::orOnCondition()|orOnCondition()]]
を呼んで条件を追加しなければなりません。
[[yii\db\ActiveQuery::onCondition()|onCondition()]] ではなく、[[yii\db\ActiveQuery::andOnCondition()|andOnCondition()]]
または [[yii\db\ActiveQuery::orOnCondition()|orOnCondition()]] を呼んで条件を追加しなければなりません。
このようにすると、次のようなクエリ構築のコードを書くことが出来るようになります。
@ -1430,8 +1506,8 @@ $comments = Comment::find()->active()->all();
$inactiveComments = Comment::find()->active(false)->all();
```
> Tip: 大きなプロジェクトでは、アクティブ・レコード・クラスをクリーンに保つことが出来るように、クエリ関連のコードのほとんどをカスタマイズされたクエリ・クラスに保持することが推奨されます。
> Tip: 大きなプロジェクトでは、アクティブ・レコード・クラスをクリーンに保つことが出来るように、
クエリ関連のコードのほとんどをカスタマイズされたクエリ・クラスに保持することが推奨されます。
この新しいクエリ構築メソッドは、`Comment` に関するリレーションを定義するときや、リレーショナル・クエリを実行するときにも使用することが出来ます。
@ -1462,14 +1538,14 @@ $customers = Customer::find()->with([
])->all();
```
> Info: Yii 1.1 には、*スコープ* と呼ばれる概念がありました。
Yii 2.0 では、スコープはもはや直接にはサポートされません。
> Info: Yii 1.1 には、*スコープ* と呼ばれる概念がありました。 Yii 2.0 では、スコープはもはや直接にはサポートされません。
同じ目的を達するためには、カスタマイズされたクエリ・クラスとクエリ・メソッドを使わなければなりません。
## 追加のフィールドを選択する
アクティブ・レコードのインスタンスにクエリ結果からデータが投入されるときは、受け取ったデータセットのカラムの値が対応する属性に入れられます。
アクティブ・レコードのインスタンスにクエリ結果からデータが投入されるときは、
受け取ったデータセットのカラムの値が対応する属性に入れられます。
クエリ結果から追加のカラムや値を取得して、アクティブ・レコードの内部に格納することが出来ます。
例えば、ホテルの客室の情報を含む `room` という名前のテーブルがあるとしましょう。
@ -1548,7 +1624,8 @@ $room->height = 2;
$room->volume; // まだ指定されていないため、この値は `null` になります。
```
[[yii\db\BaseActiveRecord::__get()|__get()]] と [[yii\db\BaseActiveRecord::__set()|__set()]] のマジック・メソッドを使用すれば、プロパティの動作をエミュレートすることが出来ます。
[[yii\db\BaseActiveRecord::__get()|__get()]] と [[yii\db\BaseActiveRecord::__set()|__set()]] のマジック・メソッドを使用すれば、
プロパティの動作をエミュレートすることが出来ます。
```php
class Room extends \yii\db\ActiveRecord
@ -1579,7 +1656,8 @@ class Room extends \yii\db\ActiveRecord
}
```
このようにすると、SELECT クエリによって容積が提供されていない場合に、モデルの他の属性を使って容積を自動的に計算することが出来ます。
このようにすると、SELECT クエリによって容積が提供されていない場合に、
モデルの他の属性を使って容積を自動的に計算することが出来ます。
集約フィールドも、同じように、定義されたリレーションを使って計算することが出来ます。

125
docs/guide-ja/db-dao.md

@ -1,14 +1,15 @@
データベース・アクセス・オブジェクト
====================================
[PDO](http://www.php.net/manual/ja/book.pdo.php) の上に構築された Yii DAO (データベース・アクセス・オブジェクト) は、リレーショナル・データベースにアクセスするためのオブジェクト指向 API を提供するものです。
[PDO](http://www.php.net/manual/ja/book.pdo.php) の上に構築された Yii DAO (データベース・アクセス・オブジェクト) は、
リレーショナル・データベースにアクセスするためのオブジェクト指向 API を提供するものです。
これは、データベースにアクセスする他のもっと高度な方法、例えば [クエリ・ビルダ](db-query-builder.md) や [アクティブ・レコード](db-active-record.md) の基礎でもあります。
Yii DAO を使うときは、主として素の SQL と PHP 配列を扱う必要があります。
結果として、Yii DAO はデータベースにアクセスする方法としては最も効率的なものになります。
しかし、SQL の構文はデータベースによってさまざまに異なる場合がありますので、Yii DAO を使用するということは、特定のデータベースに依存しないアプリケーションを作るためには追加の労力が必要になる、ということをも同時に意味します。
Yii は下記の DBMS のサポートを内蔵しています。
Yii 2.0 では、DAO は下記の DBMS のサポートを内蔵しています。
- [MySQL](http://www.mysql.com/)
- [MariaDB](https://mariadb.com/)
@ -18,13 +19,19 @@ Yii は下記の DBMS のサポートを内蔵しています。
- [Oracle](http://www.oracle.com/us/products/database/overview/index.html)
- [MSSQL](https://www.microsoft.com/en-us/sqlserver/default.aspx): バージョン 2008 以上。
> Info: Yii 2.1 以降では、CUBRID、Oracle および MSSQL に対する DAO サポートは、フレームワーク内蔵のコア・コンポーネント
としては提供されていません。それらは、独立した [エクステンション](structure-extensions.md) としてインストールされる
必要があります。[yiisoft/yii2-oracle](https://www.yiiframework.com/extension/yiisoft/yii2-oracle) および
[yiisoft/yii2-mssql](https://www.yiiframework.com/extension/yiisoft/yii2-mssql) が
[公式エクステンション](https://www.yiiframework.com/extensions/official) として提供されています。
> Note: PHP 7 用の pdo_oci の新しいバージョンは、現在、ソース・コードとしてのみ存在します。
[コミュニティによる説明](https://github.com/yiisoft/yii2/issues/10975#issuecomment-248479268) に従ってコンパイルするか、
または、[PDO エミュレーション・レイヤ](https://github.com/taq/pdooci) を使って下さい。
## DB 接続を作成する <span id="creating-db-connections"></span>
データベースにアクセスするために、まずは、データベースに接続するために [[yii\db\Connection]] のインスタンスを作成する必要があります。
データベースにアクセスするために、まずは、[[yii\db\Connection]] のインスタンスを作成して、データベースに接続する必要があります。
```php
$db = new yii\db\Connection([
@ -35,7 +42,8 @@ $db = new yii\db\Connection([
]);
```
DB 接続は、たいていは、さまざまな場所でアクセスする必要がありますので、次のように、[アプリケーション・コンポーネント](structure-application-components.md) の形式で構成するのが通例です。
DB 接続は、たいていは、さまざまな場所でアクセスする必要がありますので、次のように、
[アプリケーション・コンポーネント](structure-application-components.md) の形式で構成するのが通例です。
```php
return [
@ -60,8 +68,7 @@ return [
DB 接続を構成するときは、つねに [[yii\db\Connection::dsn|dsn]] プロパティによってデータ・ソース名 (DSN) を指定しなければなりません。
DSN の形式はデータベースによってさまざまに異なります。
詳細は [PHP マニュアル](http://www.php.net/manual/ja/function.PDO-construct.php) を参照して下さい。
下記にいくつかの例を挙げます。
詳細は [PHP マニュアル](http://www.php.net/manual/ja/function.PDO-construct.php) を参照して下さい。下記にいくつかの例を挙げます。
* MySQL, MariaDB: `mysql:host=localhost;dbname=mydatabase`
* SQLite: `sqlite:/path/to/database/file`
@ -88,11 +95,13 @@ ODBC 経由でデータベースに接続しようとする場合は、[[yii\db\
[[yii\db\Connection::dsn|dsn]] プロパティに加えて、たいていは [[yii\db\Connection::username|username]] と [[yii\db\Connection::password|password]] も構成しなければなりません。
構成可能なプロパティの全てのリストは [[yii\db\Connection]] を参照して下さい。
> Info: DB 接続のインスタンスを作成するとき、実際のデータベース接続は、最初の SQL を実行するか、[[yii\db\Connection::open()|open()]] メソッドを明示的に呼ぶかするまでは確立されません。
> Info: DB 接続のインスタンスを作成するとき、実際のデータベース接続は、最初の SQL を実行するか、
[[yii\db\Connection::open()|open()]] メソッドを明示的に呼ぶかするまでは確立されません。
> Tip: 時として、何らかの環境変数を初期化するために、データベース接続を確立した直後に何かクエリを実行したい場合があるでしょう (例えば、タイムゾーンや文字セットを設定するなどです)。
> Tip: 時として、何らかの環境変数を初期化するために、データベース接続を確立した直後に何かクエリを実行したい場合があるでしょう
> (例えば、タイムゾーンや文字セットを設定するなどです)。
> そうするために、データベース接続の [[yii\db\Connection::EVENT_AFTER_OPEN|afterOpen]] イベントに対するイベント・ハンドラを登録することが出来ます。
> 以下のように、アプリケーションの構成情報に直接にハンドラを登録してください
> 以下のように、アプリケーションの構成情報に直接にハンドラを登録することが出来ます
>
> ```php
> 'db' => [
@ -137,13 +146,14 @@ $count = Yii::$app->db->createCommand('SELECT COUNT(*) FROM post')
->queryScalar();
```
> Note: 精度を保つために、対応するデータベースカラムの型が数値である場合でも、データベースから取得されたデータは、全て文字列として表現されます。
> Note: 精度を保つために、対応するデータベース・カラムの型が数値である場合でも、
データベースから取得されたデータは、全て文字列として表現されます。
### パラメータをバインドする <span id="binding-parameters"></span>
パラメータを持つ SQL から DB コマンドを作成するときは、SQL インジェクション攻撃を防止するために、ほとんど全ての場合においてパラメータをバインドする手法を用いるべきです。
例えば、
パラメータを持つ SQL から DB コマンドを作成するときは、SQL インジェクション攻撃を防止するために、
ほとんど全ての場合においてパラメータをバインドする手法を用いるべきです。例えば、
```php
$post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status=:status')
@ -158,7 +168,8 @@ SQL 文において、一つまたは複数のパラメータ・プレースホ
* [[yii\db\Command::bindValue()|bindValue()]]: 一つのパラメータの値をバインドします。
* [[yii\db\Command::bindValues()|bindValues()]]: 一回の呼び出しで複数のパラメータの値をバインドします。
* [[yii\db\Command::bindParam()|bindParam()]]: [[yii\db\Command::bindValue()|bindValue()]] と似ていますが、パラメータを参照渡しでバインドすることもサポートしています。
* [[yii\db\Command::bindParam()|bindParam()]]: [[yii\db\Command::bindValue()|bindValue()]] と似ていますが、
パラメータを参照渡しでバインドすることもサポートしています。
次の例はパラメータをバインドする方法の選択肢を示すものです。
@ -174,8 +185,8 @@ $post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status
```
パラメータ・バインディングは [プリペアド・ステートメント](http://php.net/manual/ja/mysqli.quickstart.prepared-statements.php) によって実装されています。
パラメータ・バインディングには、SQL インジェクション攻撃を防止する以外にも、SQL 文を一度だけ準備して異なるパラメータで複数回実行することにより、パフォーマンスを向上させる効果もあります。
例えば、
パラメータ・バインディングには、SQL インジェクション攻撃を防止する以外にも、SQL 文を一度だけ準備して異なるパラメータで複数回実行することにより、
パフォーマンスを向上させる効果もあります。例えば、
```php
$command = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id');
@ -185,7 +196,8 @@ $post2 = $command->bindValue(':id', 2)->queryOne();
// ...
```
[[yii\db\Command::bindParam()|bindParam()]] はパラメータを参照渡しでバインドすることをサポートしていますので、上記のコードは次のように書くことも出来ます。
[[yii\db\Command::bindParam()|bindParam()]] はパラメータを参照渡しでバインドすることをサポートしていますので、
上記のコードは次のように書くことも出来ます。
```php
$command = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id')
@ -198,21 +210,21 @@ $id = 2;
$post2 = $command->queryOne();
```
クエリの実行の前にプレースホルダを変数 `$id` にバインドし、そして、後に続く各回の実行の前にその変数の値を変更していること (これは、たいてい、ループで行います) に着目してください。
このやり方でクエリを実行すると、パラメータの値が違うごとに新しいクエリを実行するのに比べて、はるかに効率を良くすることが出来ます。
クエリの実行の前にプレースホルダを変数 `$id` にバインドし、そして、後に続く各回の実行の前にその変数の値を変更していること
(これは、たいてい、ループで行います) に着目してください。
このやり方でクエリを実行すると、パラメータの値が違うごとに新しいクエリを実行するのに比べて、
はるかに効率を良くすることが出来ます。
> Info: パラメータ・バインディングは、素の SQL を含む文字列に値を挿入しなければならない場所でのみ使用されます。
> [クエリ・ビルダ](db-query-builder.md) や [アクティブ・レコード](db-active-record.md) のような高レベルの抽象的レイヤーでは、
> 多くの場所で SQL に変換される値の配列を指定する場合がよくあります。
> これらの場所では Yii によってパラメータ・バインディングが内部的に実行されますので、
> パラメータを手動で指定する必要はありません。
> これらの場所では Yii によってパラメータ・バインディングが内部的に実行されますので、パラメータを手動で指定する必要はありません。
### SELECT しないクエリを実行する <span id="non-select-queries"></span>
今までのセクションで紹介した `queryXyz()` メソッドは、すべて、データベースからデータを取得する SELECT クエリを扱うものでした。
データを返さないクエリのためには、代りに [[yii\db\Command::execute()]] メソッドを呼ばなければなりません。
例えば、
データを返さないクエリのためには、代りに [[yii\db\Command::execute()]] メソッドを呼ばなければなりません。例えば、
```php
Yii::$app->db->createCommand('UPDATE post SET status=1 WHERE id=1')
@ -221,9 +233,9 @@ Yii::$app->db->createCommand('UPDATE post SET status=1 WHERE id=1')
[[yii\db\Command::execute()]] メソッドは SQL の実行によって影響を受けた行の数を返します。
INSERT、UPDATE および DELETE クエリのためには、素の SQL を書く代りに、それぞれ、[[yii\db\Command::insert()|insert()]]、[[yii\db\Command::update()|update()]]、[[yii\db\Command::delete()|delete()]] を呼んで、対応する SQL を構築することが出来ます。
これらのメソッドは、テーブルとカラムの名前を適切に引用符で囲み、パラメータの値をバインドします。
例えば、
INSERT、UPDATE および DELETE クエリのためには、素の SQL を書く代りに、それぞれ、
[[yii\db\Command::insert()|insert()]]、[[yii\db\Command::update()|update()]]、[[yii\db\Command::delete()|delete()]] を呼んで、対応する SQL を構築することが出来ます。
これらのメソッドは、テーブルとカラムの名前を適切に引用符で囲み、パラメータの値をバインドします。例えば、
```php
// INSERT (テーブル名, カラムの値)
@ -251,8 +263,7 @@ Yii::$app->db->createCommand()->batchInsert('user', ['name', 'age'], [
])->execute();
```
もう一つの有用なメソッドは [[yii\db\Command::upsert()|upsert()]] です。
upsert は、(ユニーク制約に合致する)行がまだ存在しない場合はデータベース・テーブルに行を挿入し、
もう一つの有用なメソッドは [[yii\db\Command::upsert()|upsert()]] です。upsert は、(ユニーク制約に合致する)行がまだ存在しない場合はデータベース・テーブルに行を挿入し、
既に行が存在している場合は行を更新する、アトミックな操作です。
```php
@ -277,10 +288,11 @@ Yii::$app->db->createCommand()->upsert('pages', [
データベースによって名前を引用符で囲む規則がさまざまに異なるからです。
この問題を克服するために、次のように、Yii によって導入された引用符の構文を使用することが出来ます。
* `[[カラム名]]`: 引用符で囲まれるカラム名を二重角括弧で包む。
* `{{テーブル名}}`: 引用符で囲まれるテーブル名を二重波括弧で包む。
* `[[カラム名]]`: 引用符で囲むべきカラム名は、二重角括弧で包む。
* `{{テーブル名}}`: 引用符で囲むべきテーブル名は、二重波括弧で包む。
Yii DAO は、このような構文を、DBMS 固有の文法に従って、適切な引用符で囲まれたカラム名とテーブル名に自動的に変換します。
Yii DAO は、このような構文を、DBMS 固有の文法に従って、
適切な引用符で囲まれたカラム名とテーブル名に自動的に変換します。
例えば、
```php
@ -289,9 +301,11 @@ $count = Yii::$app->db->createCommand("SELECT COUNT([[id]]) FROM {{employee}}")
->queryScalar();
```
### テーブル接頭辞を使う <span id="using-table-prefix"></span>
あなたの DB テーブル名のほとんどが何か共通の接頭辞を持っている場合は、Yii DAO によってサポートされているテーブル接頭辞の機能を使うことが出来ます。
あなたの DB テーブル名のほとんどが何か共通の接頭辞を持っている場合は、
Yii DAO によって提供されているテーブル接頭辞の機能を使うことが出来ます。
最初に、アプリケーションの構成情報で、[[yii\db\Connection::tablePrefix]] プロパティによって、テーブル接頭辞を指定します。
@ -318,9 +332,11 @@ $count = Yii::$app->db->createCommand("SELECT COUNT([[id]]) FROM {{%employee}}")
->queryScalar();
```
## トランザクションを実行する <span id="performing-transactions"></span>
一続きになった複数の関連するクエリを実行するときは、データの整合性と一貫性を保証するために、一連のクエリをトランザクションで囲む必要がある場合があります。
一続きになった複数の関連するクエリを実行するときは、データの整合性と一貫性を保証するために、
一連のクエリをトランザクションで囲む必要がある場合があります。
一連のクエリのどの一つが失敗した場合でも、データベースは、何一つクエリが実行されなかったかのような状態へとロールバックされます。
次のコードはトランザクションの典型的な使用方法を示すものです。
@ -338,7 +354,6 @@ Yii::$app->db->transaction(function($db) {
```php
$db = Yii::$app->db;
$transaction = $db->beginTransaction();
try {
$db->createCommand($sql1)->execute();
$db->createCommand($sql2)->execute();
@ -362,17 +377,15 @@ try {
が呼ばれて、トランザクションの中で失敗したクエリに先行するクエリによって行なわれた変更が、ロールバックされます。
そして、`throw $e` が、まるでそれをキャッチしなかったかのように、例外を再スローしますので、通常のエラー処理プロセスがその例外の面倒を見ることになります。
> Note: 上記のコードでは、PHP 5.x と PHP 7.x との互換性のために、二つの
> catch ブロックを持っています。`\Exception` は PHP 7.0 以降では、
> [`\Throwable` インタフェイス](http://php.net/manual/ja/class.throwable.php) を実装しています。
> Note: 上記のコードでは、PHP 5.x と PHP 7.x との互換性のために、二つのcatch ブロックを持っています。
> `\Exception` は PHP 7.0 以降では、[`\Throwable` インタフェイス](http://php.net/manual/ja/class.throwable.php) を実装しています。
> 従って、あなたのアプリケーションが PHP 7.0 以上しか使わない場合は、`\Exception` の部分を省略することが出来ます。
### 分離レベルを指定する <span id="specifying-isolation-levels"></span>
Yii は、トランザクションの [分離レベル] の設定もサポートしています。
デフォルトでは、新しいトランザクションを開始したときは、データベースシステムによって設定された分離レベルを使用します。
デフォルトの分離レベルは、次のようにしてオーバーライドすることが出来ます。
Yii は、トランザクションの [分離レベル] の設定もサポートしています。デフォルトでは、新しいトランザクションを開始したときは、
データベースシステムによって設定された分離レベルを使用します。デフォルトの分離レベルは、次のようにしてオーバーライドすることが出来ます。
```php
$isolationLevel = \yii\db\Transaction::REPEATABLE_READ;
@ -410,6 +423,7 @@ DBMS によっては、接続全体に対してのみ分離レベルの設定を
[分離レベル]: http://ja.wikipedia.org/wiki/%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E5%88%86%E9%9B%A2%E3%83%AC%E3%83%99%E3%83%AB
### トランザクションを入れ子にする <span id="nesting-transactions"></span>
あなたの DBMS が Savepoint をサポートしている場合は、次のように、複数のトランザクションを入れ子にすることが出来ます。
@ -457,11 +471,13 @@ try {
## レプリケーションと読み書きの分離 <span id="read-write-splitting"></span>
多くの DBMS は、データベースの可用性とサーバのレスポンス・タイムを向上させるために、[データベース・レプリケーション](http://ja.wikipedia.org/wiki/%E3%83%AC%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3#.E3.83.87.E3.83.BC.E3.82.BF.E3.83.99.E3.83.BC.E3.82.B9) をサポートしています。
多くの DBMS は、データベースの可用性とサーバのレスポンス・タイムを向上させるために、
[データベース・レプリケーション](http://ja.wikipedia.org/wiki/%E3%83%AC%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3#.E3.83.87.E3.83.BC.E3.82.BF.E3.83.99.E3.83.BC.E3.82.B9) をサポートしています。
データベース・レプリケーションによって、データはいわゆる *マスタ・サーバ* から *スレーブ・サーバ* に複製されます。
データの書き込みと更新はすべてマスタ・サーバ上で実行されなければなりませんが、データの読み出しはスレーブ・サーバ上でも可能です。
データベース・レプリケーションを活用して読み書きの分離を達成するために、[[yii\db\Connection]] コンポーネントを下記のように構成することが出来ます。
データベース・レプリケーションを活用して読み書きの分離を達成するために、
[[yii\db\Connection]] コンポーネントを下記のように構成することが出来ます。
```php
[
@ -507,14 +523,16 @@ $rows = Yii::$app->db->createCommand('SELECT * FROM user LIMIT 10')->queryAll();
Yii::$app->db->createCommand("UPDATE user SET username='demo' WHERE id=1")->execute();
```
> Info: [[yii\db\Command::execute()]] を呼ぶことで実行されるクエリは、書き込みのクエリと見なされ、[[yii\db\Command]] の "query" メソッドのうちの一つによって実行されるその他すべてのクエリは、読み出しクエリと見なされます。
> Info: [[yii\db\Command::execute()]] を呼ぶことで実行されるクエリは、書き込みのクエリと見なされ、
[[yii\db\Command]] の "query" メソッドのうちの一つによって実行されるその他すべてのクエリは、読み出しクエリと見なされます。
現在アクティブなスレーブ接続は `Yii::$app->db->slave` によって取得することが出来ます。
`Connection` コンポーネントは、スレーブ間のロード・バランス調整とフェイルオーバーをサポートしています。
読み出しクエリを最初に実行するときに、`Connection` コンポーネントはランダムにスレーブを選んで接続を試みます。
読み出しクエリを最初に実行するときに、`Connection` コンポーネントはランダムにスレーブを選んで接続を試みます。
そのスレーブが「死んでいる」ことが分かったときは、他のスレーブを試します。
スレーブが一つも使用できないときは、マスタに接続します。
[[yii\db\Connection::serverStatusCache|サーバステータスキャッシュ]] を構成することによって、「死んでいる」サーバを記憶し、[[yii\db\Connection::serverRetryInterval|一定期間]] はそのサーバへの接続を再試行しないようにすることが出来ます。
[[yii\db\Connection::serverStatusCache|サーバ・ステータスキャッシュ]] を構成することによって、「死んでいる」サーバを記憶し、
[[yii\db\Connection::serverRetryInterval|一定期間]] はそのサーバへの接続を再試行しないようにすることが出来ます。
> Info: 上記の構成では、すべてのスレーブに対して 10 秒の接続タイムアウトが指定されています。
これは、10 秒以内に接続できなければ、そのスレーブは「死んでいる」と見なされることを意味します。
@ -523,6 +541,7 @@ Yii::$app->db->createCommand("UPDATE user SET username='demo' WHERE id=1")->exec
複数のマスタと複数のスレーブという構成にすることも可能です。例えば、
```php
[
'class' => 'yii\db\Connection',
@ -567,10 +586,13 @@ Yii::$app->db->createCommand("UPDATE user SET username='demo' WHERE id=1")->exec
`Connection` コンポーネントは、スレーブ間での場合と同じように、マスタ間でのロード・バランス調整とフェイルオーバーをサポートしています。
一つ違うのは、マスタが一つも利用できないときは例外が投げられる、という点です。
> Note: [[yii\db\Connection::masters|masters]] プロパティを使って一つまたは複数のマスタを構成する場合は、データベース接続を定義する `Connection` オブジェクト自体のその他のプロパティ (例えば、`dsn`、`username`、`password`) は全て無視されます。
> Note: [[yii\db\Connection::masters|masters]] プロパティを使って一つまたは複数のマスタを構成する場合は、
データベース接続を定義する `Connection` オブジェクト自体の他のプロパティ
(例えば、`dsn`、`username`、`password`) は全て無視されます。
デフォルトでは、トランザクションはマスタ接続を使用します。そして、トランザクション内では、全ての DB 操作はマスタ接続を使用します。
例えば、
デフォルトでは、トランザクションはマスタ接続を使用します。
そして、トランザクション内では、全ての DB 操作はマスタ接続を使用します。例えば、
```php
$db = Yii::$app->db;
@ -599,11 +621,11 @@ $transaction = Yii::$app->db->slave->beginTransaction();
```
時として、読み出しクエリの実行にマスタ接続を使うことを強制したい場合があります。
これは、`useMaster()` メソッドを使うによって達成できます。
これは、`useMaster()` メソッドを使うことによって達成できます。
```php
$rows = Yii::$app->db->useMaster(function ($db) {
return Yii::$app->db->createCommand('SELECT * FROM user LIMIT 10')->queryAll();
return $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll();
});
```
@ -643,7 +665,7 @@ Yii::$app->db->createCommand()->createTable('post', [
上記の配列は、生成されるカラムの名前と型を記述しています。
Yii はカラムの型のために一連の抽象データ型を提供しているため、データベースの違いを意識せずにスキーマを定義することが可能です。
これらの抽象データ型は、テーブルが作成されるデータベースに依存する DBMS 固有の型定義に変換されます。
これらの抽象データ型は、テーブルが作成されるデータベースによって異なる DBMS 固有の型定義に変換されます。
詳しい情報は [[yii\db\Command::createTable()|createTable()]] メソッドの API ドキュメントを参照してください。
データベースのスキーマを変更するだけでなく、テーブルに関する定義情報を DB 接続の [[yii\db\Connection::getTableSchema()|getTableSchema()]] メソッドによって取得することも出来ます。
@ -654,4 +676,5 @@ $table = Yii::$app->db->getTableSchema('post');
```
このメソッドは、テーブルのカラム、プライマリ・キー、外部キーなどの情報を含む [[yii\db\TableSchema]] オブジェクトを返します。
これらの情報は、主として [クエリ・ビルダ](db-query-builder.md) や [アクティブ・レコード](db-active-record.md) によって利用されて、特定のデータベースに依存しないコードを書くことを助けてくれています。
これらの情報は、主として [クエリ・ビルダ](db-query-builder.md) や [アクティブ・レコード](db-active-record.md) によって利用されて、
特定のデータベースに依存しないコードを書くことを助けてくれています。

179
docs/guide-ja/db-migrations.md

@ -1,16 +1,21 @@
データベース・マイグレーション
============================
データベース駆動型のアプリケーションを開発し保守する途上で、ソース・コードが進化するのと同じように、使用されるデータベースの構造も進化していきます。
例えば、アプリケーションの開発中に、新しいテーブルが必要であることが分ったり、アプリケーションを配備した後に、クエリのパフォーマンスを向上させるためにインデックスを作成すべきことが発見されたりします。
データベースの構造の変更が何らかのソース・コードの変更を要求する場合はよくありますから、Yii はいわゆる *データベース・マイグレーション* 機能を提供して、ソース・コードとともにバージョン管理される *データベース・マイグレーション* の形式でデータベースの変更を追跡できるようにしています。
データベース駆動型のアプリケーションを開発し保守する途上で、ソース・コードが進化するのと同じように、
使用されるデータベースの構造も進化していきます。
例えば、アプリケーションの開発中に、新しいテーブルが必要であることが分ったり、アプリケーションを配備した後に、
クエリのパフォーマンスを向上させるためにインデックスを作成すべきことが発見されたりします。
データベースの構造の変更が何らかのソース・コードの変更を要求する場合はよくありますから、
Yii はいわゆる *データベース・マイグレーション* 機能を提供して、ソース・コードとともにバージョン管理される
*データベース・マイグレーション* の形式でデータベースの変更を追跡できるようにしています。
下記の一連のステップは、開発中にチームによってデータベース・マイグレーションがどのように使用されるかを示す例です。
1. Tim が新しいマイグレーション (例えば、新しいテーブルを作成したり、カラムの定義を変更したりなど) を作る。
2. Tim が新しいマイグレーションをソース・コントロール・システム (例えば Git や Mercurial) にコミットする。
3. Doug がソース・コントロール・システムから自分のレポジトリを更新して新しいマイグレーションを受け取る。
4. Doug がマイグレーションを彼のローカルの開発用データベースに適用して、自分のデータベースの同期を取り、Tim が行った変更を反映する。
4. Doug がマイグレーションを彼のローカルの開発用データベースに適用して、自分のデータベースの同期を取り、
Tim が行った変更を反映する。
そして、次の一連のステップは、本番環境でデータベース・マイグレーションとともに新しいリリースを配備する方法を示すものです。
@ -33,6 +38,13 @@ Yii は一連のマイグレーション・コマンドライン・ツールを
> Tip: マイグレーションはデータベース・スキーマに影響を及ぼすだけでなく、既存のデータを新しいスキーマに合うように修正したり、RBAC 階層を作成したり、
キャッシュをクリーンアップしたりするために使うことも出来ます。
> Note: マイグレーションを使ってデータを操作する際に、作成済みの[アクティブ・レコード](db-active-record.md)・クラスを使えば
> 便利かも知れないと気が付くことがあるでしょう。なぜなら、ロジックのいくつかは既にアクティブ・レコードで実装済みなのですから。
> しかしながら、永久に不変であり続けることを本質とするマイグレーションにおいて書かれるコードとは対照的に、アプリケーションの
> ロジックは変化にさらされるものであることに留意しなければなりません。つまり、マイグレーション・コードにアクティブ・レコードを使った場合、
> アクティブ・レコードのレイヤにおけるロジックの変更が既存のマイグレーションを偶発的に破壊する可能性があります。この理由により、
> マイグレーション・コードは、アクティブ・レコード・クラスなど、他のアプリケーション・ロジックから独立を保つべきです。
## マイグレーションを作成する <span id="creating-migrations"></span>
@ -43,16 +55,19 @@ yii migrate/create <name>
```
要求される `name` パラメータには、マイグレーションの非常に短い説明を指定します。
例えば、マイグレーションが *news* という名前のテーブルを作成するものである場合は、`create_news_table` という名前を使って、次のようにコマンドを実行すれば良いでしょう。
例えば、マイグレーションが *news* という名前のテーブルを作成するものである場合は、
`create_news_table` という名前を使って、次のようにコマンドを実行すれば良いでしょう。
```
yii migrate/create create_news_table
```
> Note: この `name` 引数は、生成されるマイグレーション・クラス名の一部として使用されますので、アルファベット、数字、および/または、アンダースコアだけを含むものでなければなりません。
> Note: この `name` 引数は、生成されるマイグレーション・クラス名の一部として使用されますので、
アルファベット、数字、および/または、アンダースコアだけを含むものでなければなりません。
上記のコマンドは、`m150101_185401_create_news_table.php` という名前の新しい PHP クラス・ファイルを `@app/migrations` ディレクトリに作成します。
このファイルは次のようなコードを含み、主として、スケルトン・コードを持った `m150101_185401_create_news_table` というマイグレーション・クラスを宣言するためのものす。
上記のコマンドは、`m150101_185401_create_news_table.php` という名前の新しい PHP クラス・ファイルを
`@app/migrations` ディレクトリに作成します。このファイルは次のようなコードを含み、主として、
スケルトン・コードを持った `m150101_185401_create_news_table` というマイグレーション・クラスを宣言するためのものす。
```php
<?php
@ -87,8 +102,7 @@ class m150101_185401_create_news_table extends Migration
```
各データベース・マイグレーションは [[yii\db\Migration]] から拡張した PHP クラスとして定義されます。
マイグレーション・クラスの名前は、`m<YYMMDD_HHMMSS>_<Name>` という形式で自動的に生成されます。
ここで、
マイグレーション・クラスの名前は、`m<YYMMDD_HHMMSS>_<Name>` という形式で自動的に生成されます。ここで、
* `<YYMMDD_HHMMSS>` は、マイグレーション作成コマンドが実行された UTC 日時を表し、
* `<Name>` は、あなたがコマンドに与えた `name` 引数と同じ値になります。
@ -119,7 +133,6 @@ class m150101_185401_create_news_table extends Migration
{
$this->dropTable('news');
}
}
```
@ -128,10 +141,12 @@ class m150101_185401_create_news_table extends Migration
また、データベース・マイグレーションを取り消すことはあまり一般的ではありませんので、場合によっては、面倒くさいというだけの理由で `down()` を実装しないこともあるでしょう。
そういう場合は、マイグレーションが取り消し不可能であることを示すために、`down()` メソッドで false を返さなければなりません。
基底のマイグレーション・クラス [[yii\db\Migration]] は、[[yii\db\Migration::db|db]] プロパティによって、データベース接続にアクセスすることを可能にしています。
このデータベース接続によって、[データベース・スキーマを扱う](db-dao.md#database-schema) で説明されているメソッドを使い、データベース・スキーマを操作することが出来ます。
基底のマイグレーション・クラス [[yii\db\Migration]] は、[[yii\db\Migration::db|db]] プロパティによって、
データベース接続にアクセスすることを可能にしています。このデータベース接続によって、[データベース・スキーマを扱う](db-dao.md#database-schema)
で説明されているメソッドを使い、データベース・スキーマを操作することが出来ます。
テーブルやカラムを作成するときは、物理的な型を使うのでなく、*抽象型* を使って、あなたのマイグレーションが特定の DBMS に依存しないようにします。
テーブルやカラムを作成するときは、物理的な型を使うのでなく、*抽象型* を使って、
あなたのマイグレーションが特定の DBMS に依存しないようにします。
[[yii\db\Schema]] クラスが、サポートされている抽象型を表す一連の定数を定義しています。
これらの定数は `TYPE_<Name>` という形式の名前を持っています。
例えば、`TYPE_PK` は、オート・インクリメントのプライマリ・キー型であり、`TYPE_STRING` は文字列型です。
@ -141,7 +156,8 @@ MySQL の場合は、`TYPE_PK` は `int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY`
抽象型を使用するときに、付随的な制約を追加することが出来ます。
上記の例では、`Schema::TYPE_STRING` に ` NOT NULL` を追加して、このカラムが null を許容しないことを指定しています。
> Info: 抽象型と物理型の対応関係は、それぞれの `QueryBuilder` の具象クラスの [[yii\db\QueryBuilder::$typeMap|$typeMap]] プロパティによって定義されています。
> Info: 抽象型と物理型の対応関係は、それぞれの `QueryBuilder` の具象クラスの [[yii\db\QueryBuilder::$typeMap|$typeMap]]
プロパティによって定義されています。
バージョン 2.0.6 以降は、カラムのスキーマを定義するための更に便利な方法を提供するスキーマビルダが新たに導入されています。
したがって、上記のマイグレーションは次のように書くことが出来ます。
@ -180,7 +196,6 @@ class m150101_185401_create_news_table extends Migration
例えば、`create_xxx_table` や `drop_xxx_table` であれば、テーブルの作成や削除をするコードが追加されます。
以下で、この機能の全ての変種を説明します。
### テーブルの作成
```
@ -291,7 +306,6 @@ class m150811_220037_create_post_table extends Migration
> Note: プライマリ・キーが自動的に追加されて、デフォルトでは `id` と名付けられます。
> 別の名前を使いたい場合は、`--fields="name:primaryKey"` のように、明示的に指定してください。
#### 外部キー
バージョン 2.0.8 からは、`foreignKey` キーワードを使って外部キーを生成することができます。
@ -403,18 +417,20 @@ class m160328_040430_create_post_table extends Migration
これらはすべて同じコードを生成します。
`foreignKey` キーワードは括弧の中にパラメータを取ることが出来て、これが生成される外部キーの関連テーブルの名前になります。
`foreignKey` キーワードは括弧の中にパラメータを取ることが出来て、
これが生成される外部キーの関連テーブルの名前になります。
パラメータが渡されなかった場合は、テーブル名はカラム名から推測されます。
上記の例で `author_id:integer:notNull:foreignKey(user)` は、`user` テーブルへの外部キーを持つ `author_id` という名前のカラムを生成します。
一方、`category_id:integer:defaultValue(1):foreignKey` は、`category` テーブルへの外部キーを持つ `category_id` というカラムを生成します。
上記の例で `author_id:integer:notNull:foreignKey(user)` は、
`user` テーブルへの外部キーを持つ `author_id` という名前のカラムを生成します。
一方、`category_id:integer:defaultValue(1):foreignKey` は、
`category` テーブルへの外部キーを持つ `category_id` というカラムを生成します。
2.0.11 以降では、`foreignKey` キーワードは空白で区切られた第二のパラメータを取ることが出来ます。
これは、生成される外部キーに関連づけられるカラム名を表します。
第二のパラメータが渡されなかった場合は、カラム名はテーブル・スキーマから取得されます。
スキーマが存在しない場合や、プライマリ・キーが設定されていなかったり、複合キーであったりする場合は、デフォルト名として `id` が使用されます。
### テーブルを削除する
```
@ -444,7 +460,8 @@ class m150811_220037_drop_post_table extends Migration
### カラムを追加する
マイグレーションの名前が `add_xxx_column_to_yyy_table` の形式である場合、ファイルの内容は、必要となる `addColumn``dropColumn` を含むことになります。
マイグレーションの名前が `add_xxx_column_to_yyy_table` の形式である場合、ファイルの内容は、
必要となる `addColumn``dropColumn` を含むことになります。
カラムを追加するためには、次のようにします。
@ -477,7 +494,8 @@ yii migrate/create add_xxx_column_yyy_column_to_zzz_table --fields="xxx:integer,
### カラムを削除する
マイグレーションの名前が `drop_xxx_column_from_yyy_table` の形式である場合、ファイルの内容は、必要となる `addColumn``dropColumn` を含むことになります。
マイグレーションの名前が `drop_xxx_column_from_yyy_table` の形式である場合、
ファイルの内容は、必要となる `addColumn``dropColumn` を含むことになります。
```
yii migrate/create drop_position_column_from_post_table --fields="position:integer"
@ -502,7 +520,8 @@ class m150811_220037_drop_position_column_from_post_table extends Migration
### 中間テーブルを追加する
マイグレーションの名前が `create_junction_table_for_xxx_and_yyy_tables` の形式である場合は、中間テーブルを作成するのに必要となるコードが生成されます。
マイグレーションの名前が `create_junction_table_for_xxx_and_yyy_tables` の形式である場合は、
中間テーブルを作成するのに必要となるコードが生成されます。
```
yii migrate/create create_junction_table_for_post_and_tag_tables --fields="created_at:dateTime"
@ -604,10 +623,10 @@ class m160328_041642_create_junction_table_for_post_and_tag_tables extends Migra
2.0.11 以降では、中間テーブルの外部キーのカラム名はテーブル・スキーマから取得されます。
スキーマでテーブルが定義されていない場合や、プライマリ・キーが設定されていなかったり複合キーであったりする場合は、デフォルト名 `id` が使われます。
### トランザクションを使うマイグレーション <span id="transactional-migrations"></span>
複雑な一連の DB マイグレーションを実行するときは、通常、データベースの一貫性と整合性を保つために、各マイグレーションが全体として成功または失敗することを保証する必要があります。
複雑な一連の DB マイグレーションを実行するときは、通常、データベースの一貫性と整合性を保つために、
各マイグレーションが全体として成功または失敗することを保証する必要があります。
この目的を達成するために、各マイグレーションの DB 操作を [トランザクション](db-dao.md#performing-transactions) で囲むことが推奨されます。
トランザクションを使うマイグレーションを実装するためのもっと簡単な方法は、マイグレーションのコードを `safeUp()``safeDown()` のメソッドに入れることです。
@ -646,20 +665,24 @@ class m150101_185401_create_news_table extends Migration
```
通常、`safeUp()` で複数の DB 操作を実行する場合は、`safeDown()` では実行の順序を逆にしなければならないことに注意してください。
上記の例では、`safeUp()` では、最初にテーブルを作って、次に行を挿入し、`safeDown()` では、先に行を削除して、次にテーブルを削除しています。
上記の例では、`safeUp()` では、最初にテーブルを作って、次に行を挿入し、
`safeDown()` では、先に行を削除して、次にテーブルを削除しています。
> Note: 全ての DBMS がトランザクションをサポートしている訳ではありません。
また、トランザクションに入れることが出来ない DB クエリもあります。
> Note: 全ての DBMS がトランザクションをサポートしている訳ではありません。また、トランザクションに入れることが出来ない DB クエリもあります。
いくつかの例を [暗黙のコミット](http://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html) で見ることが出来ます。
その場合には、代りに、`up()` と `down()` を実装しなければなりません。
### データベース・アクセス・メソッド <span id="db-accessing-methods"></span>
基底のマイグレーション・クラス [[yii\db\Migration]] は、データベースにアクセスして操作するための一連のメソッドを提供しています。
あなたは、これらのメソッドが、[[yii\db\Command]] クラスによって提供される [DAO メソッド](db-dao.md) と同じような名前を付けられていることに気付くでしょう。
例えば、[[yii\db\Migration::createTable()]] メソッドは、[[yii\db\Command::createTable()]] と全く同じように、新しいテーブルを作成します。
例えば、[[yii\db\Migration::createTable()]] メソッドは、[[yii\db\Command::createTable()]] と全く同じように、
新しいテーブルを作成します。
[[yii\db\Migration]] によって提供されているメソッドを使うことの利点は、[[yii\db\Command]] インスタンスを明示的に作成する必要がないこと、そして、各メソッドを実行すると、どのようなデータベース操作がどれだけの時間をかけて実行されたかを教えてくれる有益なメッセージが自動的に表示されることです。
[[yii\db\Migration]] によって提供されているメソッドを使うことの利点は、[[yii\db\Command]]
インスタンスを明示的に作成する必要がないこと、そして、各メソッドを実行すると、
どのようなデータベース操作がどれだけの時間をかけて実行されたかを教えてくれる有益なメッセージが自動的に表示されることです。
以下がそういうデータベース・アクセス・メソッドの一覧です。
@ -688,14 +711,16 @@ class m150101_185401_create_news_table extends Migration
* [[yii\db\Migration::dropCommentFromTable()|dropCommentFromTable()]]: テーブルからコメントを削除
> Info: [[yii\db\Migration]] は、データベース・クエリ・メソッドを提供しません。
これは、通常、データベースからのデータ取得については、メッセージを追加して表示する必要がないからです。
更にまた、複雑なクエリを構築して実行するためには、強力な [クエリ・ビルダ](db-query-builder.md) を使うことが出来るからです。
> Note: マイグレーションを使ってデータを操作する場合に、あなたは、あなたの [アクティブ・レコード](db-active-record.md) クラスをデータ操作に使えば便利じゃないか、と気付くかもしれません。
> なぜなら、いくつかのロジックは既にアクティブ・レコードで実装済みだから、と。
> しかしながら、マイグレーションの中で書かれるコードが永久に不変であることを本質とするのと対照的に、アプリケーションのロジックは変化にさらされるものであるということを心に留めなければなりません。
> 従って、マイグレーションのコードでアクティブ・レコードを使用していると、アクティブ・レコードのレイヤにおけるロジックの変更が思いがけず既存のマイグレーションを破壊することがあり得ます。
> このような理由のため、マイグレーションのコードはアクティブ・レコードのようなアプリケーションの他のロジックから独立を保つべきです。
> これは、通常、データベースからのデータ取得については、メッセージを追加して表示する必要がないからです。
> 更にまた、複雑なクエリを構築して実行するためには、強力な [クエリ・ビルダ](db-query-builder.md) を使うことが出来るからです。
> マイグレーションの中でクエリ・ビルダを使うと、次のようなコードになります。
>
> ```php
> // 全ユーザについて、status フィールドを更新する
> foreach((new Query)->from('users')->each() as $user) {
> $this->update('users', ['status' => 1], ['id' => $user['id']]);
> }
> ```
## マイグレーションを適用する <span id="applying-migrations"></span>
@ -707,17 +732,20 @@ yii migrate
```
コマンドを実行すると、まだ適用されていない全てのマイグレーションが一覧表示されます。
リストされたマイグレーションを適用することをあなたが確認すると、タイムスタンプの値の順に、一つずつ、すべての新しいマイグレーション・クラスの `up()` または `safeUp()` メソッドが実行されます。
リストされたマイグレーションを適用することをあなたが確認すると、タイムスタンプの値の順に、一つずつ、
すべての新しいマイグレーション・クラスの `up()` または `safeUp()` メソッドが実行されます。
マイグレーションのどれかが失敗した場合は、コマンドは残りのマイグレーションを適用せずに終了します。
> Tip: あなたのサーバでコマンドラインを使用できない場合は
> [web shell](https://github.com/samdark/yii2-webshell) エクステンションを使ってみてください。
適用が成功したマイグレーションの一つ一つについて、`migration` という名前のデータベース・テーブルに行が挿入されて、マイグレーションの成功が記録されます。
この記録によって、マイグレーション・ツールは、どのマイグレーションが適用され、どのマイグレーションが適用されていないかを特定することが出来ます。
適用が成功したマイグレーションの一つ一つについて、`migration` という名前のデータベース・テーブルに行が挿入されて、
マイグレーションの成功が記録されます。この記録によって、マイグレーション・ツールは、どのマイグレーションが適用され、
どのマイグレーションが適用されていないかを特定することが出来ます。
> Info: マイグレーション・ツールは、コマンドの [[yii\console\controllers\MigrateController::db|db]] オプションで指定されたデータベースに `migration` テーブルを自動的に作成します。
デフォルトでは、このデータベースは `db` [アプリケーション・コンポーネント](structure-application-components.md) によって指定されます。
> Info: マイグレーション・ツールは、コマンドの [[yii\console\controllers\MigrateController::db|db]] オプションで指定されたデータベースに
`migration` テーブルを自動的に作成します。デフォルトでは、このデータベースは
`db` [アプリケーション・コンポーネント](structure-application-components.md) によって指定されます。
時として、利用できる全てのマイグレーションではなく、一つまたは数個の新しいマイグレーションだけを適用したい場合があります。
コマンドを実行するときに、適用したいマイグレーションの数を指定することによって、そうすることが出来ます。
@ -737,7 +765,8 @@ yii migrate/to m150101_185401_create_news_table # フルネームを使用
yii migrate/to 1392853618 # UNIX タイムスタンプを使用
```
指定されたマイグレーションよりも古いものが適用されずに残っている場合は、指定されたものが適用される前に、すべて適用されます。
指定されたマイグレーションよりも古いものが適用されずに残っている場合は、指定されたものが適用される前に、
すべて適用されます。
指定されたマイグレーションが既に適用済みである場合、それより新しいものが適用されていれば、すべて取り消されます。
@ -746,7 +775,6 @@ yii migrate/to 1392853618 # UNIX タイムスタンプ
適用済みのマイグレーションを一個または複数個取り消したい場合は、下記のコマンドを使うことが出来ます。
```
yii migrate/down # 最近に適用されたマイグレーション一個を取り消す
yii migrate/down 3 # 最近に適用されたマイグレーション三個を取り消す
@ -768,6 +796,13 @@ yii migrate/redo 3 # 最後に適用された三個のマイグレーショ
> Note: マイグレーションが取り消し不可能な場合は、それを再適用することは出来ません。
## マイグレーションをリフレッシュする <span id="refreshing-migrations"></span>
Yii 2.0.13 以降、データベースから全てのテーブルと外部キーを削除して、全てのマイグレーションを最初から再適用することが出来ます。
```
yii migrate/fresh # データベースを削除し、全てのマイグレーションを最初から適用する
```
## マイグレーションをリスト表示する <span id="listing-migrations"></span>
@ -786,7 +821,8 @@ yii migrate/new all # 適用可能な全てのマイグレーションを表
## マイグレーション履歴を修正する <span id="modifying-migration-history"></span>
時として、実際にマイグレーションを適用したり取り消したりするのではなく、データベースが特定のマイグレーションまでアップグレードされたとマークしたいだけ、という場合があります。
時として、実際にマイグレーションを適用したり取り消したりするのではなく、
データベースが特定のマイグレーションまでアップグレードされたとマークしたいだけ、という場合があります。
このようなことがよく起るのは、データベースを手作業で特定の状態に変更した後に、その変更のための一つまたは複数のマイグレーションを記録はするが再度適用はしたくない、という場合です。
次のコマンドでこの目的を達することが出来ます。
@ -805,12 +841,12 @@ yii migrate/mark 1392853618 # UNIX タイムスタンプ
マイグレーションコマンドをカスタマイズする方法がいくつかあります。
### コマンドライン・オプションを使う <span id="using-command-line-options"></span>
マイグレーション・コマンドには、その動作をカスタマイズするために使うことが出来るコマンドライン・オプションがいくつかあります。
* `interactive`: 真偽値 (デフォルト値は true)。
マイグレーションを対話モードで実行するかどうかを指定します。
* `interactive`: 真偽値 (デフォルト値は true)。マイグレーションを対話モードで実行するかどうかを指定します。
true である場合は、コマンドが何らかの操作を実行する前に、ユーザは確認を求められます。
コマンドがバックグラウンドのプロセスで使用される場合は、このオプションを false にセットします。
@ -819,13 +855,11 @@ yii migrate/mark 1392853618 # UNIX タイムスタンプ
この値は、ディレクトリ・パスか、パス・[エイリアス](concept-aliases.md) として指定することが出来ます。
ディレクトリが存在する必要があり、そうでなければコマンドがエラーを発生させることに注意してください。
* `migrationTable`: 文字列 (デフォルト値は `migration`)。
マイグレーション履歴の情報を保存するためのデータベース・テーブル名を指定します。
* `migrationTable`: 文字列 (デフォルト値は `migration`)。マイグレーション履歴の情報を保存するためのデータベース・テーブル名を指定します。
テーブルが存在しない場合は、コマンドによって自動的に作成されます。
`version varchar(255) primary key, apply_time integer` という構造のテーブルを手作業で作成しても構いません。
* `db`: 文字列 (デフォルト値は `db`)。
データベース [アプリケーション・コンポーネント](structure-application-components.md) の ID を指定します。
* `db`: 文字列 (デフォルト値は `db`)。データベース [アプリケーション・コンポーネント](structure-application-components.md) の ID を指定します。
このコマンドによってマイグレーションを適用されるデータベースを表します。
* `templateFile`: 文字列 (デフォルト値は `@yii/views/migration.php`)。
@ -839,8 +873,7 @@ yii migrate/mark 1392853618 # UNIX タイムスタンプ
'add_column' => '@yii/views/addColumnMigration.php',
'drop_column' => '@yii/views/dropColumnMigration.php',
'create_junction' => '@yii/views/createTableMigration.php'
]`)。
マイグレーション・コードを生成するためのテンプレート・ファイルを指定します。
]`)。マイグレーション・コードを生成するためのテンプレート・ファイルを指定します。
詳細は "[マイグレーションを生成する](#generating-migrations)" を参照してください。
* `fields`: マイグレーション・コードを生成するためのカラム定義文字列の配列。
@ -849,16 +882,20 @@ yii migrate/mark 1392853618 # UNIX タイムスタンプ
次の例は、これらのオプションの使い方を示すものです。
例えば、`forum` モジュールにマイグレーションを適用しようとしており、そのマイグレーション・ファイルがモジュールの `migrations` ディレクトリに配置されている場合、次のコマンドを使うことが出来ます。
例えば、`forum` モジュールにマイグレーションを適用しようとしており、
そのマイグレーション・ファイルがモジュールの `migrations` ディレクトリに配置されている場合、
次のコマンドを使うことが出来ます。
```
# forum モジュールのマイグレーションを非対話的に適用する
yii migrate --migrationPath=@app/modules/forum/migrations --interactive=0
```
### コマンドをグローバルに構成する <span id="configuring-command-globally"></span>
マイグレーション・コマンドを実行するたびに同じオプションの値を入力する代りに、次のように、アプリケーションの構成情報でコマンドを一度だけ構成して済ませることが出来ます。
マイグレーション・コマンドを実行するたびに同じオプションの値を入力する代りに、次のように、
アプリケーションの構成情報でコマンドを一度だけ構成して済ませることが出来ます。
```php
return [
@ -871,7 +908,8 @@ return [
];
```
上記のように構成しておくと、`migrate` コマンドを実行するたびに、`backend_migration` テーブルがマイグレーション履歴を記録するために使われるようになります。
上記のように構成しておくと、`migrate` コマンドを実行するたびに、
`backend_migration` テーブルがマイグレーション履歴を記録するために使われるようになります。
もう、`migrationTable` のコマンドライン・オプションを使ってテーブルを指定する必要はなくなります。
@ -879,14 +917,14 @@ return [
2.0.10 以降では、マイグレーションのクラスに名前空間を適用することが出来ます。
マイグレーションの名前空間のリストをを [[yii\console\controllers\MigrateController::migrationNamespaces|migrationNamespaces]] によって指定することが出来ます。
マイグレーションのクラスに名前空間を使うと、マイグレーションのソースについて、複数の配置場所を使用することが出来ます。
例えば、
マイグレーションのクラスに名前空間を使うと、マイグレーションのソースについて、複数の配置場所を使用することが出来ます。例えば、
```php
return [
'controllerMap' => [
'migrate' => [
'class' => 'yii\console\controllers\MigrateController',
'migrationPath' => null, // app\migrations が下記にあげられている場合に、名前空間に属さないマイグレーションを無効化する
'migrationNamespaces' => [
'app\migrations', // アプリケーション全体のための共通のマイグレーション
'module\migrations', // プロジェクトの特定のモジュールのためのマイグレーション
@ -898,25 +936,26 @@ return [
```
> Note: 異なる名前空間に属するマイグレーションを適用しても、**単一の** マイグレーション履歴が生成されます。
> つまり、特定の名前空間に属するマイグレーションだけを適用したり元に戻したりすることは出来ません。
つまり、特定の名前空間に属するマイグレーションだけを適用したり元に戻したりすることは出来ません。
名前空間を持つマイグレーションを操作するときは、新規作成時も、元に戻すときも、マイグレーション名の前にフルパスの名前空間を指定しなければなりません。
バック・スラッシュ (`\`) のシンボルは、通常、シェルでは特殊文字として扱われますので、シェルのエラーや誤った動作を防止するために、適切にエスケープしなければならないことに注意して下さい。
例えば、
バック・スラッシュ (`\`) のシンボルは、通常、シェルでは特殊文字として扱われますので、シェルのエラーや誤った動作を防止するために、
適切にエスケープしなければならないことに注意して下さい。例えば、
```
yii migrate/create 'app\\migrations\\createUserTable'
```
> Note: [[yii\console\controllers\MigrateController::migrationPath|migrationPath]] によって指定されたマイグレーションは、名前空間を持つことが出来ません。
名前空間を持つマイグレーションは [[yii\console\controllers\MigrateController::migrationNamespaces]] プロパティを通じてのみ適用可能です。
> Note: [[yii\console\controllers\MigrateController::migrationPath|migrationPath]] によって指定されたマイグレーションは、
名前空間を持つことが出来ません。 名前空間を持つマイグレーションは [[yii\console\controllers\MigrateController::migrationNamespaces]]
プロパティを通じてのみ適用可能です。
バージョン 2.0.12 以降は [[yii\console\controllers\MigrateController::migrationPath|migrationPath]] プロパティは
名前空間を持たないマイグレーションを含む複数のディレクトリを指定した配列を受け入れるようになりました。
この機能追加は、主として、いろんな場所にあるマイグレーションを使っている既存のプロジェクトによって使われることを意図しています。
これらのマイグレーションは、主として、他の開発者による Yii エクステンションなど、外部ソースに由来するものであり、
新しい手法を使い始めようとしても、名前空間を使うように変更することが簡単には出来ないものだからです。
### 分離されたマイグレーション <span id="separated-migrations"></span>
プロジェクトのマイグレーション全体に単一のマイグレーション履歴を使用することが望ましくない場合もあります。
@ -934,12 +973,14 @@ return [
'class' => 'yii\console\controllers\MigrateController',
'migrationNamespaces' => ['app\migrations'],
'migrationTable' => 'migration_app',
'migrationPath' => null,
],
// 特定のモジュールのためのマイグレーション
'migrate-module' => [
'class' => 'yii\console\controllers\MigrateController',
'migrationNamespaces' => ['module\migrations'],
'migrationTable' => 'migration_module',
'migrationPath' => null,
],
// 特定のエクステンションのためのマイグレーション
'migrate-rbac' => [
@ -996,7 +1037,8 @@ class m150101_185401_create_news_table extends Migration
同じデータベースを使う複数のマイグレーションがある場合は、上記の `init()` コードを持つ基底のマイグレーション・クラスを作成することを推奨します。
そうすれば、個々のマイグレーション・クラスは、その基底クラスから拡張することが出来ます。
> Tip: 異なるデータベースを操作するためには、[[yii\db\Migration::db|db]] プロパティを設定する以外にも、マイグレーション・クラスの中で新しいデータベース接続を作成するという方法があります。
> Tip: 異なるデータベースを操作するためには、[[yii\db\Migration::db|db]] プロパティを設定する以外にも、
マイグレーション・クラスの中で新しいデータベース接続を作成するという方法があります。
そうすれば、そのデータベース接続で [DAO メソッド](db-dao.md) を使って、違うデータベースを操作することが出来ます。
複数のデータベースに対してマイグレーションを適用するために採用できるもう一つの戦略としては、異なるデータベースに対するマイグレーションは異なるマイグレーションパスに保持する、というものがあります。
@ -1008,4 +1050,5 @@ yii migrate --migrationPath=@app/migrations/db2 --db=db2
...
```
最初のコマンドは `@app/migrations/db1` にあるマイグレーションを `db1` データベースに適用し、第二のコマンドは `@app/migrations/db2` にあるマイグレーションを `db2` データベースに適用する、という具合です。
最初のコマンドは `@app/migrations/db1` にあるマイグレーションを `db1` データベースに適用し、
第二のコマンドは `@app/migrations/db2` にあるマイグレーションを `db2` データベースに適用する、という具合です。

210
docs/guide-ja/db-query-builder.md

@ -1,7 +1,8 @@
クエリ・ビルダ
==============
[データベース・アクセス・オブジェクト](db-dao.md) の上に構築されているクエリ・ビルダは、SQL クエリをプログラム的に、かつ、DBMS の違いを意識せずに作成することを可能にしてくれます。
[データベース・アクセス・オブジェクト](db-dao.md) の上に構築されているクエリ・ビルダは、SQL クエリをプログラム的に、
かつ、DBMS の違いを意識せずに作成することを可能にしてくれます。
クエリ・ビルダを使うと、生の SQL 文を書くことに比べて、より読みやすい SQL 関連のコードを書き、より安全な SQL 文を生成することが容易になります。
通常、クエリ・ビルダの使用は、二つのステップから成ります。
@ -32,7 +33,9 @@ LIMIT 10
> Info: 通常は、[[yii\db\QueryBuilder]] ではなく、主として [[yii\db\Query]] を使用します。
前者は、クエリ・メソッドの一つを呼ぶときに、後者によって黙示的に起動されます。
[[yii\db\QueryBuilder]] は、DBMS に依存しない [[yii\db\Query]] オブジェクトから、DBMS に依存する SQL 文を生成する (例えば、テーブルやカラムの名前を DBMS ごとに違う方法で引用符で囲む) 役割を負っているクラスです。
[[yii\db\QueryBuilder]] は、DBMS に依存しない [[yii\db\Query]] オブジェクトから、DBMS に依存する SQL 文を生成する
(例えば、テーブルやカラムの名前を DBMS ごとに違う方法で引用符で囲む) 役割を負っているクラスです。
## クエリを構築する <span id="building-queries"></span>
@ -80,8 +83,7 @@ $query->select(['user_id' => 'user.id', 'email']);
すなわち、*全て* のカラムが選択されることになります。
カラム名に加えて、DB 式をセレクトすることも出来ます。
カンマを含む DB 式をセレクトする場合は、自動的に引用符で囲む機能が誤動作しないように、配列形式を使わなければなりません。
例えば、
カンマを含む DB 式をセレクトする場合は、自動的に引用符で囲む機能が誤動作しないように、配列形式を使わなければなりません。例えば、
```php
$query->select(["CONCAT(first_name, ' ', last_name) AS full_name", 'email']);
@ -91,8 +93,7 @@ $query->select(["CONCAT(first_name, ' ', last_name) AS full_name", 'email']);
[特定のデータベースに依存しない引用符の構文](db-dao.md#quoting-table-and-column-names) を使うことが出来ます。
バージョン 2.0.1 以降では、サブ・クエリもセレクトすることが出来ます。
各サブ・クエリは、[[yii\db\Query]] オブジェクトの形で指定しなければなりません。
例えば、
各サブ・クエリは、[[yii\db\Query]] オブジェクトの形で指定しなければなりません。例えば、
```php
$subQuery = (new Query())->select('COUNT(*)')->from('user');
@ -108,6 +109,13 @@ $query = (new Query())->select(['id', 'count' => $subQuery])->from('post');
$query->select('user_id')->distinct();
```
追加のカラムをセレクトするためには [[yii\db\Query::addSelect()|addSelect()]] を呼ぶことが出来ます。例えば、
```php
$query->select(['id', 'username'])
->addSelect(['email']);
```
### [[yii\db\Query::from()|from()]] <span id="from"></span>
@ -159,12 +167,10 @@ $query->from(['u' => $subQuery]);
- 演算子形式、例えば、`['like', 'name', 'test']`
- オブジェクト形式、例えば、`new LikeCondition('name', 'LIKE', 'test')`
#### 文字列形式 <span id="string-format"></span>
文字列形式は、非常に単純な条件を定義する場合や、DBMS の組み込み関数を使う必要がある場合に最適です。
これは、生の SQL を書いている場合と同じように動作します。
例えば、
これは、生の SQL を書いている場合と同じように動作します。例えば、
```php
$query->where('status=1');
@ -184,7 +190,8 @@ $query->where('YEAR(somedate) = 2015');
$query->where("status=$status");
```
`パラメータ・バインディング` を使う場合は、[[yii\db\Query::params()|params()]] または [[yii\db\Query::addParams()|addParams()]] を使って、パラメータの指定を分離することが出来ます。
`パラメータ・バインディング` を使う場合は、[[yii\db\Query::params()|params()]] または [[yii\db\Query::addParams()|addParams()]]
を使って、パラメータの指定を分離することが出来ます。
```php
$query->where('status=:status')
@ -194,7 +201,6 @@ $query->where('status=:status')
生の SQL が使われる場所ではどこでもそうですが、文字列形式で条件を書く場合には、テーブルやカラムの名前を表すために
[特定のデータベースに依存しない引用符の構文](db-dao.md#quoting-table-and-column-names) を使うことが出来ます。
#### ハッシュ形式 <span id="hash-format"></span>
値が等しいことを要求する単純な条件をいくつか `AND` で結合する場合は、ハッシュ形式を使うのが最適です。
@ -222,8 +228,7 @@ $query->where(['id' => $userQuery]);
```
ハッシュ形式を使う場合、Yii は内部的にパラメータ・バインディングを使用します。
従って、[文字列形式](#string-format) とは対照的に、ここでは手動でパラメータを追加する必要はありません。
ただし、Yii はカラム名を決してエスケープしないことに注意して下さい。
従って、[文字列形式](#string-format) とは対照的に、ここでは手動でパラメータを追加する必要はありません。ただし、Yii はカラム名を決してエスケープしないことに注意して下さい。
従って、ユーザから取得した変数を何ら追加のチェックをすることなくカラム名として渡すと、SQL インジェクション攻撃に対して脆弱になります。
アプリケーションを安全に保つためには、カラム名として変数を使わないこと、または、変数をホワイト・リストによってフィルターすることが必要です。
カラム名をユーザから取得する必要がある場合は、ガイドの [データをフィルタリングする](output-data-widgets.md#filtering-data) という記事を読んで下さい。
@ -239,8 +244,7 @@ $query->where([$column => $value]);
#### 演算子形式 <span id="operator-format"></span>
演算子形式を使うと、任意の条件をプログラム的な方法で指定することが出来ます。
これは次の形式を取るものです。
演算子形式を使うと、任意の条件をプログラム的な方法で指定することが出来ます。これは次の形式を取るものです。
```php
[演算子, オペランド1, オペランド2, ...]
@ -249,24 +253,25 @@ $query->where([$column => $value]);
ここで、各オペランドは、文字列形式、ハッシュ形式、あるいは、再帰的に演算子形式として指定することが出来ます。
そして、演算子には、次のどれか一つを使うことが出来ます。
- `and`: 複数のオペランドが `AND` を使って結合されます。例えば、`['and', 'id=1', 'id=2']` は `id=1 AND id=2` を生成します。
- `and`: 複数のオペランドが `AND` を使って結合されます。
例えば、`['and', 'id=1', 'id=2']` は `id=1 AND id=2` を生成します。
オペランドが配列である場合は、ここで説明されている規則に従って文字列に変換されます。
例えば、`['and', 'type=1', ['or', 'id=1', 'id=2']]` は `type=1 AND (id=1 OR id=2)` を生成します。
このメソッドは、文字列を引用符で囲ったりエスケープしたりしません。
- `or`: オペランドが `OR` を使って結合されること以外は `and` 演算子と同じです。
- `not`: オペランド1 だけを受け取って `NOT()` で包みます。例えば、`['not', 'id=1']` は `NOT (id=1)` を生成します。
オペランド1 は、それ自体も複数の式を表す配列であっても構いません。例えば、`['not', ['status' => 'draft', 'name' => 'example']]` は `NOT ((status='draft') AND (name='example'))` を生成します。
- `not`: オペランド1 だけを受け取って `NOT()` で包みます。例えば、`['not', 'id=1']` は `NOT (id=1)` を生成します。オペランド1 は、それ自体も複数の式を表す配列であっても構いません。例えば、`['not', ['status' => 'draft', 'name' => 'example']]` は `NOT ((status='draft') AND (name='example'))` を生成します。
- `between`: オペランド 1 はカラム名、オペランド 2 と 3 はカラムの値が属すべき範囲の開始値と終了値としなければなりません。
例えば、`['between', 'id', 1, 10]` は `id BETWEEN 1 AND 10` を生成します。
値が二つのカラムの値の間にあるという条件 (例えば、`11 BETWEEN min_id AND max_id`) を構築する必要がある場合は、
[[yii\db\conditions\BetweenColumnsCondition|BetweenColumnsCondition]] を使用しなければなりません。
条件定義のオブジェクト形式について更に学習するためには [条件 – オブジェクト形式](#object-format) のセクションを参照して下さい。
条件定義のオブジェクト形式について更に学習するためには [条件 – オブジェクト形式](#object-format)
のセクションを参照して下さい。
- `not between`: 生成される条件において `BETWEEN``NOT BETWEEN` に置き換えられる以外は、`between` と同じです。
- `not between`: 生成される条件において `BETWEEN``NOT BETWEEN` に置き換えられる以外は、
`between` と同じです。
- `in`: オペランド 1 はカラム名または DB 式でなければなりません。
オペランド 2 は、配列または `Query` オブジェクトのどちらかを取ることが出来ます。
@ -279,23 +284,30 @@ $query->where([$column => $value]);
- `not in`: 生成される条件において `IN``NOT IN` に置き換えられる以外は、`in` と同じです。
- `like`: オペランド 1 はカラム名または DB 式、オペランド 2 はカラムまたは DB 式がマッチすべき値を示す文字列または配列でなければなりません。
- `like`: オペランド 1 はカラム名または DB 式、オペランド 2 はそのカラムまたは DB 式がマッチすべき値を示す
文字列または配列でなければなりません。
例えば、`['like', 'name', 'tester']` は `name LIKE '%tester%'` を生成します。
値域が配列として与えられた場合は、複数の `LIKE` 述語が生成されて 'AND' によって結合されます。
例えば、`['like', 'name', ['test', 'sample']]` は `name LIKE '%test%' AND name LIKE '%sample%'` を生成します。
例えば、`['like', 'name', ['test', 'sample']]` は
`name LIKE '%test%' AND name LIKE '%sample%'` を生成します。
さらに、オプションである三番目のオペランドによって、値の中の特殊文字をエスケープする方法を指定することも出来ます。
このオペランド 3 は、特殊文字とそのエスケープ結果のマッピングを示す配列でなければなりません。
このオペランドが提供されない場合は、デフォルトのエスケープマッピングが使用されます。
このオペランドが提供されない場合は、デフォルトのエスケープマッピングが使用されます。
`false` または空の配列を使って、値が既にエスケープ済みであり、それ以上エスケープを適用すべきでないことを示すことが出来ます。
エスケープマッピングを使用する場合 (または第三のオペランドが与えられない場合) は、値が自動的に一組のパーセント記号によって囲まれることに注意してください。
エスケープマッピングを使用する場合 (または第三のオペランドが与えられない場合) は、
値が自動的に一対のパーセント記号によって囲まれることに注意してください。
> Note: PostgreSQL を使っている場合は、`like` の代りに、大文字と小文字を区別しない比較のための [`ilike`](http://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE) を使うことも出来ます。
> Note: PostgreSQL を使っている場合は、`like` の代りに、大文字と小文字を区別しない比較のための
> [`ilike`](http://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE) を使うことも出来ます。
- `or like`: オペランド 2 が配列である場合に `LIKE` 述語が `OR` によって結合される以外は、`like` 演算子と同じです。
- `or like`: オペランド 2 が配列である場合に `LIKE` 述語が `OR` によって結合される以外は、
`like` 演算子と同じです。
- `not like`: 生成される条件において `LIKE``NOT LIKE` に置き換えられる以外は、`like` 演算子と同じです。
- `not like`: 生成される条件において `LIKE``NOT LIKE` に置き換えられる以外は、
`like` 演算子と同じです。
- `or not like`: `NOT LIKE` 述語が `OR` によって結合される以外は、`not like` 演算子と同じです。
- `or not like`: `NOT LIKE` 述語が `OR` によって結合される以外は、
`not like` 演算子と同じです。
- `exists`: 要求される一つだけのオペランドは、サブ・クエリを表す [[yii\db\Query]] のインスタンスでなければなりません。
これは `EXISTS (sub-query)` という式を構築します。
@ -305,10 +317,9 @@ $query->where([$column => $value]);
- `>`、`<=`、その他、二つのオペランドを取る有効な DB 演算子全て: 最初のオペランドはカラム名、第二のオペランドは値でなければなりません。
例えば、`['>', 'age', 10]` は `age>10` を生成します。
演算子形式を使う場合、Yii は内部的にパラメータ・バインディングを使用します。
演算子形式を使う場合、Yii は値に対して内部的にパラメータ・バインディングを使用します。
従って、[文字列形式](#string-format) とは対照的に、ここでは手動でパラメータを追加する必要はありません。
ただし、Yii はカラム名を決してエスケープしないことに注意して下さい。
従って、ユーザから取得した変数を何ら追加のチェックをすることなくカラム名として渡すと、SQL インジェクション攻撃に対して脆弱になります。
ただし、Yii はカラム名を決してエスケープしないことに注意して下さい。従って、変数をカラム名として渡すと、アプリケーションは SQL インジェクション攻撃に対して脆弱になります。
アプリケーションを安全に保つためには、カラム名として変数を使わないこと、または、変数をホワイト・リストによってフィルターすることが必要です。
カラム名をユーザから取得する必要がある場合は、ガイドの [データをフィルタリングする](output-data-widgets.md#filtering-data) という記事を読んで下さい。
例えば、次のコードは脆弱です。
@ -324,9 +335,11 @@ $query->where([$column => $value]);
#### オブジェクト形式 <span id="object-format"></span>
オブジェクト形式は 2.0.14 から利用可能な、条件を定義するための最も強力でもあり、最も複雑でもある方法です。
クエリ・ビルダの上にあなた自身の抽象レイヤを構築したいか、または独自の複雑な条件を実装したいかする場合には、この形式を採用する必要があります。
クエリ・ビルダの上にあなた自身の抽象レイヤを構築したいときや、または独自の複雑な条件を実装したいときは、
この形式を採用する必要があります。
条件クラスのインスタンスはイミュータブルです。条件クラスのインスタンスは条件データを保持し、条件ビルダにゲッターを提供することを唯一の目的とします。
条件クラスのインスタンスはイミュータブルです。
条件クラスのインスタンスは条件データを保持し、条件ビルダにゲッターを提供することを唯一の目的とします。
そして、条件ビルダが、条件クラスのインスタンスに保存されたデータを SQL の式に変換するロジックを持つクラスです。
内部的には、上述の三つの形式は、生の SQL を構築するに先立って、暗黙のうちにオブジェクト形式に変換されます。
@ -340,8 +353,9 @@ $query->andWhere(new OrCondition([
]))
```
演算子形式からオブジェクト形式への変換は、演算子の名前とそれを表すクラス名を対応づける
[[yii\db\QueryBuilder::conditionClasses|QueryBuilder::conditionClasses]] プロパティに従って行われます。
演算子形式からオブジェクト形式への変換は、
演算子の名前とそれを表すクラス名を対応づける [[yii\db\QueryBuilder::conditionClasses|QueryBuilder::conditionClasses]]
プロパティに従って行われます。
- `AND`, `OR` -> `yii\db\conditions\ConjunctionCondition`
- `NOT` -> `yii\db\conditions\NotCondition`
@ -360,8 +374,6 @@ $query->andWhere(new OrCondition([
これらのメソッドを複数回呼んで、複数の条件を別々に追加することが出来ます。
例えば、
条件の一部を動的に構築しようとする場合は、`andWhere()` と `orWhere()` を使うのが非常に便利です。
```php
$status = 10;
$search = 'yii';
@ -379,10 +391,12 @@ if (!empty($search)) {
WHERE (`status` = 10) AND (`title` LIKE '%yii%')
```
#### フィルタ条件 <span id="filter-conditions"></span>
ユーザの入力に基づいて `WHERE` の条件を構築する場合、普通は、空の入力値は無視したいものです。
例えば、ユーザ名とメール・アドレスによる検索が可能な検索フォームにおいては、ユーザが username/email のインプット・フィールドに何も入力しなかった場合は、username/email の条件を無視したいでしょう。
例えば、ユーザ名とメール・アドレスによる検索が可能な検索フォームにおいては、
ユーザが username/email のインプット・フィールドに何も入力しなかった場合は、username/email の条件を無視したいでしょう。
[[yii\db\Query::filterWhere()|filterWhere()]] メソッドを使うことによって、この目的を達することが出来ます。
```php
@ -393,14 +407,18 @@ $query->filterWhere([
]);
```
[[yii\db\Query::filterWhere()|filterWhere()]] と [[yii\db\Query::where()|where()]] の唯一の違いは、前者は [ハッシュ形式](#hash-format) の条件において提供された空の値を無視する、という点です。
[[yii\db\Query::filterWhere()|filterWhere()]] と [[yii\db\Query::where()|where()]] の唯一の違いは、
前者は [ハッシュ形式](#hash-format) の条件において提供された空の値を無視する、という点です。
従って、`$email` が空で `$sername` がそうではない場合は、上記のコードは、結果として `WHERE username=:username` という SQL 条件になります。
> Info: 値が空であると見なされるのは、`null`、空の配列、空の文字列、または空白のみを含む文字列である場合です。
[[yii\db\Query::andWhere()|andWhere()]] または [[yii\db\Query::orWhere()|orWhere()]] と同じように、[[yii\db\Query::andFilterWhere()|andFilterWhere()]] または [[yii\db\Query::orFilterWhere()|orFilterWhere()]] を使って、既存の条件に別のフィルタ条件を追加することも出来ます。
[[yii\db\Query::andWhere()|andWhere()]] または [[yii\db\Query::orWhere()|orWhere()]] と同じように、
[[yii\db\Query::andFilterWhere()|andFilterWhere()]] または [[yii\db\Query::orFilterWhere()|orFilterWhere()]] を使って、
既存の条件に別のフィルタ条件を追加することも出来ます。
さらに加えて、値の方に含まれている比較演算子を適切に判断してくれる [[yii\db\Query::andFilterCompare()]] があります。
さらに加えて、値の方に含まれている比較演算子を適切に判断してくれる
[[yii\db\Query::andFilterCompare()]] があります。
```php
$query->andFilterCompare('name', 'John Doe');
@ -408,7 +426,7 @@ $query->andFilterCompare('rating', '>9');
$query->andFilterCompare('value', '<=100');
```
比較演算子を明示的に指定することも可能です。
演算子を明示的に指定することも可能です。
```php
$query->andFilterCompare('name', 'Doe', 'like');
@ -424,7 +442,6 @@ Yii 2.0.11 以降には、`HAVING` の条件のためにも、同様のメソッ
[[yii\db\Query::orderBy()|orderBy()]] メソッドは SQL クエリの `ORDER BY` 句を指定します。例えば、
```php
// ... ORDER BY `id` ASC, `name` DESC
$query->orderBy([
@ -453,10 +470,10 @@ $query->orderBy('id ASC')
->addOrderBy('name DESC');
```
### [[yii\db\Query::groupBy()|groupBy()]] <span id="group-by"></span>
[[yii\db\Query::groupBy()|groupBy()]] メソッドは SQL クエリの `GROUP BY` 句を指定します。
例えば、
[[yii\db\Query::groupBy()|groupBy()]] メソッドは SQL クエリの `GROUP BY` 句を指定します。例えば、
```php
// ... GROUP BY `id`, `status`
@ -484,8 +501,7 @@ $query->groupBy(['id', 'status'])
### [[yii\db\Query::having()|having()]] <span id="having"></span>
[[yii\db\Query::having()|having()]] メソッドは SQL クエリの `HAVING` 句を指定します。
このメソッドが取る条件は、[where()](#where) と同じ方法で指定することが出来ます。
例えば、
このメソッドが取る条件は、[where()](#where) と同じ方法で指定することが出来ます。例えば、
```php
// ... HAVING `status` = 1
@ -516,7 +532,8 @@ $query->limit(10)->offset(20);
無効な上限やオフセット (例えば、負の数) を指定した場合は、無視されます。
> Info: `LIMIT``OFFSET` をサポートしていない DBMS (例えば MSSQL) に対しては、クエリ・ビルダが `LIMIT`/`OFFSET` の振る舞いをエミュレートする SQL 文を生成します。
> Info: `LIMIT``OFFSET` をサポートしていない DBMS (例えば MSSQL) に対しては、
クエリ・ビルダが `LIMIT`/`OFFSET` の振る舞いをエミュレートする SQL 文を生成します。
### [[yii\db\Query::join()|join()]] <span id="join"></span>
@ -539,7 +556,7 @@ $query->join('LEFT JOIN', 'post', 'post.user_id = user.id');
配列記法ではなく文字列記法を使って、`'user.id = comment.userId'` という条件を指定しなければなりません。
- `$params`: オプション。結合条件にバインドされるパラメータ。
`INNER JOIN`、`LEFT JOIN` および `RIGHT JOIN` を指定するためには、それぞれ、次のショートカットメソッドを使うことが出来ます。
`INNER JOIN`、`LEFT JOIN` および `RIGHT JOIN` を指定するためには、それぞれ、次のショートカットメソッドを使うことが出来ます。
- [[yii\db\Query::innerJoin()|innerJoin()]]
- [[yii\db\Query::leftJoin()|leftJoin()]]
@ -554,8 +571,7 @@ $query->leftJoin('post', 'post.user_id = user.id');
複数のテーブルを結合するためには、テーブルごとに一回ずつ、上記の結合メソッドを複数回呼び出します。
テーブルを結合することに加えて、サブ・クエリを結合することも出来ます。
そうするためには、結合されるべきサブ・クエリを [[yii\db\Query]] オブジェクトとして指定します。
例えば、
そうするためには、結合されるべきサブ・クエリを [[yii\db\Query]] オブジェクトとして指定します。例えば、
```php
$subQuery = (new \yii\db\Query())->from('post');
@ -619,9 +635,8 @@ $row = (new \yii\db\Query())
> Note: [[yii\db\Query::one()|one()]] メソッドはクエリ結果の最初の行だけを返します。このメソッドは `LIMIT 1`
を生成された SQL 文に追加しません。このことは、クエリが一つまたは少数の行しか返さないことが判っている場合
(例えば、何らかのプライマリ・キーでクエリを発行する場合) は問題になりませんし、むしろ好ましいことです。
しかし、クエリ結果が多数のデータ行になる可能性がある場合は、パフォーマンスを向上させるために、
明示的に `limit(1)` を呼部べきです。例えば、
`(new \yii\db\Query())->from('user')->limit(1)->one()` のように。
しかし、クエリ結果が多数のデータ行になる可能性がある場合は、パフォーマンスを向上させるために、明示的に `limit(1)` を呼ぶべきです。例えば、
`(new \yii\db\Query())->from('user')->limit(1)->one()`
上記のメソッドの全ては、オプションで、DB クエリの実行に使用されるべき [[yii\db\Connection|DB 接続]] を表す `$db` パラメータを取ることが出来ます。
このパラメータを省略した場合は、DB 接続として `db` [アプリケーション・コンポーネント](structure-application-components.md) が使用されます。
@ -667,8 +682,7 @@ $rows = $command->queryAll();
[[yii\db\Query::all()|all()]] を呼ぶと、結果の行は連続した整数でインデックスされた配列で返されます。
場合によっては、違う方法でインデックスしたいことがあるでしょう。
例えば、特定のカラムの値や、何らかの式の値によってインデックスするなどです。
この目的は、[[yii\db\Query::all()|all()]] の前に [[yii\db\Query::indexBy()|indexBy()]] を呼ぶことによって達成することが出来ます。
例えば、
この目的は、[[yii\db\Query::all()|all()]] の前に [[yii\db\Query::indexBy()|indexBy()]] を呼ぶことによって達成することが出来ます。例えば、
```php
// [100 => ['id' => 100, 'username' => '...', ...], 101 => [...], 103 => [...], ...] を返す
@ -689,11 +703,11 @@ $query = (new \yii\db\Query())
})->all();
```
この無名関数は、現在の行データを含む `$row` というパラメータを取り、現在の行のインデックス値として使われるスカラ値を返さなくてはなりません。
この無名関数は、現在の行データを含む `$row` というパラメータを取り、
現在の行のインデックス値として使われるスカラ値を返さなくてはなりません。
> Note: [[yii\db\Query::groupBy()|groupBy()]] や [[yii\db\Query::orderBy()|orderBy()]]
> のようなクエリ・メソッドが SQL に変換されてクエリの一部となるのとは対照的に、
> このメソッドはデータベースからデータが取得された後で動作します。
> のようなクエリ・メソッドが SQL に変換されてクエリの一部となるのとは対照的に、このメソッドはデータベースからデータが取得された後で動作します。
> このことは、クエリの SELECT に含まれるカラム名だけを使うことが出来る、ということを意味します。
> また、テーブル・プレフィックスを付けてカラムを選択した場合、例えば `customer.id` を選択した場合は、
> リザルトセットのカラム名は `id` しか含みませんので、テーブルプレフィックス無しで `->indexBy('id')` と呼ぶ必要があります。
@ -703,8 +717,8 @@ $query = (new \yii\db\Query())
大量のデータを扱う場合は、[[yii\db\Query::all()]] のようなメソッドは適していません。
なぜなら、それらのメソッドは、クエリの結果全てをクライアントのメモリに読み込むことを必要とするためです。
この問題を解決するために、Yii はバッチ・クエリのサポートを提供しています。
エリ結果はサーバに保持し、クライアントはカーソルを利用して1回に1バッチずつ結果セットを反復取得するのです。
この問題を解決するために、Yii はバッチ・クエリのサポートを提供しています。クエリ結果はサーバに保持し、
クライアントはカーソルを利用して1回に1バッチずつ結果セットを反復取得するのです。
> Warning: MySQL のバッチ・クエリの実装には既知の制約と回避策があります。下記を参照して下さい。
@ -728,15 +742,16 @@ foreach ($query->each() as $user) {
}
```
[[yii\db\Query::batch()]] メソッドと [[yii\db\Query::each()]] メソッドは [[yii\db\BatchQueryResult]] オブジェクトを返します。
このオブジェクトは `Iterator` インタフェイスを実装しており、従って、`foreach` 構文の中で使うことが出来ます。
[[yii\db\Query::batch()]] メソッドと [[yii\db\Query::each()]] メソッドは [[yii\db\BatchQueryResult]]
オブジェクトを返します。このオブジェクトは `Iterator` インタフェイスを実装しており、従って、`foreach` 構文の中で使うことが出来ます。
初回の反復の際に、データベースに対する SQL クエリが作成されます。データは、その後、反復のたびにバッチ・モードで取得されます。
デフォルトでは、バッチ・サイズは 100 であり、各バッチにおいて 100 行のデータが取得されます。
バッチ・サイズは、`batch()` または `each()` メソッドに最初のパラメータを渡すことによって変更することが出来ます。
[[yii\db\Query::all()]] とは対照的に、バッチ・クエリは一度に 100 行のデータしかメモリに読み込みません。
[[yii\db\Query::indexBy()]] によって、いずれかのカラムでクエリ結果をインデックスするように指定している場合でも、バッチ・クエリは正しいインデックスを保持します。
[[yii\db\Query::indexBy()]] によって、いずれかのカラムでクエリ結果をインデックスするように指定している場合でも、
バッチ・クエリは正しいインデックスを保持します。
例えば、
@ -756,18 +771,21 @@ foreach ($query->each() as $username => $user) {
#### MySQL におけるバッチ・クエリの制約 <span id="batch-query-mysql"></span>
MySQL のバッチ・クエリの実装は PDO ドライバのライブラリに依存しています。
デフォルトでは、MySQL のクエリは [`バッファ・モード`](http://php.net/manual/ja/mysqlinfo.concepts.buffering.php) で実行されます。
このことが、カーソルを使ってデータを取得する目的を挫折させます。
というのは、バッファ・モードでは、ドライバによって結果セット全体がクライアントのメモリに読み込まれることを防止できないからです。
MySQL のバッチ・クエリの実装は PDO ドライバのライブラリに依存しています。デフォルトでは、MySQL のクエリは
[`バッファ・モード`](http://php.net/manual/ja/mysqlinfo.concepts.buffering.php) で実行されます。
このことが、カーソルを使ってデータを取得する目的を挫折させます。というのは、バッファ・モードでは、
ドライバによって結果セット全体がクライアントのメモリに読み込まれることを防止できないからです。
> Note: `libmysqlclient` が使われている場合 (PHP5 ではそれが普通ですが) は、結果セットに使用されたメモリは PHP のメモリ使用量としてカウントされません。
そのため、一見、バッチ・クエリが正しく動作するように見えますが、実際には、データ・セット全体がクライアントのメモリに読み込まれて、クライアントのメモリを使い果たす可能性があります。
> Note: `libmysqlclient` が使われている場合 (PHP5 ではそれが普通ですが) は、
結果セットに使用されたメモリは PHP のメモリ使用量としてカウントされません。そのため、一見、バッチ・クエリが正しく動作するように見えますが、
実際には、データ・セット全体がクライアントのメモリに読み込まれて、クライアントのメモリを使い果たす可能性があります。
バッファ・モードを無効化してクライアントのメモリ要求量を削減するためには、PDO 接続のプロパティ `PDO::MYSQL_ATTR_USE_BUFFERED_QUERY``false` に設定しなければなりません。
しかし、そうすると、データ・セット全体を取得するまでは、同じ接続を通じては別のクエリを実行できなくなります。
これによって `ActiveRecord` が必要に応じてテーブル・スキーマを取得するためのクエリを実行できなくなる可能性があります。
これが問題にならない場合 (テーブル・スキーマが既にキャッシュされている場合) は、元の接続を非バッファ・モードに切り替えて、バッチ・クエリを実行した後に元に戻すということが可能です。
バッファ・モードを無効化してクライアントのメモリ要求量を削減するためには、PDO 接続のプロパティ
`PDO::MYSQL_ATTR_USE_BUFFERED_QUERY``false` に設定しなければなりません。しかし、そうすると、
データ・セット全体を取得するまでは、同じ接続を通じては別のクエリを実行できなくなります。これによって
`ActiveRecord` が必要に応じてテーブル・スキーマを取得するためのクエリを実行できなくなる可能性があります。
これが問題にならない場合 (テーブル・スキーマが既にキャッシュされている場合) は、元の接続を非バッファ・モードに切り替えて、
バッチ・クエリを実行した後に元に戻すということが可能です。
```php
Yii::$app->db->pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
@ -777,8 +795,9 @@ Yii::$app->db->pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
Yii::$app->db->pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
```
> Note: MyISAM の場合は、バッチ・クエリが継続している間、テーブルがロックされて、他の接続からの書き込みアクセスが遅延または拒絶されることがあります。
非バッファ・モードのクエリを使う場合は、カーソルを開いている時間を可能な限り短くするように努めて下さい。
> Note: MyISAM の場合は、バッチ・クエリが継続している間、テーブルがロックされて、
他の接続からの書き込みアクセスが遅延または拒絶されることがあります。非バッファ・モードのクエリを使う場合は、
カーソルを開いている時間を可能な限り短くするように努めて下さい。
スキーマがキャッシュされていない場合、またはバッチ・クエリを処理している間に他のクエリを走らせる必要がある場合は、
独立した非バッファ・モードのデータベース接続を作成することが出来ます。
@ -794,10 +813,13 @@ $unbufferedDb->open();
$unbufferedDb->pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
```
`$unbufferedDb``PDO::MYSQL_ATTR_USE_BUFFERED_QUERY``false` であること以外は、元のバッファ・モードの `$db` と同じ PDO 属性を持つことを保証したい場合は、
[`$db` のディープ・コピー](https://github.com/yiisoft/yii2/issues/8420#issuecomment-301423833) をしてから、手動で false に設定することを考慮して下さい。
`$unbufferedDb``PDO::MYSQL_ATTR_USE_BUFFERED_QUERY``false` であること以外は、
元のバッファ・モードの `$db` と同じ PDO 属性を持つことを保証したい場合は、
[`$db` のディープ・コピー](https://github.com/yiisoft/yii2/issues/8420#issuecomment-301423833)
をしてから、手動で false に設定することを考慮して下さい。
そして、クエリは普通に作成します。新しい接続を使ってバッチ・クエリを走らせ、結果をバッチで取得、または一つずつ取得します。
そして、クエリは普通に作成します。新しい接続を使ってバッチ・クエリを走らせ、結果をバッチで取得、
または一つずつ取得します。
```php
// データを 1000 のバッチで取得
@ -819,8 +841,8 @@ $unbufferedDb->close();
```
> Note: 非バッファ・モードのクエリは PHP 側でのメモリ消費は少なくなりますが、MySQL サーバの負荷を増加させ得ます。
特別に巨大なデータに対するアプリの動作については、あなた自身のコードを設計することが推奨されます。
例えば、[整数のキーで範囲を分割して、非バッファ・モードのクエリでループする](https://github.com/yiisoft/yii2/issues/8420#issuecomment-296109257) など。
特別に巨大なデータに対するアプリの動作については、あなた自身のコードを設計することが推奨されます。
例えば、[整数のキーで範囲を分割して、非バッファ・モードのクエリでループする](https://github.com/yiisoft/yii2/issues/8420#issuecomment-296109257) など。
### 特製の条件や式を追加する <span id="adding-custom-conditions-and-expressions"></span>
@ -838,8 +860,7 @@ $unbufferedDb->close();
]
```
このような条件を一度に適用できたら良いですね。
一つのクエリの中で複数回使われる場合には、最適化の効果が大きいでしょう。
このような条件を一度に適用できたら良いですね。一つのクエリの中で複数回使われる場合には、最適化の効果が大きいでしょう。
特製の条件オブジェクトを作って、それを実証しましょう。
Yii には、条件を表現するクラスを特徴付ける [[yii\db\conditions\ConditionInterface|ConditionInterface]] があります。
@ -912,8 +933,7 @@ class AllGreaterConditionBuilder implements \yii\db\ExpressionBuilderInterface
```
後は、単に [[yii\db\QueryBuilder|QueryBuilder]] に私たちの新しい条件について知らせるだけです – 
条件のマッピングを `expressionBuilders` 配列に追加します。
次のように、アプリケーション構成で直接に追加することが出来ます。
条件のマッピングを `expressionBuilders` 配列に追加します。次のように、アプリケーション構成で直接に追加することが出来ます。
```php
'db' => [
@ -951,7 +971,8 @@ $query->andWhere(new AllGreaterCondition(['posts', 'comments', 'reactions', 'sub
],
```
そして、`app\db\conditions\AllGreaterCondition` の中で `AllGreaterCondition::fromArrayDefinition()` メソッドの本当の実装を作成します。
そして、`app\db\conditions\AllGreaterCondition` の中で `AllGreaterCondition::fromArrayDefinition()`
メソッドの本当の実装を作成します。
```php
namespace app\db\conditions;
@ -974,13 +995,18 @@ $query->andWhere(['ALL>', ['posts', 'comments', 'reactions', 'subscriptions'], $
```
お気付きのことと思いますが、ここには二つの概念があります。Expression(式)と Condition(条件)です。
[[yii\db\ExpressionInterface]] は、それを構築するために [[yii\db\ExpressionBuilderInterface]] を実装した式ビルダクラスを必要とするオブジェクトを特徴付けるインタフェイスです。
また [[yii\db\condition\ConditionInterface]] は、[[yii\db\ExpressionInterface|ExpressionInterface]] を拡張して、上述されたように配列形式の定義から作成できるオブジェクトに対して使用されるべきものですが、同様にビルダを必要とするものです。
[[yii\db\ExpressionInterface]] は、それを構築するために [[yii\db\ExpressionBuilderInterface]]
を実装した式ビルダクラスを必要とするオブジェクトを特徴付けるインタフェイスです。
また [[yii\db\condition\ConditionInterface]] は、[[yii\db\ExpressionInterface|ExpressionInterface]] を拡張して、
上述されたように配列形式の定義から作成できるオブジェクトに対して使用されるべきものですが、同様にビルダを必要とするものです。
要約すると、
- Expression(式) – データセットのためのデータ転送オブジェクトであり、最終的に何らかの SQL 文にコンパイルされる。(演算子、文字列、配列、JSON、等)
- Condition(条件) – Expression(式) のスーパーセットで、一つの SQL 条件にコンパイルすることが可能な複数の式(またはスカラ値)の集合。
- Expression(式) – データセットのためのデータ転送オブジェクトであり、最終的に何らかの SQL 文にコンパイルされる。
(演算子、文字列、配列、JSON、等)
- Condition(条件) – Expression(式) のスーパーセットで、一つの SQL 条件にコンパイルすることが可能な複数の式
(またはスカラ値)の集合。
[[yii\db\ExpressionInterface|ExpressionInterface]] を実装する独自のクラスを作成して、データを SQL 文に変換することの複雑さを隠蔽することが出来ます。
[[yii\db\ExpressionInterface|ExpressionInterface]] を実装する独自のクラスを作成して、
データを SQL 文に変換することの複雑さを隠蔽することが出来ます。
[次の記事](db-active-record.md) では、式について、さらに多くの例を学習します。

34
docs/guide-ja/input-file-upload.md

@ -1,16 +1,16 @@
ファイルをアップロードする
==========================
Yii におけるファイルのアップロードは、通常、アップロードされる個々のファイルを `UploadedFile` としてカプセル化する [[yii\web\UploadedFile]] の助けを借りて実行されます。
これを [[yii\widgets\ActiveForm]] および [モデル](structure-models.md) と組み合わせることで、安全なファイル・アップロード・メカニズムを簡単に実装することが出来ます。
Yii におけるファイルのアップロードは、通常、アップロードされる個々のファイルを `UploadedFile` としてカプセル化する
[[yii\web\UploadedFile]] の助けを借りて実行されます。これを [[yii\widgets\ActiveForm]] および [モデル](structure-models.md)
と組み合わせることで、安全なファイル・アップロード・メカニズムを簡単に実装することが出来ます。
## モデルを作成する <span id="creating-models"></span>
プレーンなテキスト・インプットを扱うのと同じように、一つのファイルをアップロードするためには、モデル・クラスを作成して、そのモデルの一つの属性を使ってアップロードされるファイルのインスタンスを保持します。
また、ファイルのアップロードを検証するために、検証規則も宣言しなければなりません。
例えば、
プレーンなテキスト・インプットを扱うのと同じように、一つのファイルをアップロードするためには、モデル・クラスを作成して、
そのモデルの一つの属性を使ってアップロードされるファイルのインスタンスを保持します。
また、ファイルのアップロードを検証するために、検証規則も宣言しなければなりません。例えば、
```php
namespace app\models;
@ -52,11 +52,9 @@ class UploadForm extends Model
詳細については、[コア・バリデータ](tutorial-core-validators.md#file) のセクションを参照してください。
> Tip: 画像をアップロードしようとする場合は、`image` バリデータを代りに使うことを考慮しても構いません。
`image` バリデータは [[yii\validators\ImageValidator]] によって実装されており、属性が有効な画像、すなわち、保存したり [Imagine エクステンション](https://github.com/yiisoft/yii2-imagine) を使って処理したりすることが可能な有効な画像を、受け取ったかどうかを検証します。
`image` バリデータは [[yii\validators\ImageValidator]] によって実装されており、属性が有効な画像、すなわち、
保存したり [Imagine エクステンション](https://github.com/yiisoft/yii2-imagine) を使って処理したりすることが可能な有効な画像を、受け取ったかどうかを検証します。
上記のコードにおいて作成した `UploadForm` というモデルは、HTML フォームで `<input type="file">` となる `$file` という属性を持ちます。
この属性は [[yii\validators\FileValidator|FileValidator]] を使用する `file` という検証規則を持ちます。
## ファイル・インプットをレンダリングする <span id="rendering-file-input"></span>
@ -79,8 +77,8 @@ use yii\widgets\ActiveForm;
ファイルが正しくアップロードされるように、フォームに `enctype` オプションを追加することを憶えておくのは重要なことです。
`fileInput()` を呼ぶと `<input type="file">` のタグがレンダリングされて、ユーザがアップロードするファイルを選ぶことが出来るようになります。
> Tip: バージョン 2.0.8 以降では、ファイル・インプットのフィールドが使われているときは、[[yii\widgets\ActiveField::fileInput|fileInput]] がフォームに `enctype` オプションを自動的に追加します。
> Tip: バージョン 2.0.8 以降では、ファイル・インプットのフィールドが使われているときは、
[[yii\widgets\ActiveField::fileInput|fileInput]] がフォームに `enctype` オプションを自動的に追加します。
## 繋ぎ合せる <span id="wiring-up"></span>
@ -113,7 +111,8 @@ class SiteController extends Controller
}
```
上記のコードでは、フォームが送信されると [[yii\web\UploadedFile::getInstance()]] メソッドが呼ばれて、アップロードされたファイルが `UploadedFile` のインスタンスとして表現されます。
上記のコードでは、フォームが送信されると [[yii\web\UploadedFile::getInstance()]] メソッドが呼ばれて、
アップロードされたファイルが `UploadedFile` のインスタンスとして表現されます。
そして、次に、モデルの検証によってアップロードされたファイルが有効なものであることを確かめ、サーバにファイルを保存します。
@ -123,7 +122,8 @@ class SiteController extends Controller
最初に、モデル・クラスを修正して、`file` 検証規則に `maxFiles` オプションを追加して、アップロードを許可されるファイルの最大数を制限しなければなりません。
`maxFiles``0` に設定することは、同時にアップロード出来るファイル数に制限がないことを意味します。
同時にアップロードすることを許されるファイルの数は、また、PHP のディレクティブ [`max_file_uploads`](http://php.net/manual/ja/ini.core.php#ini.max-file-uploads) (デフォルト値は 20) によっても制限されます。
同時にアップロードすることを許されるファイルの数は、また、PHP のディレクティブ
[`max_file_uploads`](http://php.net/manual/ja/ini.core.php#ini.max-file-uploads) (デフォルト値は 20) によっても制限されます。
`upload()` メソッドも、アップロードされた複数のファイルを一つずつ保存するように修正しなければなりません。
```php
@ -160,7 +160,8 @@ class UploadForm extends Model
}
```
ビュー・ファイルでは、`fileInput()` の呼び出しに `multiple` オプションを追加して、ファイル・アップロードのフィールドが複数のファイルを受け取ることが出来るようにしなければなりません。
ビュー・ファイルでは、`fileInput()` の呼び出しに `multiple` オプションを追加して、
ファイル・アップロードのフィールドが複数のファイルを受け取ることが出来るようにしなければなりません。
```php
<?php
@ -176,7 +177,8 @@ use yii\widgets\ActiveForm;
<?php ActiveForm::end() ?>
```
そして、最後に、コントローラ・アクションの中では、`UploadedFile::getInstance()` の代りに `UploadedFile::getInstances()` を呼んで、`UploadedFile` インスタンスの配列を `UploadForm::imageFiles` に代入しなければなりません。
そして、最後に、コントローラ・アクションの中では、`UploadedFile::getInstance()` の代りに `UploadedFile::getInstances()` を呼んで、
`UploadedFile` インスタンスの配列を `UploadForm::imageFiles` に代入しなければなりません。
```php
namespace app\controllers;

29
docs/guide-ja/input-form-javascript.md

@ -34,8 +34,10 @@ function (event, messages, deferreds)
引数は以下の通り:
- `event`: イベントのオブジェクト。
- `messages`: 連想配列で、キーは属性の ID、値は対応する属性のエラー・メッセージの配列です。
- `deferreds`: Deferred オブジェクトの配列。`deferreds.add(callback)` を使って、新しい deferrd な検証を追加することが出来ます。
- `messages`: 連想配列で、キーは属性の ID、
値は対応する属性のエラー・メッセージの配列です。
- `deferreds`: Deferred オブジェクトの配列。`deferreds.add(callback)` を使って、
新しい deferrd な検証を追加することが出来ます。
ハンドラが真偽値 `false` を返すと、このイベントに続くフォームの検証は中止されます。
その結果、`afterValidate` イベントもトリガされません。
@ -53,13 +55,14 @@ function (event, messages, errorAttributes)
引数は以下の通り:
- `event`: イベントのオブジェクト。
- `messages`: 連想配列で、キーは属性の ID、値は対応する属性のエラー・メッセージの配列です。
- `errorAttributes`: 検証エラーがある属性の配列。この引数の構造については `attributeDefaults` を参照して下さい。
- `messages`: 連想配列で、キーは属性の ID、
値は対応する属性のエラー・メッセージの配列です。
- `errorAttributes`: 検証エラーがある属性の配列。
この引数の構造については `attributeDefaults` を参照して下さい。
### `beforeValidateAttribute`
`beforeValidateAttribute` イベントは、属性を検証する前にトリガされます。
イベント・ハンドラのシグニチャは以下の通り:
```javascript
@ -69,9 +72,11 @@ function (event, attribute, messages, deferreds)
引数は以下の通り:
- `event`: イベントのオブジェクト。
- `attribute`: 検証される属性。この引数の構造については `attributeDefaults` を参照して下さい。
- `attribute`: 検証される属性。
この引数の構造については `attributeDefaults` を参照して下さい。
- `messages`: 指定された属性に対する検証エラー・メッセージを追加することが出来る配列。
- `deferreds`: Deferred オブジェクトの配列。`deferreds.add(callback)` を使って、新しい deferrd な検証を追加することが出来ます。
- `deferreds`: Deferred オブジェクトの配列。
`deferreds.add(callback)` を使って、新しい deferrd な検証を追加することが出来ます。
ハンドラが真偽値 `false` を返すと、指定された属性の検証は中止されます。
その結果、`afterValidateAttribute` イベントもトリガされません。
@ -89,8 +94,10 @@ function (event, attribute, messages)
引数は以下の通り:
- `event`: イベントのオブジェクト。
- `attribute`: 検証される属性。この引数の構造については `attributeDefaults` を参照して下さい。
- `messages`: 指定された属性に対する追加の検証エラー・メッセージを追加することが出来る配列。
- `attribute`: 検証される属性。
この引数の構造については `attributeDefaults` を参照して下さい。
- `messages`: 指定された属性に対する追加の検証エラー・メッセージを追加することが
出来る配列。
### `beforeSubmit`
@ -102,9 +109,7 @@ function (event, attribute, messages)
function (event)
```
引数は以下の通り:
- `event`: イベントのオブジェクト。
ここで、`event` は、イベントのオブジェクトです。
ハンドラが真偽値 `false` を返すと、フォームの送信は中止されます。

41
docs/guide-ja/input-forms.md

@ -10,7 +10,8 @@ Yii においてフォームを使用するときは、主として [[yii\widget
フォームは、クライアント・サイドで表示されるものですが、たいていの場合、対応する [モデル](structure-models.md) を持ち、それを使ってサーバ・サイドでフォームの入力を検証します
(入力の検証の詳細については、[入力を検証する](input-validation.md) のセクションを参照してください)。
モデルに基づくフォームを作成する場合、最初のステップは、モデルそのものを定義することです。
モデルは、データベースの何らかのデータを表現するために [アクティブ・レコード](db-active-record.md) から派生させたクラスか、あるいは、任意の入力、例えばログイン・フォームの入力を保持するための ([[yii\base\Model]] から派生させた) 汎用的な Model クラスか、どちらかにすることが出来ます。
モデルは、データベースの何らかのデータを表現するために [アクティブ・レコード](db-active-record.md) から派生させたクラスか、
あるいは、任意の入力、例えばログイン・フォームの入力を保持するための ([[yii\base\Model]] から派生させた) 汎用的な Model クラスか、どちらかにすることが出来ます。
> Tip: フォームのフィールドがデータベースのカラムと異なっていたり、そのフォーム特有のフォーマット形式やロジックがあったりする場合は、
> [[yii\base\Model]] を拡張した独自のモデルを作るほうを選んで下さい。
@ -34,7 +35,8 @@ class LoginForm extends \yii\base\Model
```
コントローラにおいて、このモデルのインスタンスをビューに渡し、ビューでは [[yii\widgets\ActiveForm|ActiveForm]] ウィジェットがフォームを表示するのに使われます。
コントローラにおいて、このモデルのインスタンスをビューに渡し、ビューでは [[yii\widgets\ActiveForm|ActiveForm]]
ウィジェットがフォームを表示するのに使われます。
```php
<?php
@ -58,16 +60,16 @@ $form = ActiveForm::begin([
### `begin()``end()` で囲む <span id="wrapping-with-begin-and-end"></span>
上記のコードでは、[[yii\widgets\ActiveForm::begin()|ActiveForm::begin()]] がフォームのインスタンスを作成するとともに、フォームの開始をマークしています。
[[yii\widgets\ActiveForm::begin()|ActiveForm::begin()]] と [[yii\widgets\ActiveForm::end()|ActiveForm::end()]] の間に置かれた全てのコンテントが HTML の `<form>` タグによって囲まれます。
[[yii\widgets\ActiveForm::begin()|ActiveForm::begin()]] と [[yii\widgets\ActiveForm::end()|ActiveForm::end()]]
の間に置かれた全てのコンテントが HTML の `<form>` タグによって囲まれます。
どのウィジェットでも同じですが、ウィジェットをどのように構成すべきかに関するオプションを指定するために、`begin` メソッドに配列を渡すことが出来ます。
この例では、追加の CSS クラスと要素を特定するための ID が渡されて、`<form>` の開始タグに適用されています。
利用できるオプションの全ては [[yii\widgets\ActiveForm]] の API ドキュメントに記されていますので参照してください。
### ActiveField <span id="activefield"></span>
フォームの中では、フォームの要素を作成するために、ActiveForm ウィジェットの [[yii\widgets\ActiveForm::field()|ActiveForm::field()]] メソッドが呼ばれています。
このメソッドは、フォームの要素だけでなく、そのラベルも作成し、適用できる JavaScript の検証メソッドがあれば、それも追加します。
[[yii\widgets\ActiveForm::field()|ActiveForm::field()]] メソッドは、[[yii\widgets\ActiveField]] のインスタンスを返します。
このメソッドの呼び出し結果を直接にエコーすると、結果は通常の (text の) インプットになります。
このメソッドは、フォームの要素だけでなく、そのラベルも作成し、適用できる JavaScript の検証メソッドがあれば、それも追加します。[[yii\widgets\ActiveForm::field()|ActiveForm::field()]] メソッドは、
[[yii\widgets\ActiveField]] のインスタンスを返します。このメソッドの呼び出し結果を直接にエコーすると、結果は通常の (text の) インプットになります。
このメソッドの呼び出しに追加の [[yii\widgets\ActiveField|ActiveField]] のメソッドをチェーンして、出力結果をカスタマイズすることが出来ます。
```php
@ -89,7 +91,8 @@ $form = ActiveForm::begin([
> この方法を使えば、[GridView](output-data-widgets.md#grid-view) で使われるフィルター・モデルで、もっと見栄えの良い URL を生成させることが出来ます。
モデルの属性を指定するために、もっと洗練された方法を使うことも出来ます。
例えば、複数のファイルをアップロードしたり、複数の項目を選択したりする場合に、属性の名前に `[]` を付けて、属性が配列の値を取り得ることを指定することが出来ます。
例えば、複数のファイルをアップロードしたり、複数の項目を選択したりする場合に、属性の名前に `[]` を付けて、
属性が配列の値を取り得ることを指定することが出来ます。
```php
// 複数のファイルのアップロードを許可する
@ -106,11 +109,15 @@ echo $form->field($model, 'items[]')->checkboxList(['a' => 'Item A', 'b' => 'Ite
> 名前の衝突は訳の分らない失敗を生じさせることがあります。
> 命名規則の完全なリストを知り、この問題についてあなたのマークアップをチェックするためには、[DOMLint](http://kangax.github.io/domlint/) を参照してください。
フォームに HTML タグを追加するためには、素の HTML を使うか、または、上記の例の [[yii\helpers\Html::submitButton()|Html::submitButton()]] のように、[[yii\helpers\Html|Html]] ヘルパ・クラスのメソッドを使うことが出来ます。
フォームに HTML タグを追加するためには、素の HTML を使うか、または、上記の例の [[yii\helpers\Html::submitButton()|Html::submitButton()]] のように、
[[yii\helpers\Html|Html]] ヘルパ・クラスのメソッドを使うことが出来ます。
> Tip: あなたのアプリケーションで Twitter Bootstrap CSS を使っている場合は、[[yii\widgets\ActiveForm]] の代りに [[yii\bootstrap\ActiveForm]] を使うのが良いでしょう。
> Tip: あなたのアプリケーションで Twitter Bootstrap CSS を使っている場合は、[[yii\widgets\ActiveForm]] の代りに
> [[yii\bootstrap\ActiveForm]] を使うのが良いでしょう。
> 後者は前者の拡張であり、bootstrap CSS フレームワークで使用するための追加のスタイルをサポートしています。
> Tip: 必須フィールドをアスタリスク付きのスタイルにするために、次の CSS を使うことが出来ます。
>
> ```css
@ -147,7 +154,8 @@ $items = Category::find()
```
このような `$items` が、いろんなリスト・ウィジェットによって処理されるべきものとなります。
フォームのフィールドの値(および現在アクティブな項目)は、`$model` の属性の現在の値に従って自動的に設定されます。
フォームのフィールドの値(および現在アクティブな項目)は、
`$model` の属性の現在の値に従って自動的に設定されます。
#### ドロップダウン・リストを作る <span id="creating-activeform-dropdownlist"></span>
@ -194,11 +202,13 @@ echo $form->field($model, 'category')->checkboxList([
Pjax を使う <span id="working-with-pjax"></span>
-----------
[[yii\widgets\Pjax|Pjax]] ウィジェットを使うと、ページ全体をリロードせずに、ページの一部分だけを更新することが出来ます。
[[yii\widgets\Pjax|Pjax]] ウィジェットを使うと、ページ全体をリロードせずに、
ページの一部分だけを更新することが出来ます。
これを使うと、送信後にフォームだけを更新して、その中身を入れ替えることが出来ます。
[[yii\widgets\Pjax::$formSelector|$formSelector]] を構成すると、どのフォームの送信が pjax を起動するかを指定することが出来ます。
それが指定されていない場合は、Pjax に囲まれたコンテントの中にあって `data-pjax` 属性を持つすべてのフォームが pjax リクエストを起動することになります。
それが指定されていない場合は、Pjax に囲まれたコンテントの中にあって
`data-pjax` 属性を持つすべてのフォームが pjax リクエストを起動することになります。
```php
use yii\widgets\Pjax;
@ -229,9 +239,10 @@ Pjax::end();
を扱うときに問題があることが知られています。
この問題は解決される見込みがなく、関数自体も HTML5 で導入された `FormData` クラスによって置き換えられるべきものとして、廃止予定となっています。
このことは、すなわち、ajax または [[yii\widgets\Pjax|Pjax]] ウィジェットを使う場合、ファイルと送信ボタンの値に対する唯一の公式なサポートは、
`FormData` クラスに対する [ブラウザのサポート](https://developer.mozilla.org/en-US/docs/Web/API/FormData#Browser_compatibility) に依存しているということを意味します。
このことは、すなわち、ajax または [[yii\widgets\Pjax|Pjax]] ウィジェットを使う場合、
ファイルと送信ボタンの値に対する唯一の公式なサポートは、
`FormData` クラスに対する [ブラウザのサポート](https://developer.mozilla.org/en-US/docs/Web/API/FormData#Browser_compatibility)
に依存しているということを意味します。
さらに読むべき文書 <span id="further-reading"></span>
------------------

7
docs/guide-ja/input-multiple-models.md

@ -2,10 +2,13 @@
==============================
複雑なデータを扱う場合には、複数の異なるモデルを使用してユーザの入力を収集する必要があることがあり得ます。
例えば、ユーザのログイン情報は `user` テーブルに保存されているけれども、ユーザのプロファイル情報は `profile` テーブルに保存されているという場合を考えて見ると、ユーザに関して入力されたデータを `User` モデルと `Profile` モデルによって収集しなければならないでしょう。
例えば、ユーザのログイン情報は `user` テーブルに保存されているけれども、ユーザのプロファイル情報は
`profile` テーブルに保存されているという場合を考えて見ると、ユーザに関して入力されたデータを `User` モデルと
`Profile` モデルによって収集しなければならないでしょう。
Yii のモデルとフォームのサポートを使えば、単一のモデルを扱うのとそれほど違いのない方法によってこの問題を解決することが出来ます。
下記において、`User` と `Profile` の二つのモデルのデータを収集することが出来るフォームをどのようにして作成することが出来るかを示します。
下記において、`User` と `Profile` の二つのモデルのデータを収集することが出来るフォームをどのようにして作成することが
出来るかを示します。
最初に、ユーザとプロファイルのデータを収集するためのコントローラ・アクションは、次のように書くことが出来ます。

16
docs/guide-ja/input-tabular-input.md

@ -2,7 +2,8 @@
==================================
時として、一つのフォームで同じ種類の複数のモデルを扱わなければならないことがあります。
例えば、それぞれが「名前-値」の形で保存され、`Setting` [アクティブ・レコード](db-active-record.md) モデルとして表される複数の設定項目を扱うフォームです。
例えば、それぞれが「名前-値」の形で保存され、`Setting` [アクティブ・レコード](db-active-record.md)
モデルとして表される複数の設定項目を扱うフォームです。
この種のフォームは「表形式インプット」と呼ばれることもよくあります。
これとは対照的な、異なる種類のさまざまなモデルを扱うことについては、[複数のモデルを持つ複雑なフォーム](input-multiple-models.md) のセクションで扱います。
@ -54,10 +55,12 @@ class SettingsController extends Controller
}
```
上記のコードでは、データベースからモデルを読み出すときに [[yii\db\ActiveQuery::indexBy()|indexBy()]] を使って、モデルのプライマリ・キーでインデックスされた配列にデータを投入しています。
このインデックスが、後で、フォーム・フィールドを特定するために使われます。
[[yii\base\Model::loadMultiple()|Model::loadMultiple()]] が POST から来るフォーム・データを複数のモデルに代入し、[[yii\base\Model::validateMultiple()|Model::validateMultiple()]] が全てのモデルを一度に検証します。
保存するときには、`validateMultiple()` を使ってモデルの検証を済ませていますので、[[yii\db\ActiveRecord::save()|save()]] のパラメータに `false` を渡して、二度目の検証を実行しないようにしています。
上記のコードでは、データベースからモデルを読み出すときに [[yii\db\ActiveQuery::indexBy()|indexBy()]] を使って、
モデルのプライマリ・キーでインデックスされた配列にデータを投入しています。このインデックスが、後で、
フォーム・フィールドを特定するために使われます。[[yii\base\Model::loadMultiple()|Model::loadMultiple()]] が
POST から来るフォーム・データを複数のモデルに代入し、[[yii\base\Model::validateMultiple()|Model::validateMultiple()]] が全てのモデルを一度に検証します。
保存するときには、`validateMultiple()` を使ってモデルの検証を済ませていますので、[[yii\db\ActiveRecord::save()|save()]]
のパラメータに `false` を渡して、二度目の検証を実行しないようにしています。
次に、`update` ビューの中にあるフォームです。
@ -76,8 +79,7 @@ ActiveForm::end();
```
ここで全ての設定項目について、それぞれ、項目名を示すラベルと、項目の値を入れたインプットをレンダリングしています。
インプットの名前に適切なインデックスを追加することが肝腎です。
というのは、`loadMultiple` がそれを見て、どのモデルにどの値を代入するかを決定するからです。
インプットの名前に適切なインデックスを追加することが肝腎です。というのは、`loadMultiple` がそれを見て、どのモデルにどの値を代入するかを決定するからです。
### 不特定の数の新しいレコードを動的に作成する

164
docs/guide-ja/input-validation.md

@ -1,12 +1,12 @@
入力を検証する
==============
経験則として言えることは、エンド・ユーザから受信したデータは決して信用せず、利用する前に検証しなければならない、ということです。
経験則として言えることは、エンド・ユーザから受信したデータは決して信用せず、
利用する前に検証しなければならない、ということです。
[モデル](structure-models.md) にユーザの入力が投入されたら、モデルの [[yii\base\Model::validate()]] メソッドを呼んで入力を検証することが出来ます。
このメソッドは検証が成功したか否かを示す真偽値を返します。
検証が失敗した場合は、[[yii\base\Model::errors]] プロパティからエラー・メッセージを取得することが出来ます。
例えば、
検証が失敗した場合は、[[yii\base\Model::errors]] プロパティからエラー・メッセージを取得することが出来ます。例えば、
```php
$model = new \app\models\ContactForm();
@ -24,6 +24,7 @@ if ($model->validate()) {
}
```
## 規則を宣言する <span id="declaring-rules"></span>
`validate()` を現実に動作させるためには、検証する予定の属性に対して検証規則を宣言しなければなりません。
@ -43,12 +44,14 @@ public function rules()
}
```
[[yii\base\Model::rules()|rules()]] メソッドは規則の配列を返すべきものですが、その配列の各要素は次の形式の配列でなければなりません。
[[yii\base\Model::rules()|rules()]] メソッドは規則の配列を返すべきものですが、
その配列の各要素は次の形式の配列でなければなりません。
```php
[
// 必須。この規則によって検証されるべき属性を指定する。
// 属性が一つだけの場合は、配列の中に入れずに、属性の名前を直接に書いてもよい。
// 属性が一つだけの場合は、属性の名前を直接に書いてもよい。
// (配列の中に入れずに)
['属性1', '属性2', ...],
// 必須。この規則のタイプを指定する。
@ -71,11 +74,12 @@ public function rules()
* コア・バリデータのエイリアス。例えば、`required`、`in`、`date`、等々。
コア・バリデータの完全なリストは [コア・バリデータ](tutorial-core-validators.md) を参照してください。
* モデル・クラス内のバリデーション・メソッドの名前、または無名関数。詳細は、[インライン・バリデータ](#inline-validators) の項を参照してください。
* 完全修飾のバリデータ・クラス名。詳細は [スタンドアロン・バリデータ](#standalone-validators) の項を参照してください。
* モデル・クラス内のバリデーション・メソッドの名前、または無名関数。
詳細は、[インライン・バリデータ](#inline-validators) の項を参照してください。
* 完全修飾のバリデータ・クラス名。詳細は [スタンドアロン・バリデータ](#standalone-validators)
の項を参照してください。
一つの規則は、一つまたは複数の属性を検証するために使用することが出来ます。
そして、一つの属性は、一つまたは複数の規則によって検証され得ます。
一つの規則は、一つまたは複数の属性を検証するために使用することが出来ます。そして、一つの属性は、一つまたは複数の規則によって検証され得ます。
`on` オプションを指定することで、規則を特定の [シナリオ](structure-models.md#scenarios) においてのみ適用することが出来ます。
`on` オプションを指定しない場合は、規則が全てのシナリオに適用されることになります。
@ -88,7 +92,9 @@ public function rules()
3. 全てのアクティブな規則を一つずつ使って、その規則に関連付けられた全てのアクティブな属性を一つずつ検証します。
検証規則はリストに挙げられている順に評価されます。
属性は、上記の検証のステップに従って、`scenarios()` でアクティブな属性であると宣言されており、かつ、`rules()` で宣言された一つまたは複数のアクティブな規則と関連付けられている場合に、また、その場合に限って、検証されます。
属性は、上記の検証のステップに従って、`scenarios()` でアクティブな属性であると宣言されており、
かつ、`rules()` で宣言された一つまたは複数のアクティブな規則と関連付けられている場合に、
また、その場合に限って、検証されます。
> Note: 規則に名前を付けると便利です。すなわち、
>
@ -116,9 +122,11 @@ public function rules()
### エラー・メッセージをカスタマイズする <span id="customizing-error-messages"></span>
たいていのバリデータはデフォルトのエラー・メッセージを持っていて、属性の検証が失敗した場合にそれを検証の対象であるモデルに追加します。
例えば、[[yii\validators\RequiredValidator|required]] バリデータは、このバリデータを使って `username` 属性を検証したとき、規則に合致しない場合は「ユーザ名は空ではいけません。」というエラー・メッセージをモデルに追加します。
例えば、[[yii\validators\RequiredValidator|required]] バリデータは、このバリデータを使って `username` 属性を検証したとき、
規則に合致しない場合は「ユーザ名は空ではいけません。」というエラー・メッセージをモデルに追加します。
規則のエラー・メッセージは、次に示すように、規則を宣言するときに `message` プロパティを指定することによってカスタマイズすることが出来ます。
規則のエラー・メッセージは、次に示すように、規則を宣言するときに `message`
プロパティを指定することによってカスタマイズすることが出来ます。
```php
public function rules()
@ -130,24 +138,30 @@ public function rules()
```
バリデータの中には、検証を失敗させたさまざまな原因をより詳しく説明するための追加のエラー・メッセージをサポートしているものがあります。
例えば、[[yii\validators\NumberValidator|number]] バリデータは、検証される値が大きすぎたり小さすぎたりしたときに、検証の失敗を説明するために、それぞれ、[[yii\validators\NumberValidator::tooBig|tooBig]] および [[yii\validators\NumberValidator::tooSmall|tooSmall]] のメッセージをサポートしています。
例えば、[[yii\validators\NumberValidator|number]] バリデータは、検証される値が大きすぎたり小さすぎたりしたときに、
検証の失敗を説明するために、それぞれ、[[yii\validators\NumberValidator::tooBig|tooBig]] および
[[yii\validators\NumberValidator::tooSmall|tooSmall]] のメッセージをサポートしています。
これらのエラー・メッセージも、バリデータの他のプロパティと同様、検証規則の中で構成することが出来ます。
### 検証のイベント <span id="validation-events"></span>
[[yii\base\Model::validate()]] は、呼び出されると、検証のプロセスをカスタマイズするためにオーバーライドできる二つのメソッドを呼び出します。
[[yii\base\Model::validate()]] は、呼び出されると、
検証のプロセスをカスタマイズするためにオーバーライドできる二つのメソッドを呼び出します。
* [[yii\base\Model::beforeValidate()]]: デフォルトの実装は [[yii\base\Model::EVENT_BEFORE_VALIDATE]] イベントをトリガするものです。
このメソッドをオーバーライドするか、または、イベントに反応して、検証が実行される前に、何らかの前処理 (例えば入力されたデータの正規化) をすることが出来ます。
このメソッドをオーバーライドするか、または、イベントに反応して、検証が実行される前に、
何らかの前処理 (例えば入力されたデータの正規化) をすることが出来ます。
このメソッドは、検証を続行すべきか否かを示す真偽値を返さなくてはなりません。
* [[yii\base\Model::afterValidate()]]: デフォルトの実装は [[yii\base\Model::EVENT_AFTER_VALIDATE]] イベントをトリガするものです。
このメソッドをオーバーライドするか、または、イベントに反応して、検証が完了した後に、何らかの後処理をすることが出来ます。
このメソッドをオーバーライドするか、または、イベントに反応して、検証が完了した後に、
何らかの後処理をすることが出来ます。
### 条件付きの検証 <span id="conditional-validation"></span>
特定の条件が満たされる場合に限って属性を検証したい場合、例えば、ある属性の検証が他の属性の値に依存する場合には、[[yii\validators\Validator::when|when]] プロパティを使って、そのような条件を定義することが出来ます。
特定の条件が満たされる場合に限って属性を検証したい場合、例えば、ある属性の検証が他の属性の値に依存する場合には、
[[yii\validators\Validator::when|when]] プロパティを使って、そのような条件を定義することが出来ます。
例えば、
```php
@ -183,10 +197,10 @@ function ($model, $attribute)
### データのフィルタリング <span id="data-filtering"></span>
ユーザ入力をフィルタまたは前処理する必要があることがよくあります。
例えば、`username` の入力値の前後にある空白を除去したいというような場合です。
この目的を達するために検証規則を使うことが出来ます。
例えば、`username` の入力値の前後にある空白を除去したいというような場合です。この目的を達するために検証規則を使うことが出来ます。
次の例では、入力値の前後にある空白を除去して、空の入力値を null に変換することを、[trim](tutorial-core-validators.md#trim) および [default](tutorial-core-validators.md#default) のコア・バリデータで行っています。
次の例では、入力値の前後にある空白を除去して、空の入力値を null に変換することを、
[trim](tutorial-core-validators.md#trim) および [default](tutorial-core-validators.md#default) のコア・バリデータで行っています。
```php
return [
@ -195,9 +209,11 @@ return [
];
```
もっと汎用的な [filter](tutorial-core-validators.md#filter) バリデータを使って、もっと複雑なデータ・フィルタリングをすることも出来ます。
もっと汎用的な [filter](tutorial-core-validators.md#filter) バリデータを使って、
もっと複雑なデータ・フィルタリングをすることも出来ます。
お分かりのように、これらの検証規則は実際には入力を検証しません。そうではなくて、検証される属性の値を処理して書き戻すのです。
お分かりのように、これらの検証規則は実際には入力を検証しません。
そうではなくて、検証される属性の値を処理して書き戻すのです。
ユーザ入力の完全な処理を次のサンプル・コードで示します。
これは、ある属性に整数の値だけが保存されるように保証しようとするものです。
@ -213,8 +229,7 @@ return [
1. 入力値から先頭と末尾のホワイト・スペースをトリムします。
2. 空の入力値がデータベースで `null` として保存されることを保証します。
"not set(未設定)" という値と、実際の値である `0` は区別します。
`null` が許されない時は、ここで別のデフォルト値を設定することが出来ます。
"not set(未設定)" という値と、実際の値である `0` は区別します。`null` が許されない時は、ここで別のデフォルト値を設定することが出来ます。
3. 空でない場合は、値は 0 以上の整数であることを検証します。
通常のバリデータでは [[yii\validators\Validator::$skipOnEmpty|$skipOnEmpty]] が `true` に設定されています。
4. 例えば、文字列 `'42'` は、整数 `42` にキャストして、値が整数型になることを保証します。
@ -224,8 +239,7 @@ return [
### 空の入力値を扱う <span id="handling-empty-inputs"></span>
HTML フォームから入力データが送信されたとき、入力値が空である場合には何らかのデフォルト値を割り当てなければならないことがよくあります。
[default](tutorial-core-validators.md#default) バリデータを使ってそうすることが出来ます。
例えば、
[default](tutorial-core-validators.md#default) バリデータを使ってそうすることが出来ます。例えば、
```php
return [
@ -238,27 +252,27 @@ return [
```
デフォルトでは、入力値が空であると見なされるのは、それが、空文字列であるか、空配列であるか、null であるときです。
空を検知するこのデフォルトのロジックは、[[yii\validators\Validator::isEmpty]] プロパティを PHP コーラブルで構成することによって、カスタマイズすることが出来ます。
例えば、
空を検知するこのデフォルトのロジックは、[[yii\validators\Validator::isEmpty]] プロパティを PHP コーラブルで構成することによって、
カスタマイズすることが出来ます。例えば、
```php
return [
['agree', 'required', 'isEmpty' => function ($value) {
return empty($value);
}],
];
}]
```
> Note: たいていのバリデータは、[[yii\validators\Validator::skipOnEmpty]] プロパティがデフォルト値 `true` を取っている場合は、空の入力値を処理しません。
そのようなバリデータは、関連付けられた属性が空の入力値を受け取ったときは、検証の過程ではスキップされるだけになります。
[コア・バリデータ](tutorial-core-validators.md) の中では、`captcha`、`default`、`filter`、`required`、そして `trim` だけが空の入力値を処理します。
> Note: たいていのバリデータは、[[yii\validators\Validator::skipOnEmpty]] プロパティがデフォルト値 `true` を取っている場合は、
空の入力値を処理しません。そのようなバリデータは、関連付けられた属性が空の入力値を受け取ったときは、
検証の過程ではスキップされるだけになります。[コア・バリデータ](tutorial-core-validators.md) の中では、
`captcha`、`default`、`filter`、`required`、そして `trim` だけが空の入力値を処理します。
## その場限りの検証 <span id="ad-hoc-validation"></span>
時として、何らかのモデルに結び付けられていない値に対する *その場限りの検証* を実行しなければならない場合があります。
実行する必要がある検証が一種類 (例えば、メール・アドレスの検証) だけである場合は、使いたいバリデータの [[yii\validators\Validator::validate()|validate()]] メソッドを次のように呼び出すことが出来ます。
実行する必要がある検証が一種類 (例えば、メール・アドレスの検証) だけである場合は、
使いたいバリデータの [[yii\validators\Validator::validate()|validate()]] メソッドを次のように呼び出すことが出来ます。
```php
$email = 'test@example.com';
@ -293,7 +307,9 @@ public function actionSearch($name, $email)
}
```
[[yii\base\DynamicModel::validateData()]] メソッドは `DynamicModel` のインスタンスを作成し、与えられた値 (この例では `name``email`) を使って属性を定義し、そして、与えられた規則で [[yii\base\Model::validate()]] を呼び出します。
[[yii\base\DynamicModel::validateData()]] メソッドは `DynamicModel` のインスタンスを作成し、
与えられた値 (この例では `name``email`) を使って属性を定義し、そして、与えられた規則で
[[yii\base\Model::validate()]] を呼び出します。
別の選択肢として、次のように、もっと「クラシック」な構文を使って、その場限りのデータ検証を実行することも出来ます。
@ -313,8 +329,11 @@ public function actionSearch($name, $email)
}
```
検証を実行した後は、通常のモデルで行うのと同様に、検証が成功したか否かを [[yii\base\DynamicModel::hasErrors()|hasErrors()]] メソッドを呼んでチェックして、[[yii\base\DynamicModel::errors|errors]] プロパティから検証エラーを取得することが出来ます。
また、このモデルのインスタンスによって定義された動的な属性に対しても、例えば `$model->name``$model->email` のようにして、アクセスすることが出来ます。
検証を実行した後は、通常のモデルで行うのと同様に、検証が成功したか否かを
[[yii\base\DynamicModel::hasErrors()|hasErrors()]] メソッドを呼んでチェックして、
[[yii\base\DynamicModel::errors|errors]] プロパティから検証エラーを取得することが出来ます。
また、このモデルのインスタンスによって定義された動的な属性に対しても、例えば `$model->name`
`$model->email` のようにして、アクセスすることが出来ます。
## バリデータを作成する <span id="creating-validators"></span>
@ -338,7 +357,8 @@ Yii のリリースに含まれている [コア・バリデータ](tutorial-cor
function ($attribute, $params, $validator)
```
属性が検証に失敗した場合は、メソッド/関数 は [[yii\base\Model::addError()]] を呼んでエラー・メッセージをモデルに保存し、後で読み出してエンド・ユーザに表示することが出来るようにしなければなりません。
属性が検証に失敗した場合は、メソッド/関数 は [[yii\base\Model::addError()]] を呼んでエラー・メッセージをモデルに保存し、
後で読み出してエンド・ユーザに表示することが出来るようにしなければなりません。
下記にいくつかの例を示します。
@ -382,9 +402,10 @@ class MyForm extends Model
> $validator->addError($this, $attribute, 'The value "{value}" is not acceptable for {attribute}.');
> ```
> Note: デフォルトでは、インライン・バリデータは、関連付けられている属性が空の入力値を受け取ったり、既に何らかの検証規則に失敗したりしている場合には、適用されません。
> 規則が常に適用されることを保証したい場合は、規則の宣言において [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] および/または [[yii\validators\Validator::skipOnError|skipOnError]] のプロパティを false に設定することが出来ます。
> 例えば、
> Note: デフォルトでは、インライン・バリデータは、関連付けられている属性が空の入力値を受け取ったり、
> 既に何らかの検証規則に失敗したりしている場合には、適用されません。規則が常に適用されることを保証したい場合は、
> 規則の宣言において [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] および/または
> [[yii\validators\Validator::skipOnError|skipOnError]] のプロパティを false に設定することが出来ます。例えば、
>
> ```php
> [
@ -397,7 +418,9 @@ class MyForm extends Model
スタンドアロン・バリデータは、[[yii\validators\Validator]] またはその子クラスを拡張するクラスです。
[[yii\validators\Validator::validateAttribute()]] メソッドをオーバーライドすることによって、その検証ロジックを実装することが出来ます。
[インライン・バリデータ](#inline-validators) でするのと同じように、属性が検証に失敗した場合は、[[yii\base\Model::addError()]] を呼んでエラー・メッセージをモデルに保存します。
[インライン・バリデータ](#inline-validators) でするのと同じように、属性が検証に失敗した場合は、
[[yii\base\Model::addError()]] を呼んでエラー・メッセージをモデルに保存します。
例えば、上記のインライン・バリデータは、新しい [[components/validators/CountryValidator]] クラスに作りかえることが出来ます。
この場合、[[yii\validators\Validator::addError()]] を使って特製のメッセージをモデルに設定することが出来ます。
@ -418,12 +441,11 @@ class CountryValidator extends Validator
}
```
あなたのバリデータで、モデルを使わない値の検証をサポートしたい場合は、[[yii\validators\Validator::validate()]] もオーバーライドしなければなりません。
または、`validateAttribute()` と `validate()` の代りに、[[yii\validators\Validator::validateValue()]] をオーバーライドしても構いません。
あなたのバリデータで、モデルを使わない値の検証をサポートしたい場合は、[[yii\validators\Validator::validate()]]
もオーバーライドしなければなりません。または、`validateAttribute()` と `validate()` の代りに、
[[yii\validators\Validator::validateValue()]] をオーバーライドしても構いません。
と言うのは、前の二つは、デフォルトでは、`validateValue()` を呼び出すことによって実装されているからです。
次の例は、上記のバリデータ・クラスをあなたのモデルの中でどのように使用することが出来るかを示すものです。
```php
@ -450,6 +472,7 @@ class EntryForm extends Model
}
```
## 複数の属性の検証 <span id="multiple-attributes-validation"></span>
時として、バリデータが複数の属性に関係する場合があります。次のようなフォームを考えてみてください。
@ -555,8 +578,7 @@ foreach ($attributes as $attribute) {
$this->addError('*', '子どもの数に対して給与が不足しています。');
```
結果として、フォームのフィールドの近くにはこのエラー・メッセージは表示されません。
これを表示するためには、ビューにエラー・サマリを含めます。
結果として、フォームのフィールドの近くにはこのエラー・メッセージは表示されません。これを表示するためには、ビューにエラー・サマリを含めます。
```php
<?= $form->errorSummary($model) ?>
@ -564,7 +586,6 @@ $this->addError('*', '子どもの数に対して給与が不足しています
> Note: 複数の属性を一度に検証するバリデータを作成する方法が [community cookbook](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-validator-multiple-attributes.md) で分り易く解説されています。.
## クライアント・サイドでの検証 <span id="client-side-validation"></span>
エンド・ユーザが HTML フォームで値を入力する際には、JavaScript に基づくクライアント・サイドでの検証を提供することが望まれます。
@ -619,7 +640,8 @@ class LoginForm extends Model
```
次のコードによって構築される HTML フォームは、`username` と `password` の二つの入力フィールドを含みます。
何も入力せずにこのフォームを送信すると、何かを入力するように要求するエラー・メッセージが、サーバと少しも交信することなく、ただちに表示されることに気付くでしょう。
何も入力せずにこのフォームを送信すると、何かを入力するように要求するエラー・メッセージが、
サーバと少しも交信することなく、ただちに表示されることに気付くでしょう。
```php
<?php $form = yii\widgets\ActiveForm::begin(); ?>
@ -629,18 +651,21 @@ class LoginForm extends Model
<?php yii\widgets\ActiveForm::end(); ?>
```
舞台裏では、[[yii\widgets\ActiveForm]] がモデルで宣言されている検証規則を読んで、クライアント・サイドの検証をサポートするバリデータのために、適切な JavaScript コードを生成します。
舞台裏では、[[yii\widgets\ActiveForm]] がモデルで宣言されている検証規則を読んで、
クライアント・サイドの検証をサポートするバリデータのために、適切な JavaScript コードを生成します。
ユーザが入力フィールドの値を変更したりフォームを送信したりすると、クライアント・サイドの検証の JavaScript が起動されます。
クライアント・サイドの検証を完全に無効にしたい場合は、[[yii\widgets\ActiveForm::enableClientValidation]] プロパティを `false` に設定することが出来ます。
また、個々の入力フィールドごとにクライアント・サイドの検証を無効にしたい場合には、入力フィールドの [[yii\widgets\ActiveField::enableClientValidation]] プロパティを false に設定することが出来ます。
クライアント・サイドの検証を完全に無効にしたい場合は、[[yii\widgets\ActiveForm::enableClientValidation]]
プロパティを `false` に設定することが出来ます。
また、個々の入力フィールドごとにクライアント・サイドの検証を無効にしたい場合には、
入力フィールドの [[yii\widgets\ActiveField::enableClientValidation]] プロパティを false に設定することが出来ます。
`eanbleClientValidation` が入力フィールドのレベルとフォームのレベルの両方で構成されている場合は前者が優先されます。
> Info: バージョン 2.0.11 以降、[[yii\validators\Validator]] を拡張する全てのバリデータは、
> クライアント・サイドのオプションを独立したメソッド - [[yii\validators\Validator::getClientOptions()]] から受け取るようになりました。
> これを使うと、次のことが可能になります。
> Info: バージョン 2.0.11 以降、[[yii\validators\Validator]] を拡張する全てのバリデータは、クライアント・サイドのオプションを
> 独立したメソッド - [[yii\validators\Validator::getClientOptions()]] から受け取るようになりました。これを使うと、次のことが可能になります。
>
> - 独自のクライアント・サイド検証を実装しながら、サーバ・サイド検証のオプションとの同期はそのまま残す
> - 独自のクライアント・サイド検証を実装しながら、
> サーバ・サイド検証のオプションとの同期はそのまま残す
> - 特殊な要求に合うように拡張またはカスタマイズする
>
> ```php
@ -655,7 +680,9 @@ class LoginForm extends Model
### クライアント・サイドの検証を実装する <span id="implementing-client-side-validation"></span>
クライアント・サイドの検証をサポートするバリデータを作成するためには、クライアント・サイドでの検証を実行する JavaScript コードを返す [[yii\validators\Validator::clientValidateAttribute()]] メソッドを実装しなければなりません。
クライアント・サイドの検証をサポートするバリデータを作成するためには、
クライアント・サイドでの検証を実行する JavaScript コードを返す
[[yii\validators\Validator::clientValidateAttribute()]] メソッドを実装しなければなりません。
その JavaScript の中では、次の事前定義された変数を使用することが出来ます。
- `attribute`: 検証される属性の名前。
@ -712,7 +739,8 @@ JS;
> ```
> Tip: クライアント・サイドの検証を手動で操作する必要がある場合、すなわち、動的にフィールドを追加したり、何か特殊な UI ロジックを実装する場合は、
> Yii 2.0 Cookbook の [Working with ActiveForm via JavaScript](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-activeform-js.md) を参照してください。
> Yii 2.0 Cookbook の [Working with ActiveForm via JavaScript](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-activeform-js.md)
> を参照してください。
### Deferred 検証 <span id="deferred-validation"></span>
@ -764,8 +792,8 @@ JS;
> Note: 属性が検証された後に、`resolve()` メソッドを呼び出さなければなりません。
そうしないと、主たるフォームの検証が完了しません。
簡潔に記述できるように、`deferred` 配列はショートカット・メソッド `add()` を装備しており、このメソッドを使うと、自動的に Deferred オブジェクトを作成して `deferred` 配列に追加することが出来ます。
このメソッドを使えば、上記の例は次のように簡潔に記すことが出来ます。
簡潔に記述できるように、`deferred` 配列はショートカット・メソッド `add()` を装備しており、このメソッドを使うと、自動的に Deferred オブジェクトを作成して
`deferred` 配列に追加することが出来ます。このメソッドを使えば、上記の例は次のように簡潔に記すことが出来ます。
```php
public function clientValidateAttribute($model, $attribute, $view)
@ -797,7 +825,8 @@ JS;
このような場合には、AJAX ベースの検証を使うことが出来ます。
AJAX 検証は、通常のクライアント・サイドでの検証と同じユーザ体験を保ちながら、入力値を検証するためにバックグラウンドで AJAX リクエストを発行します。
単一のインプット・フィールドに対して AJAX 検証を有効にするためには、そのフィールドの [[yii\widgets\ActiveField::enableAjaxValidation|enableAjaxValidation]] プロパティを true に設定し、フォームに一意の `id` を指定します。
単一のインプット・フィールドに対して AJAX 検証を有効にするためには、そのフィールドの
[[yii\widgets\ActiveField::enableAjaxValidation|enableAjaxValidation]] プロパティを true に設定し、フォームに一意の `id` を指定します。
```php
use yii\widgets\ActiveForm;
@ -813,7 +842,8 @@ echo $form->field($model, 'username', ['enableAjaxValidation' => true]);
ActiveForm::end();
```
フォームの全てのインプットに対して AJAX 検証を有効にするためには、フォームのレベルで [[yii\widgets\ActiveForm::enableAjaxValidation|enableAjaxValidation]] を true に設定します。
フォームの全てのインプットに対して AJAX 検証を有効にするためには、フォームのレベルで
[[yii\widgets\ActiveForm::enableAjaxValidation|enableAjaxValidation]] を true に設定します。
```php
$form = ActiveForm::begin([
@ -822,7 +852,8 @@ $form = ActiveForm::begin([
]);
```
> Note: `enableAjaxValidation` プロパティがインプット・フィールドのレベルとフォームのレベルの両方で構成された場合は、前者が優先されます。
> Note: `enableAjaxValidation` プロパティがインプット・フィールドのレベルとフォームのレベルの両方で構成された場合は、
前者が優先されます。
また、サーバ・サイドでは、AJAX 検証のリクエストを処理できるように準備しておく必要があります。
これは、コントローラのアクションにおいて、次のようなコード断片を使用することで達成できます。
@ -840,6 +871,7 @@ if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
> Info: AJAX 検証を実行するためには、[Deferred 検証](#deferred-validation) を使うことも出来ます。
しかし、ここで説明された AJAX 検証の機能の方がより体系化されており、コーディングの労力も少なくて済みます。
`enableClientValidation``enableAjaxValidation` が両方とも `true` に設定されているときは、クライアント検証が成功した後でだけ AJAX 検証のリクエストが起動されます。
`enableClientValidation``enableAjaxValidation` が両方とも `true` に設定されているときは、
クライアント検証が成功した後でだけ AJAX 検証のリクエストが起動されます。
`validateOnChange`, `validateOnBlur` または `validateOnType``true` に設定されている単一のフィールドを検証する場合、
当該フィールドが単独でクライアント検証を通ったら AJAX リクエストが送信されることに注意して下さい。

Loading…
Cancel
Save