Commit d3f6ce77 by Hisateru Tanaka

docs/guide-ja/concept-*.md - added [ci skip]

parent 5e4362f6
エイリアス
=======
ファイルパスや URL を表すのにエイリアスを使用すると、あなたはプロジェクト内で絶対パスや URL をハードコードする必要がなくなります。エイリアスは、通常のファイルパスや URL と区別するために、 `@` 文字で始まる必要があります。Yii はすでに利用可能な多くの事前定義エイリアスを持っています。
たとえば、 `@yii` というエイリアスは Yii フレームワークのインストールパスを表し、 `@web` は現在実行中の Web アプリケーションのベース URL を表します。
エイリアスの定義 <a name="defining-aliases"></a>
----------------
[[Yii::setAlias()]] を呼び出すことにより、ファイルパスまたは URL のエイリアスを定義することができます。
```php
// ファイルパスのエイリアス
Yii::setAlias('@foo', '/path/to/foo');
// URL のエイリアス
Yii::setAlias('@bar', 'http://www.example.com');
```
> 補足: エイリアスされているファイルパスやURLは、必ずしも実在するファイルまたはリソースを参照しない場合があります。
定義済みのエイリアスがあれば、スラッシュ `/` に続けて 1 つ以上のパスセグメントを追加することで([[Yii::setAlias()]]
の呼び出しを必要とせずに) 新しいエイリアスを導出することができます。 [[Yii::setAlias()]] を通じて定義されたエイリアスは
*ルートエイリアス* となり、それから派生したエイリアスは *派生エイリアス* になります。たとえば、 `@foo` がルートエイリアスなら、
`@foo/bar/file.php` は派生エイリアスです。
エイリアスを、他のエイリアス (ルートまたは派生のいずれか) を使用して定義することができます:
```php
Yii::setAlias('@foobar', '@foo/bar');
```
ルートエイリアスは通常、 [ブートストラップ](runtime-bootstrapping.md) 段階で定義されます。
たとえば、[エントリスクリプト](structure-entry-scripts.md)[[Yii::setAlias()]] を呼び出すことができます。
便宜上、 [アプリケーション](structure-applications.md) は、`aliases` という名前の書き込み可能なプロパティを提供しており、
それをアプリケーションの [構成情報](concept-configurations.md) で設定することが可能です。
```php
return [
// ...
'aliases' => [
'@foo' => '/path/to/foo',
'@bar' => 'http://www.example.com',
],
];
```
エイリアスの解決 <a name="resolving-aliases"></a>
-----------------
[[Yii::getAlias()]] を呼び出して、ルートエイリアスが表すファイルパスまたはURLを解決することができます。
同メソッドで、対応するファイルパスまたはURLに派生するエイリアスを解決することもできます。
```php
echo Yii::getAlias('@foo'); // /path/to/foo を表示
echo Yii::getAlias('@bar'); // http://www.example.com を表示
echo Yii::getAlias('@foo/bar/file.php'); // /path/to/foo/bar/file.php を表示
```
派生エイリアスによって表されるパスやURLは、派生エイリアス内のルートエイリアス部分を、対応するパス/URL
で置換して決定されます。
> 補足: [[Yii::getAlias()]] メソッドは、 結果のパスやURLが実在するファイルやリソースを参照しているかをチェックしません。
ルートエイリアス名にはスラッシュ `/` 文字を含むことができます。 [[Yii::getAlias()]] メソッドは、
エイリアスのどの部分がルートエイリアスであるかを賢く判別し、正確に対応するファイルパスやURLを決定します:
```php
Yii::setAlias('@foo', '/path/to/foo');
Yii::setAlias('@foo/bar', '/path2/bar');
Yii::getAlias('@foo/test/file.php'); // /path/to/foo/test/file.php を表示
Yii::getAlias('@foo/bar/file.php'); // /path2/bar/file.php を表示
```
もし `@foo/bar` がルートエイリアスとして定義されていなければ、最後のステートメントは `/path/to/foo/bar/file.php` を表示します。
エイリアスの使用 <a name="using-aliases"></a>
-------------
エイリアスは、それをパスやURLに変換するための [[Yii::getAlias()​]] の呼び出しがなくても、Yiiの多くの場所でみられます。
たとえば、 [[yii\caching\FileCache::cachePath]] ファイルパスとファイルパスを表すエイリアスの両方を受け入れることができ、
`@` プレフィックスによって、エイリアスとファイルパスを区別することができます。
```php
use yii\caching\FileCache;
$cache = new FileCache([
'cachePath' => '@runtime/cache',
]);
```
プロパティやメソッドのパラメータがエイリアスをサポートしているかどうかは、API ドキュメントに注意を払ってください。
事前定義されたエイリアス <a name="predefined-aliases"></a>
------------------
Yii では、一般的に使用されるフ​​ァイルのパスと URL を簡単に参照できるよう、エイリアスのセットが事前に定義されています:
- `@yii`, `BaseYii.php` ファイルがあるディレクトリ (フレームワークディレクトリとも呼ばれます)
- `@app`, 現在実行中のアプリケーションの [[yii\base\Application::basePath|ベースパス]]
- `@runtime`, 現在実行中のアプリケーションの [[yii\base\Application::runtimePath|ランタイムパス]] 。デフォルトは `@app/runtime`
- `@webroot`, 現在実行中の Web アプリケーションの Web ルートディレクトリ。エントリスクリプトを含むディレクトリをもとに決定されます。
- `@web`, 現在実行中の Web アプリケーションのベース URL。これは、 [[yii\web\Request::baseUrl]] と同じ値を持ちます。
- `@vendor`, [[yii\base\Application::vendorPath|Composerのベンダーディレクトリ]] 。デフォルトは `@app/vendor`
- `@bower`, [bower パッケージ](http://bower.io/) が含まれるルートディレクトリ。デフォルトは `@vendor/bower`
- `@npm`, [npm パッケージ](https://www.npmjs.org/) が含まれるルートディレクトリ。デフォルトは `@vendor/npm`
`@yii` エイリアスは [エントリスクリプト](structure-entry-scripts.md)`Yii.php` ファイルを読み込んだ時点で定義されます。
エイリアスの残りの部分は、アプリケーションのコンストラクタ内で、アプリケーションの [構成情報](concept-configurations.md) を適用するときに定義されます。
エクステンションのエイリアス <a name="extension-aliases"></a>
-----------------
Composer でインストールされる各 [エクステンション](structure-extensions.md) ごとに、エイリアスが自動的に定義されます。
各エイリアスは、その `composer.json` ファイルで宣言された、エクステンションのルート名前空間にちなんで名付けられており、
それらは、パ​​ッケージのルートディレクトリを表します。たとえば、あなたが `yiisoft/yii2-jui` エクステンションをインストールしたとすると、
自動的に `@yii/jui` というエイリアスができ、 [ブートストラップ](runtime-bootstrapping.md) 段階で、次のと同等のものとして定義されます:
```php
Yii::setAlias('@yii/jui', 'VendorPath/yiisoft/yii2-jui');
```
クラスのオートローディング
=================
Yiiは、必要となるすべてのクラスファイルを、特定してインクルードするにあたり、 [クラスのオートローディングメカニズム](http://www.php.net/manual/en/language.oop5.autoload.php)
を頼りにします。[PSR-4 標準](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md) に準拠した、高性能なクラスのオートローダーを提供します。
このオートローダーは、あなたが `Yii.php` ファイルをインクルードするときにインストールされます。
> 補足: 説明を簡単にするため、このセクションではクラスのオートローディングについてのみお話しします。しかし、
ここに記述されている内容は、同様に、インタフェースとトレイトのオートロードにも適用されることに注意してください。
Yii オートローダーの使用 <a name="using-yii-autoloader"></a>
------------------------
Yii のクラスオートローダーを使用するには、自分のクラスを作成して名前を付けるとき、次の2つの単純なルールに従わなければなりません:
* 各クラスは名前空間の下になければなりません (例 `foo\bar\MyClass`)
* 各クラスは次のアルゴリズムで決定される個別のファイルに保存されなければなりません:
```php
// $className は先頭にバックスラッシュを持つ完全修飾名
$classFile = Yii::getAlias('@' . str_replace('\\', '/', $className) . '.php');
```
たとえば、クラス名と名前空間が `foo\bar\MyClass` であれば、対応するクラスファイルのパスの [エイリアス](concept-aliases.md) は、
`@foo/bar/MyClass.php` になります。このエイリアスがファイルパスになるようにするには、`@foo` または `@foo/bar`
のどちらかが、 [ルートエイリアス](concept-aliases.md#defining-aliases) でなければなりません。
[Basic Application Template](start-basic.md) を使用している場合、最上位の名前空間 `app` の下にクラスを置くことができ、
そうすると、新しいエイリアスを定義しなくても、Yii によってそれらをオートロードできるようになります。これは `@app`
[事前定義されたエイリアス](concept-aliases.md#predefined-aliases) であるためで、`app\components\MyClass` のようなクラス名を
今説明したアルゴリズムに従って、クラスファイル `AppBasePath/components/MyClass.php` だと解決できるのです。
[Advanced Application Template](tutorial-advanced-app.md) では、各階層にそれ自身のルートエイリアスを持っています。たとえば、
フロントエンド層はルートエイリアス `@frontend` を持ち、バックエンド層は `@backend` です。その結果、名前空間 `frontend` の下に
フロントエンドクラスを置き、バックエンドクラスを `backend` の下に置けます。これで、これらのクラスは Yii のオートローダーによって
オートロードできるようになります。
クラスマップ <a name="class-map"></a>
---------
Yii のクラスオートローダーは、 *クラスマップ* 機能をサポートしており、クラス名を対応するクラスファイルのパスにマップできます。
オートローダーがクラスをロードしているとき、クラスがマップに見つかるかどうかを最初にチェックします。もしあれば、対応する
ファイルのパスは、それ以上チェックされることなく、直接インクルードされます。これでクラスのオートローディングを非常に高速化できます。
実際に、すべての Yii のコアクラスは、この方法でオートロードされています。
次の方法で、 `Yii::$classMap` に格納されるクラスマップにクラスを追加できます:
```php
Yii::$classMap['foo\bar\MyClass'] = 'path/to/MyClass.php';
```
クラスファイルのパスを指定するのに、 [エイリアス](concept-aliases.md) を使うことができます。クラスが使用される前にマップが準備できるように、
[ブートストラップ](runtime-bootstrapping.md) プロセス内でクラスマップを設定する必要があります。
他のオートローダーの使用 <a name="using-other-autoloaders"></a>
-----------------------
Yii はパッケージ依存関係マネージャとして Composer を包含しているので、Composer のオートローダーもインストールすることをお勧めします。
あなたが独自のオートローダーを持つサードパーティライブラリを使用している場合、それらもインストールする必要があります。
Yii オートローダーを他のオートローダーと一緒に使うときは、他のすべてのオートローダーがインストールされた *後で*`Yii.php`
ファイルをインクルードする必要があります。これで Yii のオートローダーが、任意クラスのオートローディング要求に応答する最初のものになります。
たとえば、次のコードは [Basic Application Template](start-basic.md)[エントリスクリプト](structure-entry-scripts.md) から抜粋したものです。
最初の行は、Composer のオートローダーをインストールしており、二行目は Yii のオートローダーをインストールしています。
```php
require(__DIR__ . '/../vendor/autoload.php');
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');
```
あなたは Yii のオートローダーを使わず、Composer のオートローダーだけを単独で使用することもできます。しかし、そうすることによって、
あなたのクラスのオートローディングのパフォーマンスは低下し、クラスをオートロード可能にするために Composer が設定したルールに従わなければならなくなります。
> Info: Yiiのオートローダーを使用したくない場合は、 `Yii.php` ファイルの独自のバージョンを作成し、
それを [エントリスクリプト](structure-entry-scripts.md) でインクルードする必要があります。
エクステンションクラスのオートロード <a name="autoloading-extension-classes"></a>
-----------------------------
Yii のオートローダーは、 [エクステンション](structure-extensions.md) クラスのオートロードが可能です。唯一の要件は、
エクステンションがその `composer.json` ファイルに正しく `autoload` セクションを指定していることです。
`autoload` 指定方法の詳細については [Composer のドキュメント](https://getcomposer.org/doc/04-schema.md#autoload) 参照してください。
Yii のオートローダーを使用しない場合でも、まだ Composer のオートローダーはエクステンションクラスをオートロード可能です。
ビヘイビア
=========
ビヘイビアは [[yii\base\Behavior]] 、あるいはその子クラスのインスタンスです。ビヘイビアは
[ミックスイン](http://en.wikipedia.org/wiki/Mixin) としても知られ、既存の [[yii\base\Component|component]] クラスの
機能を、クラスの継承を変更せずに拡張することができます。コンポーネントにビヘイビアをアタッチすると、その
コンポーネントにはビヘイビアのメソッドとプロパティが "注入" され、それらのメソッドとプロパティは、
コンポーネントクラス自体に定義されているかのようにアクセスできるようになります。また、ビヘイビアは、
コンポーネントによってトリガされた [イベント](concept-events.md) に応答することができるので、
ビヘイビアでコンポーネントの通常のコード実行をカスタマイズすることができます。
ビヘイビアの定義 <a name="defining-behaviors"></a>
------------------
ビヘイビアを定義するには、 [[yii\base\Behavior]] あるいは子クラスを継承するクラスを作成します。たとえば:
```php
namespace app\components;
use yii\base\Behavior;
class MyBehavior extends Behavior
{
public $prop1;
private $_prop2;
public function getProp2()
{
return $this->_prop2;
}
public function setProp2($value)
{
$this->_prop2 = $value;
}
public function foo()
{
// ...
}
}
```
上のコードは、 `app\components\MyBehavior` という、2つのプロパティ -- `prop1``prop2` -- と
`foo()` メソッドを持つビヘイビアクラスを定義します。`prop2` プロパティは、 `getProp2()` getter メソッドと `setProp2()` setter メソッドで定義されることに着目してください。
[[yii\base\Behavior]] は [[yii\base\Object]] を継承しているので、getter と​​ setter による [プロパティ](concept-properties.md) 定義をサポートします。
このクラスはビヘイビアなので、コンポーネントにアタッチされると、そのコンポーネントは `prop1``prop2` プロパティ、それと `foo()` メソッドを持つようになります。
> Tip: ビヘイビア内から、[[yii\base\Behavior::owner]] プロパティを介して、ビヘイビアをアタッチしたコンポーネントにアクセスすることができます。
コンポーネントイベントのハンドリング
------------------
ビヘイビアが、アタッチされたコンポーネントがトリガするイベントに応答する必要がある場合は、
[[yii\base\Behavior::events()]] メソッドをオーバーライドしましょう。たとえば:
```php
namespace app\components;
use yii\db\ActiveRecord;
use yii\base\Behavior;
class MyBehavior extends Behavior
{
// ...
public function events()
{
return [
ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
];
}
public function beforeValidate($event)
{
// ...
}
}
```
[[yii\base\Behavior::events()]] メソッドは、イベントとそれに対応するハンドラのリストを返します。
上の例では [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] イベントがあること、
そのハンドラ定義である `beforeValidate()` を宣言しています。イベントハンドラを指定するときは、以下の表記方法が使えます:
* ビヘイビアクラスのメソッド名を参照する文字列 (上の例など)
* オブジェクトまたはクラス名と文字列のメソッド名 (括弧なし) 例 `[$object, 'methodName']`
* 無名関数
イベントハンドラのシグネチャは次のようにしてください。`$event` はイベントのパラメータを参照します。イベントの詳細については
[イベント](concept-events.md) セクションを参照してください。
```php
function ($event) {
}
```
ビヘイビアのアタッチ <a name="attaching-behaviors"></a>
-------------------
[[yii\base\Component|component]] へのビヘイビアのアタッチは、静的にも動的にも可能です。実際は、前者のほうがより一般的ですが。
ビヘイビアを静的にアタッチするには、ビヘイビアをアタッチしたいコンポーネントクラスの [[yii\base\Component::behaviors()|behaviors()]] メソッドをオーバーライドします。
[[yii\base\Component::behaviors()|behaviors()]] メソッドは、ビヘイビアの [構成](concept-configurations.md) のリストを返さなければなりません。
各ビヘイビアの構成内容は、ビヘイビアのクラス名でも、構成情報配列でもかまいません。
```php
namespace app\models;
use yii\db\ActiveRecord;
use app\components\MyBehavior;
class User extends ActiveRecord
{
public function behaviors()
{
return [
// 無名ビヘイビア ビヘイビアクラス名のみ
MyBehavior::className(),
// 名前付きビヘイビア ビヘイビアクラス名のみ
'myBehavior2' => MyBehavior::className(),
// 無名ビヘイビア 構成情報配列
[
'class' => MyBehavior::className(),
'prop1' => 'value1',
'prop2' => 'value2',
],
// 名前付きビヘイビア 構成情報配列
'myBehavior4' => [
'class' => MyBehavior::className(),
'prop1' => 'value1',
'prop2' => 'value2',
]
];
}
}
```
ビヘイビア構成に対応する配列のキーを指定することによって、ビヘイビアに名前を関連付けることができます。この場合、ビヘイビアは *名前付きビヘイビア* と呼ばれます。上の例では、2つの名前付きビヘイビア​​
`myBehavior2``myBehavior4` があります。ビヘイビアが名前と関連付けられていない場合は、 *無名ビヘイビア* と呼ばれます。
ビヘイビアを動的にアタッチするには、ビヘイビアをアタッチしようとしているコンポーネントの [[yii\base\Component::attachBehavior()]] メソッドを呼びます:
```php
use app\components\MyBehavior;
// ビヘイビアオブジェクトをアタッチ
$component->attachBehavior('myBehavior1', new MyBehavior);
// ビヘイビアクラスをアタッチ
$component->attachBehavior('myBehavior2', MyBehavior::className());
// 構成情報配列をアタッチ
$component->attachBehavior('myBehavior3', [
'class' => MyBehavior::className(),
'prop1' => 'value1',
'prop2' => 'value2',
]);
```
[[yii\base\Component::attachBehaviors()]] メソッドを使うと、いちどに複数のビヘイビアをアタッチできます:
```php
$component->attachBehaviors([
'myBehavior1' => new MyBehavior, // 名前付きビヘイビア
MyBehavior::className(), // 無名ビヘイビア
]);
```
次のように、 [構成情報](concept-configurations.md) を通じてビヘイビアをアタッチすることもできます:
```php
[
'as myBehavior2' => MyBehavior::className(),
'as myBehavior3' => [
'class' => MyBehavior::className(),
'prop1' => 'value1',
'prop2' => 'value2',
],
]
```
詳しくは [構成情報](concept-configurations.md#configuration-format) セクションを参照してください。
ビヘイビアの使用 <a name="using-behaviors"></a>
---------------
ビヘイビアを使用するには、まず上記の方法に従って [[yii\base\Component|コンポーネント]] にアタッチします。ビヘイビアがコンポーネントにアタッチされれば、その使用方法はシンプルです。
あなたは、アタッチされているコンポーネントを介して、ビヘイビアの *パブリック* メンバ変数、または getter や setter によって定義されたプロパティにアクセスすることができます:
```php
// "prop1" はビヘイビアクラス内で定義されたプロパティ
echo $component->prop1;
$component->prop1 = $value;
```
また同様に、ビヘイビアの *パブリック* メソッドも呼ぶことができます:
```php
// foo() はビヘイビアクラス内で定義されたパブリックメソッド
$component->foo();
```
ご覧のように、 `$component``prop1``foo()` を定義していないにもかかわらず、
アタッチされたビヘイビアによって、それらをコンポーネント定義の一部であるかのように使うことができるのです。
もし2つのビヘイビアが同じプロパティやメソッドを定義し、かつ両方とも同じコンポーネントにアタッチされている場合は、
プロパティやメソッドのアクセス時に、*最初に* コンポーネントにアタッチされたビヘイビアが優先されます。
ビヘイビアはコンポーネントにアタッチされるとき、名前と関連付けられているかもしれません。その場合、
その名前を使用してビヘイビアオブジェクトにアクセスすることができます:
```php
$behavior = $component->getBehavior('myBehavior');
```
また、コンポーネントにアタッチされた全てのビヘイビアを取得することもできます:
```php
$behaviors = $component->getBehaviors();
```
ビヘイビアのデタッチ <a name="detaching-behaviors"></a>
-------------------
ビヘイビアをデタッチするには、ビヘイビアに付けられた名前とともに [[yii\base\Component::detachBehavior()]] を呼び出します:
```php
$component->detachBehavior('myBehavior1');
```
*全ての* ビヘイビアをデタッチすることもできます:
```php
$component->detachBehaviors();
```
`TimestampBehavior` の利用 <a name="using-timestamp-behavior"></a>
-------------------------
しめくくりに、[[yii\behaviors\TimestampBehavior]] を見てみましょう。このビヘイビアは、
保存時 (つまり挿入や更新) に、[[yii\db\ActiveRecord|アクティブレコード]] モデルの
タイムスタンプ属性の自動更新をサポートします。
まず、使用しようと考えている [[yii\db\ActiveRecord|アクティブレコード]] クラスに、このビヘイビアをアタッチします:
```php
namespace app\models\User;
use yii\db\ActiveRecord;
use yii\behaviors\TimestampBehavior;
class User extends ActiveRecord
{
// ...
public function behaviors()
{
return [
[
'class' => TimestampBehavior::className(),
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
],
],
];
}
}
```
上のビヘイビア構成は、レコードが:
* 挿入されるとき、ビヘイビアは現在のタイムスタンプを `created_at``updated_at` 属性に割り当てます
* 更新されるとき、ビヘイビアは現在のタイムスタンプを `updated_at` 属性に割り当てます
所定の位置にそのコードを使用すると、もし `User` オブジェクトを設け、それを保存しようとしたら、そこで、
`created_at``updated_at` が自動的に現在のタイムスタンプで埋められます。
```php
$user = new User;
$user->email = 'test@example.com';
$user->save();
echo $user->created_at; // 現在のタイムスタンプが表示される
```
[[yii\behaviors\TimestampBehavior|TimestampBehavior]] は、指定された属性に現在のタイムスタンプを割り当てて
それをデータベースに保存する、便利なメソッド [[yii\behaviors\TimestampBehavior::touch()|touch()]] を提供します。
```php
$user->touch('login_time');
```
ビヘイビアとトレイトの比較 <a name="comparison-with-traits"></a>
----------------------
ビヘイビアは、主となるクラスにそのプロパティやメソッドを「注入する」という点で [トレイト](http://www.php.net/traits)
に似ていますが、これらは多くの面で異なります。以下に説明するように、それらは互いに長所と短所を持っています。
それらは代替手段というよりも、むしろ相互補完関係のようなものです。
### ビヘイビアを使う理由 <a name="pros-for-behaviors"></a>
ビヘイビアは通常のクラスのように、継承をサポートしています。いっぽうトレイトは、
言語サポートされたコピー&ペーストとみなすことができます。トレイトは継承をサポートしません。
ビヘイビアは、コンポーネントクラスの変更を必要とせずに、動的なコンポーネントへのアタッチとデタッチが可能です。トレイトを使用するには、クラスをトレイトを使って書き換える必要があります。
ビヘイビアは構成可能ですがトレイトは不可能です。
ビヘイビアは、イベントに応答することで、コンポーネントのコード実行をカスタマイズできます。
同じコンポーネントにアタッチされた異なるビヘイビア間で名前の競合がある場合、その競合は自動的に、
先にコンポーネントにアタッチされたものを優先することで解消されます。
別のトレイトが起こした名前競合の場合、影響を受けるプロパティやメソッドの名前変更による、手動での解決が必要です。
### トレイトを使う理由 <a name="pros-for-traits"></a>
ビヘイビアは時間もメモリも食うオブジェクトなので、トレイトはビヘイビアよりはるかに効率的です。
トレイトは言語構造であるため、IDE との相性に優れています。
コンポーネント
==========
コンポーネントは、Yiiアプリケーションの主要な構成ブロックです。コンポーネントは [[yii\base\Component]] 、
またはその派生クラスのインスタンスです。コンポーネントが他のクラスに提供する主な機能は次の 3 つです:
* [プロパティ](concept-properties.md)
* [イベント](concept-events.md)
* [ビヘイビア](concept-behaviors.md)
個々にでも、組み合わせでも、これらの機能は Yii のクラスのカスタマイズ性と使いやすさをとても高めてくれます。たとえば、[[yii\jui\DatePicker|日付選択]] を行うユーザインターフェース·コンポーネントは、
対話型の日付選択UIを生成するとき、ビューで次のように使用することができます:
```php
use yii\jui\DatePicker;
echo DatePicker::widget([
'language' => 'ja',
'name' => 'country',
'clientOptions' => [
'dateFormat' => 'yy-mm-dd',
],
]);
```
クラスが [[yii\base\Component]] を継承しているおかげで、ウィジェットのプロパティは簡単に記述できます。
コンポーネントは非常に強力ですが、 [イベント](concept-events.md)[ビヘイビア](concept-behaviors.md) をサポートするため、
余分にメモリとCPU時間を要し、通常のオブジェクトよりも少し重くなります。
あなたのコンポーネントがこれら2つの機能を必要としない場合、[[yii\base\Component]] の代わりに、 [[yii\base\Object]] からコンポーネントクラスを派生することを検討してもよいでしょう。
そうすることで、あなたのコンポーネントは、 [プロパティ](concept-properties.md) のサポートが維持されたまま、通常のPHPオブジェクトのように効率的になります。
[[yii\base\Component]] または [[yii\base\Object]] からクラスを派生するときは、次の規約に従うことが推奨されます:
- コンストラクタをオーバーライドする場合は、コンストラクタの *最後の* パラメータとして `$config` パラメータを指定し、親のコンストラクタにこのパラメータを渡すこと。
- 自分がオーバーライドしたコンストラクタの *最後で* 、必ず親クラスのコンストラクタを呼び出すこと。
- [[yii\base\Object::init()]] メソッドをオーバーライドする場合は、自分の `init` メソッドの *最初に* 、必ず `init` の親実装を呼び出すようにすること。
例:
```php
namespace yii\components\MyClass;
use yii\base\Object;
class MyClass extends Object
{
public $prop1;
public $prop2;
public function __construct($param1, $param2, $config = [])
{
// ... 構成前の初期化
parent::__construct($config);
}
public function init()
{
parent::init();
// ... 構成後の初期化
}
}
```
このガイドラインに従うことで、あなたのコンポーネントは生成時に [コンフィグ可能](concept-configurations.md) になります。例:
```php
$component = new MyClass(1, 2, ['prop1' => 3, 'prop2' => 4]);
// とする代わりに
$component = \Yii::createObject([
'class' => MyClass::className(),
'prop1' => 3,
'prop2' => 4,
], [1, 2]);
```
> 補足: [[Yii::createObject()]] を呼び出すアプローチは複雑に見えますが、より強力です。というのも、それが [依存性注入コンテナ](concept-di-container.md) 上に実装されているからです。
[[yii\base\Object]] クラスには、次のオブジェクトライフサイクルが適用されます:
1. コンストラクタ内の事前初期化。ここでデフォルトのプロパティ値を設定することができます。
2. `$config` によるオブジェクトの構成。構成情報は、コンストラクタ内で設定されたデフォルト値を上書きすることがあります。
3. [[yii\base\Object::init()|init()]] 内の事後初期化。サニティ・チェックやプロパティの正規化を行いたいときは、このメソッドをオーバーライドします。
4. オブジェクトのメソッド呼び出し。
最初の 3 つのステップは、すべてのオブジェクトのコンストラクタ内で発生します。これは、あなたがクラスインスタンス (つまり、オブジェクト) を得たときには、
すでにそのオブジェクトが適切な、信頼性の高い状態に初期化されていることを意味します。
構成情報
==============
新しいオブジェクトを作成したり、既存のオブジェクトを初期化するとき、Yiiでは構成情報が広く使用されています。構成情報は通常、作成されるオブジェクトのクラス名、およびオブジェクトの [プロパティ](concept-properties.md)
に割り当てられる初期値のリストを含みます。構成情報は、オブジェクトの [イベント](concept-events.md) にアタッチされるハンドラのリストや、オブジェクトにアタッチされる
[ビヘイビア](concept-behaviors.md) のリストを含むこともできます。
以下では、データベース接続を作成して初期化するために、構成情報が使用されています:
```php
$config = [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
];
$db = Yii::createObject($config);
```
[[Yiiの::CreateObject()]] メソッドは引数に構成情報の配列を受け取り、構成情報で名前指定されたクラスをインスタンス化してオブジェクトを作成します。オブジェクトがインスタンス化されるとき、その他の設定は、
オブジェクトのプロパティ、イベントハンドラ、およびビヘイビアを初期化するのに使われます。
すでにオブジェクトがある場合は、構成情報配列でオブジェクトのプロパティを初期化するのに [[Yii::configure()]] を使用することができます:
```php
Yii::configure($object, $config);
```
なお、この場合には、構成情報配列に `class` 要素を含んではいけません。
## 構成情報の形式 <a name="configuration-format"></a>
構成情報の形式は、フォーマルには次のように説明できます:
```php
[
'class' => 'ClassName',
'propertyName' => 'propertyValue',
'on eventName' => $eventHandler,
'as behaviorName' => $behaviorConfig,
]
```
ここで
* `class` 要素は、作成されるオブジェクトの完全修飾クラス名を指定します。
* `propertyName` 要素は、名前で指定されたプロパティの初期値を指定します。キーはプロパティ名で、値はそれに対応する初期値です。
パブリックメンバ変数と getter/setter によって定義されている [プロパティ](concept-properties.md) のみを設定することができます。
* `on eventName` 要素は、どのようなハンドラがオブジェクトの [イベント](concept-events.md) にアタッチされるかを指定します。
配列のキーが `on` に続けてイベント名という書式になることに注意してください。サポートされているイベントハンドラの形式については、
[イベント](concept-events.md) のセクションを参照してください。
* `as behaviorName` 要素は、どのような [ビヘイビア](concept-behaviors.md) がオブジェクトにアタッチされるかを指定します。
配列のキーが `as` に続けてビヘイビア名という書式になり、 `$behaviorConfig` で示される値が、ここで説明する一般的な構成情報のような、
ビヘイビアを作成するための構成情報になることに注意してください。
下記は、初期プロパティ値、イベントハンドラ、およびビヘイビアでの構成を示した例です:
```php
[
'class' => 'app\components\SearchEngine',
'apiKey' => 'xxxxxxxx',
'on search' => function ($event) {
Yii::info("Keyword searched: " . $event->keyword);
},
'as indexer' => [
'class' => 'app\components\IndexerBehavior',
// ... プロパティ初期値 ...
],
]
```
## 構成情報の使用 <a name="using-configurations"></a>
構成情報は Yii の多くの場所で使用されています。このセクションの冒頭では、 [[Yii::createObject()]]
を使って、構成情報に応じてオブジェクトを作成する方法を示しました。このサブセクションでは、
アプリケーションの構成とウィジェットの構成という、2つの主要な構成情報の用途を説明します。
### アプリケーションの構成 <a name="application-configurations"></a>
[アプリケーション](structure-applications.md) の構成は、おそらく Yii の中で最も複雑な配列のひとつです。
それは [[yii\web\Application|application]] クラスが、設定可能なプロパティとイベントを数多く持つためです。
さらに重要なことは、その [[yii\web\Application::components|components]] プロパティが、アプリケーションに登録されている
コンポーネント生成用の構成情報配列を受け取ることができることです。以下は、 [basic application template](start-basic.md)
のアプリケーション構成ファイルの概要です。
```php
$config = [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
'components' => [
'cache' => [
'class' => 'yii\caching\FileCache',
],
'mailer' => [
'class' => 'yii\swiftmailer\Mailer',
],
'log' => [
'class' => 'yii\log\Dispatcher',
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
],
],
],
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=stay2',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
],
],
];
```
この構成情報には、 `class` キーがありません。それは、[エントリスクリプト](structure-entry-scripts.md) で以下のように、
クラス名が既に与えられて使用されているためです。
```php
(new yii\web\Application($config))->run();
```
アプリケーションの `components` プロパティ構成の詳細については、 [アプリケーション](structure-applications.md) セクションと [サービスロケータ](concept-service-locator.md) セクションにあります。
### ウィジェットの構成 <a name="widget-configurations"></a>
[ウィジェット](structure-widgets.md) を使用するときは、多くの場合、ウィジェットのプロパティをカスタマイズするために、構成情報を使用する必要があります。
[[yii\base\Widget::widget()]] と [[yii\base\Widget::begin()]] の両メソッドを使って、ウィジェットを作成できます。それらは、以下のような構成情報配列を取ります。
```php
use yii\widgets\Menu;
echo Menu::widget([
'activateItems' => false,
'items' => [
['label' => 'ホーム', 'url' => ['site/index']],
['label' => '製品', 'url' => ['product/index']],
['label' => 'ログイン', 'url' => ['site/login'], 'visible' => Yii::$app->user->isGuest],
],
]);
```
上記のコードは、 `Menu` ウィジェットを作成し、その `activateItems` プロパティが `false` になるよう初期化します。
`items` プロパティも、表示されるメニュー項目で構成されます。
クラス名がすでに与えられているので、構成情報配列が `class` キーを持つべきではないことに注意してください。
## 構成情報ファイル <a name="configuration-files"></a>
構成情報がとても複雑になる場合、一般的な方法は、 *構成情報ファイル* と呼ばれる、ひとつまたは複数の PHP ファイルにそれを格納することです。
構成情報ファイルは、構成情報を表す PHP 配列を return します。
たとえば、次のように、 `web.php` と名づけたファイルにアプリケーション構成を保持することができます。
```php
return [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
'components' => require(__DIR__ . '/components.php'),
];
```
`components` の構成もまた複雑になるため、上記のように、 `components.php` と呼ぶ別のファイルにそれを格納し `web.php` でそのファイルを "require" しています。
この `components.php` の内容は、次のようになっています。
```php
return [
'cache' => [
'class' => 'yii\caching\FileCache',
],
'mailer' => [
'class' => 'yii\swiftmailer\Mailer',
],
'log' => [
'class' => 'yii\log\Dispatcher',
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
],
],
],
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=stay2',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
],
];
```
構成情報ファイルに格納されている構成情報を取得するには、以下のように、それを "require" するだけです:
```php
$config = require('path/to/web.php');
(new yii\web\Application($config))->run();
```
## デフォルト設定 <a name="default-configurations"></a>
[[Yii::createObject()]] メソッドは、 [依存性注入コンテナ](concept-di-container.md) をベースに実装されています。
そのため、指定されたクラスが [[Yii::createObject()]] を使用して作成されるとき、そのすべてのインスタンスに適用される、
いわゆる *デフォルト設定* のセットを指定することができます。デフォルト設定は、
[ブートストラップ](runtime-bootstrapping.md) コード内の `Yii::$container->set()` を呼び出すことで指定することができます。
たとえばあなたが、すべてのリンクページャーが最大で5つのページボタン (デフォルト値は10) を伴って表示されるよう
[[yii\widgets\LinkPager]] をカスタマイズしたいとき、その目標を達成するには次のコードを使用することができます。
```php
\Yii::$container->set('yii\widgets\LinkPager', [
'maxButtonCount' => 5,
]);
```
デフォルト設定を使用しなければ、あなたは、リンクページャーを使うすべての箇所で `maxButtonCount` を設定しなければなりません。
## 環境定数 <a name="environment-constants"></a>
構成情報は、多くの場合、アプリケーションが実行される環境に応じて変化します。たとえば、
開発環境では `mydb_dev` という名前のデータベースを使用し、本番サーバー上では `mydb_prod` データベースを
使用したいかもしれません。環境の切り替えを容易にするために、Yii は、あなたのアプリケーションの
[エントリスクリプト](structure-entry-scripts.md) で定義可能な `YII_ENV` という名前の定数を提供します。
たとえば:
```php
defined('YII_ENV') or define('YII_ENV', 'dev');
```
`YII_ENV` を次のいずれかの値と定義することができます:
- `prod`: 本番環境。定数 `YII_ENV_PROD` は true と評価されます。
とくに定義しない場合、これが `YII_ENV` のデフォルト値です。
- `dev`: 開発環境。定数 `YII_ENV_DEV` は true と評価されます。
- `test`: テスト環境。定数 `YII_ENV_TEST` は true と評価されます。
これらの環境定数を使用すると、現在の環境に基づいて条件付きで構成情報を指定することもできます。
たとえば、アプリケーション構成情報には、開発環境での [デバッグツールバーとデバッガ](tool-debugger.md)
を有効にするために、次のコードを含むことができます。
```php
$config = [...];
if (YII_ENV_DEV) {
// 'dev' 環境用に構成情報を調整
$config['bootstrap'][] = 'debug';
$config['modules']['debug'] = 'yii\debug\Module';
}
return $config;
```
依存性注入コンテナ
==============================
依存性注入 (DI) コンテナは、オブジェクトとそのすべての依存オブジェクトを、インスタンス化し、設定する方法を知っているオブジェクトです。
なぜ DI コンテナが便利なのかは、[Martin の記事](http://martinfowler.com/articles/injection.html) の説明がわかりやすいでしょう。
ここでは、主に Yii の提供する DI コンテナの使用方法を説明します。
依存性注入 <a name="dependency-injection"></a>
--------------------
Yii は [[yii\di\Container]] クラスを通して DI コンテナの機能を提供します。これは、次の種類の依存性注入をサポートしています:
* コンストラクタ·インジェクション
* セッター/プロパティ·インジェクション
* PHP コーラブル·インジェクション
### コンストラクタ·インジェクション <a name="constructor-injection"></a>
DI コンテナは、コンストラクタパラメータの型ヒントの助けを借りた、コンストラクタ·インジェクションをサポートしています。
型ヒントは、クラスやインタフェースが新しいオブジェクトの作成で使用されるさい、どれが依存であるのかということをコンテナに教えます。
コンテナは、依存クラスやインタフェースのインスタンスを取得し、コンストラクタを通して、新しいオブジェクトにそれらの注入を試みます。
たとえば
```php
class Foo
{
public function __construct(Bar $bar)
{
}
}
$foo = $container->get('Foo');
// これは下記と等価:
$bar = new Bar;
$foo = new Foo($bar);
```
### セッター/プロパティ·インジェクション <a name="setter-and-property-injection"></a>
セッター/プロパティ·インジェクションは、[構成情報](concept-configurations.md) を通してサポートされます。
依存関係を登録するときや、新しいオブジェクトを作成するとき、コンテナが使用する構成情報を提供することができ、
それに対応するセッターまたはプロパティを通じて依存関係が注入されます。たとえば
```php
use yii\base\Object;
class Foo extends Object
{
public $bar;
private $_qux;
public function getQux()
{
return $this->_qux;
}
public function setQux(Qux $qux)
{
$this->_qux = $qux;
}
}
$container->get('Foo', [], [
'bar' => $container->get('Bar'),
'qux' => $container->get('Qux'),
]);
```
### PHP コーラブル・インジェクション <a name="php-callable-injection"></a>
この場合、コンテナは、登録された PHP のコーラブルオブジェクトを使用し、クラスの新しいインスタンスを構築します。
コーラブルは、依存関係を解決し、新しく作成されたオブジェクトに適切にそれらを注入する責任があります。たとえば
```php
$container->set('Foo', function () {
return new Foo(new Bar);
});
$foo = $container->get('Foo');
```
依存関係の登録 <a name="registering-dependencies"></a>
------------------------
あなたは、[[yii\di\Container::set()]] 使って依存関係を登録することができます。登録には依存関係の名前だけでなく、
依存関係の定義が必要です。依存関係の名前は、クラス名、インタフェース名、エイリアス名を指定することができます。
依存関係の定義には、クラス名、構成情報配列、PHPのコーラブルを指定できます。
```php
$container = new \yii\di\Container;
// クラス名そのまま。これはなくてもかまいません。
$container->set('yii\db\Connection');
// インターフェースの登録
// クラスがインターフェースに依存する場合、対応するクラスが依存オブジェクトとしてインスタンス化されます
$container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer');
// エイリアス名の登録。$container->get('foo') を使って Connection のインスタンスを作成できます
$container->set('foo', 'yii\db\Connection');
// 構成情報をともなうクラスの登録。クラスが get() でインスタンス化されるとき構成情報が適用されます
$container->set('yii\db\Connection', [
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
]);
// クラスの構成情報をともなうエイリアス名の登録
// この場合、クラスを指定する "class" 要素が必要です
$container->set('db', [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
]);
// PHP コーラブルの登録
// このコーラブルは $container->get('db') が呼ばれるたびに実行されます
$container->set('db', function ($container, $params, $config) {
return new \yii\db\Connection($config);
});
// コンポーネントインスタンスの登録
// $container->get('pageCache') は呼ばれるたびに毎回同じインスタンスを返します
$container->set('pageCache', new FileCache);
```
> 補足: 依存関係名が、対応する依存関係の定義と同じである場合は、それを DI コンテナに登録する必要はありません。
`set()` を介して登録された依存性は、依存性が必要とされるたびにインスタンスを生成します。
[[yii\di\Container::setSingleton()]] を使うと、単一のインスタンスをひとつだけ生成する依存関係を登録することができます:
```php
$container->setSingleton('yii\db\Connection', [
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
]);
```
依存関係の解決 <a name="resolving-dependencies"></a>
----------------------
依存関係を登録すると、新しいオブジェクトを作成するのに DI コンテナを使用することができ、
コンテナが自動的に、依存性をインスタンス化して新しく作成されたオブジェクトに注入することで、
依存関係を解決します。依存関係の解決は再帰的、つまり、ある依存性が他の依存関係を持っている場合、
それらの依存関係も自動的に解決されます。
[[yii\di\Container::get()]] を使って、新しいオブジェクトを作成することができます。
このメソッドは、クラス名、インタフェース名、エイリアス名で指定できる依存関係の名前を受け取ります。
依存関係名は、 `set()``setSingleton()` を介して登録されていたりされていなかったりする
可能性があります。オプションで、クラスのコンストラクタのパラメータのリストや、新しく作成された
オブジェクトを設定するための [設定情報](concept-configurations.md) を渡すことができます。
たとえば
```php
// "db" は事前に登録されたエイリアス名
$db = $container->get('db');
// これと同じ意味: $engine = new \app\components\SearchEngine($apiKey, ['type' => 1]);
$engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1]);
```
見えないところで、DIコンテナは、単に新しいオブジェクトを作成するよりもはるかに多くの作業を行います。
コンテナは、最初の依存クラスまたはインタフェースの名前を見つけるために、クラスのコンストラクタを検査し、
自動的にそれらの依存関係を再帰で解決します。
次のコードでより洗練された例を示します。 `UserLister` クラスは `UserFinderInterface`
インタフェースを実装するオブジェクトに依存します。 `UserFinder` クラスはこのインターフェイスを実装していて、かつ、
`Connection` オブジェクトに依存します。これらのすべての依存関係は、クラスのコンストラクタのパラメータのタイプヒンティングで宣言されています。
プロパティ依存性の登録をすれば、DI コンテナは自動的にこれらの依存関係を解決し、単純に `get('userLister')`
を呼び出すだけで新しい `UserLister` インスタンスを作成できます。
```php
namespace app\models;
use yii\base\Object;
use yii\db\Connection;
use yii\di\Container;
interface UserFinderInterface
{
function findUser();
}
class UserFinder extends Object implements UserFinderInterface
{
public $db;
public function __construct(Connection $db, $config = [])
{
$this->db = $db;
parent::__construct($config);
}
public function findUser()
{
}
}
class UserLister extends Object
{
public $finder;
public function __construct(UserFinderInterface $finder, $config = [])
{
$this->finder = $finder;
parent::__construct($config);
}
}
$container = new Container;
$container->set('yii\db\Connection', [
'dsn' => '...',
]);
$container->set('app\models\UserFinderInterface', [
'class' => 'app\models\UserFinder',
]);
$container->set('userLister', 'app\models\UserLister');
$lister = $container->get('userLister');
// と、いうのはこれと同じ:
$db = new \yii\db\Connection(['dsn' => '...']);
$finder = new UserFinder($db);
$lister = new UserLister($finder);
```
実際の使いかた <a name="practical-usage"></a>
---------------
あなたのアプリケーションの [エントリスクリプト](structure-entry-scripts.md)`Yii.php` ファイルをインクルードするとき、
Yii は DI コンテナを作成します。この DI コンテナは [[Yii::$container]] を介してアクセス可能です。 [[Yii::createObject()]] を呼び出したとき、
このメソッドは実際には、新しいオブジェクトを作成ために、コンテナの [[yii\di\Container::get()|get()]] メソッドを呼び出しています。
前述のとおり、DI コンテナは(もしあれば)自動的に依存関係を解決し、新しく作成されたオブジェクトにそれらを注入します。
Yii は、新しいオブジェクトを作成するさいそのコアコードのほとんどで [[Yii::createObject()]] を使用しているため、これは、
[[Yii::$container]] を扱えばグローバルにオブジェクトをカスタマイズすることができることを意味しています。
たとえば、 [[yii\widgets\LinkPager]] のページネーションボタンのデフォルト個数をグローバルにカスタマイズすることができます:
```php
\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]);
```
次のコードでビューでウィジェットを使用すれば、 `maxButtonCount` プロパティは、
クラスで定義されているデフォルト値 10 の代わりに 5 で初期化されます。
```php
echo \yii\widgets\LinkPager::widget();
```
DIコンテナを経由して設定された値は、こうやって、まだまだ上書きすることができます:
```php
echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]);
```
DI コンテナの自動コンストラクタ・インジェクションの利点を活かす別の例です。
あなたのコントローラクラスが、ホテル予約サービスのような、いくつかの他のオブジェクトに依存するとします。
あなたは、コンストラクタパラメータを通して依存関係を宣言して、DI コンテナにあなたの課題を解決させることができます。
```php
namespace app\controllers;
use yii\web\Controller;
use app\components\BookingInterface;
class HotelController extends Controller
{
protected $bookingService;
public function __construct($id, $module, BookingInterface $bookingService, $config = [])
{
$this->bookingService = $bookingService;
parent::__construct($id, $module, $config);
}
}
```
あなたがブラウザからこのコントローラにアクセスすると、 `BookingInterface` をインスタンス化できませんという
不具合報告エラーが表示されるでしょう。これは、この依存関係に対処する方法を DI コンテナに教える必要があるからです:
```php
\Yii::$container->set('app\components\BookingInterface', 'app\components\BookingService');
```
これで、あなたが再びコントローラにアクセスするときは、 `app\components\BookingService`
のインスタンスが作成され、コントローラのコンストラクタに3番目のパラメータとして注入されるようになります。
依存関係を登録するときに <a name="when-to-register-dependencies"></a>
-----------------------------
依存関係は、新しいオブジェクトが作成されるとき必要とされるので、それらの登録は可能な限り早期に行われるべきです。
推奨プラクティス以下のとおりです:
* あなたがアプリケーションの開発者である場合、アプリケーションの [エントリスクリプト](structure-entry-scripts.md) 内、
またはエントリスクリプトにインクルードされるスクリプト内で、依存関係を登録することができます。
* あなたが再配布可能な [エクステンション](structure-extensions.md) の開発者である場合は、エクステンションのブートストラップクラス内で
依存関係を登録することができます。
まとめ <a name="summary"></a>
-------
依存性注入と [サービスロケータ](concept-service-locator.md) はともに、疎結合でよりテストしやすい方法でのソフトウェア構築を可能にする、
定番のデザインパターンです。依存性注入とサービスロケータへのより深い理解を得るために、 [Martin の記事](http://martinfowler.com/articles/injection.html)
を読むことを強くお勧めします。
Yiiはその [サービスロケータ](concept-service-locator.md) を、依存性注入(DI)コンテナの上に実装しています。
サービスロケータは、新しいオブジェクトのインスタンスを作成しようとしたとき、DI コンテナに呼び出しを転送します。
後者は、依存関係を、上で説明したように自動的に解決します。
イベント
======
イベントを使うと、既存のコードの特定の実行ポイントに、カスタムコードを挿入することができます。イベントにカスタムコードを添付すると、
イベントがトリガされたときにコードが自動的に実行されます。たとえば、メーラーオブジェクトがメッセージを正しく送信できたとき、
`messageSent` イベントをトリガするとします。もしメッセージの送信がうまく行ったことを知りたければ、単に `messageSent`
イベントにトラッキングコードを付与すするだけで、それが可能になります。
Yiiはイベントをサポートするために、 [[yii\base\Component]] と呼ばれる基底クラスを導入してします。クラスがイベントをトリガする必要がある場合は、
[[yii\base\Component]] もしくはその子クラスを継承する必要があります。
イベントハンドラ <a name="event-handlers"></a>
--------------
イベントハンドラとは、関連するイベントがトリガされたときに実行される、 [PHP コールバック](http://www.php.net/manual/en/language.types.callable.php)
です。次のコールバックのいずれも使用可能です:
- 文字列で指定されたグローバル PHP 関数 (括弧を除く) `'trim'` など
- オブジェクトとメソッド名文字列の配列で指定された、オブジェクトのメソッド (括弧を除く) `[$object, 'methodName']` など
- クラス名文字列とメソッド名文字列の配列で指定された、静的なクラスメソッド `[$class, 'methodName']` など
- 無名関数 `function ($event) { ... }` など
イベントハンドラのシグネチャはこのようになります:
```php
function ($event) {
// $event は yii\base\Event またはその子クラスのオブジェクト
}
```
`$event` パラメータを介して、イベントハンドラは発生したイベントに関して次の情報を得ることができます:
- [[yii\base\Event::name|イベント名]]
- [[yii\base\Event::sender|イベント送信元]]: `trigger()` メソッドを呼び出したオブジェクト
- [[yii\base\Event::data|カスタムデータ]]: イベントハンドラを接続するときに提供されたデータ (後述)
イベントハンドラのアタッチ <a name="attaching-event-handlers"></a>
------------------------
イベントハンドラは [[yii\base\Component::on()]] を呼び出すことでアタッチできます。たとえば:
```php
$foo = new Foo;
// このハンドラはグローバル関数です
$foo->on(Foo::EVENT_HELLO, 'function_name');
// このハンドラはオブジェクトのメソッドです
$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);
// このハンドラは静的なクラスメソッドです
$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);
// このハンドラは無名関数です
$foo->on(Foo::EVENT_HELLO, function ($event) {
// イベント処理ロジック
});
```
また、 [構成情報](concept-configurations.md) を通じてイベントハンドラをアタッチすることもできます。詳細については
[構成情報](concept-configurations.md) の章を参照してください。
イベントハンドラをアタッチするとき、 [[yii\base\Component::on()]] の3番目のパラメータとして、付加的なデータを提供することができます。
そのデータは、イベントがトリガされてハンドラが呼び出されるときに、ハンドラ内で利用きます。たとえば:
```php
// 次のコードはイベントがトリガされたとき "abc" を表示します
// "on" に3番目の引数として渡されたデータを $event->data が保持しているからです
$foo->on(Foo::EVENT_HELLO, 'function_name', 'abc');
function function_name($event) {
echo $event->data;
}
```
イベントハンドラの順序
-------------------
ひとつのイベントには、ひとつだけでなく複数のハンドラをアタッチすることができます。イベントがトリガされると、アタッチされたハンドラは、
それらがイベントにアタッチされた順序どおりに呼び出されます。あるハンドラがその後に続くハンドラの呼び出しを停止する必要がある場合は、
`$event` パラメータの [[yii\base\Event::handled]] プロパティを true に設定します:
```php
$foo->on(Foo::EVENT_HELLO, function ($event) {
$event->handled = true;
});
```
デフォルトでは、新たに接続されたハンドラは、イベントの既存のハンドラのキューに追加されます。その結果、
イベントがトリガされたとき、そのハンドラは一番最後に呼び出されます。もし、そのハンドラが最初に呼び出されるよう、
ハンドラのキューの先頭に新しいハンドラを挿入したい場合は、[[yii\base\Component::on()]] を呼び出とき、4番目のパラメータ `$append` に false を渡します:
```php
$foo->on(Foo::EVENT_HELLO, function ($event) {
// ...
}, $data, false);
```
イベントのトリガー <a name="triggering-events"></a>
-----------------
イベントは、 [[yii\base\Component::trigger()]] メソッドを呼び出すことでトリガされます。このメソッドには **イベント名** が必須で、
オプションで、イベントハンドラに渡されるパラメータを記述したイベントオブジェクトを渡すこともできます。たとえば:
```php
namespace app\components;
use yii\base\Component;
use yii\base\Event;
class Foo extends Component
{
const EVENT_HELLO = 'hello';
public function bar()
{
$this->trigger(self::EVENT_HELLO);
}
}
```
上記のコードでは、すべての `bar()` の呼び出しは、 `hello` という名前のイベントをトリガします。
> Tip: イベント名を表すときはクラス定数を使用することをお勧めします。上記の例では、定数 `EVENT_HELLO` は
`hello` イベントを表しています。このアプローチには 3 つの利点があります。まず、タイプミスを防ぐことができます。次に、IDE の自動補完サポートでイベントを
認識できるようになります。第 3 に、クラスでどんなイベントがサポートされているかを表したいとき、定数の宣言をチェックするだけで済みます。
イベントをトリガするとき、イベントハンドラに追加情報を渡したいことがあります。たとえば、メーラーが `messageSent` イベントのハンドラに
メッセージ情報を渡して、ハンドラが送信されたメッセージの詳細を知ることができるようにしたいかもしれません。
これを行うために、 [[yii\base\Component::trigger()]] メソッドの2番目のパラメータとして、イベントオブジェクトを与えることができます。
イベントオブジェクトは [[yii\base\Event]] クラスあるいはその子クラスのインスタンスでなければなりません。たとえば:
```php
namespace app\components;
use yii\base\Component;
use yii\base\Event;
class MessageEvent extends Event
{
public $message;
}
class Mailer extends Component
{
const EVENT_MESSAGE_SENT = 'messageSent';
public function send($message)
{
// ... $message 送信 ...
$event = new MessageEvent;
$event->message = $message;
$this->trigger(self::EVENT_MESSAGE_SENT, $event);
}
}
```
[[yii\base\Component::trigger()]] メソッドが呼び出されたとき、この名前を付けられたイベントに
アタッチされたハンドラがすべて呼び出されます。
イベントハンドラのデタッチ <a name="detaching-event-handlers"></a>
------------------------
イベントからハンドラを取り外すには、 [[yii\base\Component::off()]] メソッドを呼び出します。たとえば:
```php
// このハンドラはグローバル関数です
$foo->off(Foo::EVENT_HELLO, 'function_name');
// このハンドラはオブジェクトのメソッドです
$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']);
// このハンドラは静的なクラスメソッドです
$foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);
// このハンドラは無名関数です
$foo->off(Foo::EVENT_HELLO, $anonymousFunction);
```
一般的には、イベントにアタッチされたときどこかに保存してある場合を除き、無名関数を取り外そうとはしないでください。
上記の例は、無名関数は変数 `$anonymousFunction` として保存されていたものとしています。
イベントからすべてのハンドラを取り外すには、単純に、第 2 パラメータを指定せずに [[yii\base\Component::off()]] を呼び出します。
```php
$foo->off(Foo::EVENT_HELLO);
```
クラスレベル・イベントハンドラ <a name="class-level-event-handlers"></a>
--------------------------
ここまでの項では、 *インスタンスレベル* でのイベントにハンドラをアタッチする方法を説明してきました。
場合によっては、特定のインスタンスだけではなく、クラスのすべてのインスタンスがトリガした
イベントに応答したいことがあります。すべてのインスタンスにイベントハンドラをアタッチする代わりに、静的メソッド
[[yii\base\Event::on()]] を呼び出すことで、 *クラスレベル* でハンドラをアタッチすることができます。
たとえば、[アクティブレコード](db-active-record.md) オブジェクトは、データベースに新しいレコードを挿入するたびに、
[[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] イベントをトリガします。 *すべての*
[アクティブレコード](db-active-record.md) オブジェクトによって行われる挿入を追跡するには、次のコードが使えます:
```php
use Yii;
use yii\base\Event;
use yii\db\ActiveRecord;
Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
Yii::trace(get_class($event->sender) . ' が挿入されました');
});
```
[[yii\db\ActiveRecord|ActiveRecord]] またはその子クラスのいずれかが、 [[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]]
をトリガーするといつでも、このイベントハンドラが呼び出されます。ハンドラの中では、 `$event->sender` を通して、
イベントをトリガしたオブジェクトを取得することができます。
オブジェクトがイベントをトリガするときは、最初にインスタンスレベルのハンドラを呼び出し、続いてクラスレベルのハンドラとなります。
静的メソッド [[yii\base\Event::trigger()]] を呼び出すことによって、 *クラスレベル* でイベントをトリガすることができます。
クラスレベルでのイベントは、特定のオブジェクトに関連付けられていません。そのため、これはクラスレベルのイベントハンドラだけを
呼び出します。たとえば:
```php
use yii\base\Event;
Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) {
echo $event->sender; // "app\models\Foo" を表示
});
Event::trigger(Foo::className(), Foo::EVENT_HELLO);
```
この場合、`$event->sender` は、オブジェクトインスタンスではなく、イベントをトリガーするクラスの名前を指すことに注意してください。
> 注: クラスレベルのハンドラは、そのクラスのあらゆるインスタンス、またはあらゆる子クラスのインスタンスがトリガしたイベントに応答
してしまうため、よく注意して使わなければなりません。 [[yii\base\Object]] のように、クラスが低レベルの基底クラスの場合は特にそうです。
クラスレベルのイベントハンドラを取り外すときは、 [[yii\base\Event::off()]] を呼び出します。たとえば:
```php
// $handler をデタッチ
Event::off(Foo::className(), Foo::EVENT_HELLO, $handler);
// Foo::EVENT_HELLO のすべてのハンドラをデタッチ
Event::off(Foo::className(), Foo::EVENT_HELLO);
```
グローバル・イベント <a name="global-events"></a>
-------------
Yiiは、実際に上記のイベントメカニズムに基づいたトリックである、いわゆる *グローバル・イベント* をサポートしています。
グローバル・イベントは、 [アプリケーション](structure-applications.md) インスタンス自身などの、グローバルにアクセス可能なシングルトンを必要とします。
グローバルイベントを作成するには、イベント送信者は、送信者の自前の `trigger()` メソッドを呼び出す代わりに、シングルトンの
`trigger()` メソッドを呼び出してイベントをトリガします。同じく、イベントハンドラも、シングルトンのイベントにアタッチされます。たとえば:
```php
use Yii;
use yii\base\Event;
use app\components\Foo;
Yii::$app->on('bar', function ($event) {
echo get_class($event->sender); // "app\components\Foo" を表示
});
Yii::$app->trigger('bar', new Event(['sender' => new Foo]));
```
グローバルイベントを使用する利点は、オブジェクトによってトリガされるイベントハンドラを設けたいとき、オブジェクトがなくてもいい
ということです。その代わりに、ハンドラのアタッチとイベントのトリガはともに、(アプリケーションのインスタンスなど) シングルトンを
介して行われます。
しかし、グローバルイベントの名前空間はあらゆる部分から共有されているので、名前空間の整理 ("frontend.mail.sent"、"backend.mail.sent" など)
を導入するというような、賢いグローバルイベントの名前付けをする必要があります。
プロパティ
==========
PHPでは、クラスのメンバ変数は *プロパティ* とも呼ばれます。これらの変数は、クラス定義の一部で、クラスのインスタンスの状態を表すために
(すなわち、クラスのあるインスタンスを別のものと区別するために) 使用されます。現実によく、特別な方法でこのプロパティの読み書きを扱いたい
場合があります。たとえば、`label` プロパティに割り当てられる文字列が常にトリミングされるようにしたい、など。その仕事を成し遂げるために、
あなたは次のようなコードを使ってきたのではありませんか:
```php
$object->label = trim($label);
```
上記のコードの欠点は、 `label` プロパティを設定するすべてのコードで、`trim()` を呼び出す必要があるということです。もし将来的に、
`label` プロパティに、最初の文字を大文字にしなければならない、といった新たな要件が発生したら、 `label` に値を代入するすべてのコードを変更しなければなりません。コー​​ドの繰り返しはバグを誘発するので、できれば避けたいところです。
この問題を解決するために、Yii は *getter* メソッドと *setter* メソッドをベースにしたプロパティ定義をサポートする、 [[yii\base\Object]] 基底クラスを提供します。
クラスがその機能を必要とするなら、 [[yii\base\Object]] またはその子クラスを継承しましょう。
> 補足: Yiiのフレームワークのほぼすべてのコアクラスは、 [[yii\base\Object]] またはその子クラスを継承しています。
これは、コアクラスに getter または setter があれば、それをプロパティのように使用できることを意味します。
getter メソッドは、名前が `get` で始まるメソッドで、setter メソッドは、`set` で始まるメソッドです。
`get` または `set` プレフィクスの後の名前で、プロパティ名を定義します。次のコードに示すように、たとえば、`getLabel()` という getter と `setLabel()` という setter は、
`label` という名前のプロパティを定義します:
```php
namespace app\components;
use yii\base\Object;
class Foo extends Object
{
private $_label;
public function getLabel()
{
return $this->_label;
}
public function setLabel($value)
{
$this->_label = trim($value);
}
}
```
(詳しく言うと、getter および setter メソッドは、この場合には、内部的に `_label` と名付けられた private 属性を参照する `label` プロパティを作っています。)
getter と setter によって定義されたプロパティは、クラスのメンバ変数のように使用することができます。主な違いは、
それらのプロパティが読み取りアクセスされるときは、対応する getter ソッドが呼び出されることであり、プロパティに値が割り当てられるときには、
対応する setter メソッドが呼び出されるということです。例:
```php
// $label = $object->getLabel(); と同じ
$label = $object->label;
// $object->setLabel('abc'); と同じ
$object->label = 'abc';
```
setter なしの getter で定義されたプロパティは、 *読み取り専用* です。そのようなプロパティに値を代入しようとすると、
[[yii\base\InvalidCallException|InvalidCallException]] が発生します。同様に、getter なしの setter で定義されたプロパティは、
*書き込み専用* で、そのようなプロパティを読み取りしようとしても、例外が発生します。書き込み専用のプロパティを持つのは一般的ではありませんが。
getter と ​​setter で定義されたプロパティには、いくつかの特別なルールと制限があります:
* この種のプロパティでは、名前の *大文字と小文字を区別しません* 。たとえば、 `$object->label``$object->Label` は同じです。
これは、PHPのメソッド名が大文字と小文字を区別しないためです。
* この種のプロパティの名前と、クラスのメンバ変数の名前とが同じである場合、後者が優先されます。
たとえば、上記の `Foo` クラスがもしメンバ変数 `label` を持っているとすると、`$object->label = 'abc'`
という代入は *メンバ変数の* `label` に作用することになり、その行で `setLabel()` setter メソッドは呼び出されなくなります。
* これらのプロパティは可視性をサポートしていません。プロパティが public、protected、private であるかどうかで、
getter または setter メソッドの定義に違いは生じません。
* プロパティは、 *静的でない* getter および setter でしか定義できません。静的メソッドは同じようには扱われません。
このガイドの冒頭で説明した問題に戻ると、 `label` に値が代入されているあらゆる箇所で `trim()` を呼ぶのではなく、もう `setLabel()` という setter の内部だけで `trim()` を呼べば済むのです。
さらに、新しい要求でラベルの先頭を大文字にする必要が発生しても、他のいっさいのコードに触れることなく、すぐに `setLabel()` メソッドを変更することができます。一箇所の変更は、すべての `label` への代入に普遍的に作用します。
サービスロケータ
===============
サービスロケータは、アプリケーションが必要とする可能性のある各種のサービス (またはコンポーネント) を提供する方法を知っているオブジェクトです。
サービスロケータ内では、各コンポーネントは単一のインスタンスとして存在し、IDによって一意に識別されます。
あなたは、このIDを使用してサービスロケータからコンポーネントを取得できます。
Yii では、サービスロケータは単純に [[yii\di\ServiceLocator]] のインスタンス、または子クラスのインスタンスです。
Yii の中で最も一般的に使用されるサービスロケータは、 *アプリケーション* オブジェクトで、 `\Yii::$app`
を通じてアクセスできます。それが提供するサービスは、 *アプリケーションコンポーネント* と呼ばれ、それは `request`
`response``urlManager` のようなコンポーネントです。あなたはサービスロケータによって提供される機能を通じて、
簡単に、これらのコンポーネントを構成、あるいは独自の実装に置き換え、といったことができます。
アプリケーションオブジェクトの他に、各モジュールオブジェクトもまたサービスロケータです。
サービスロケータを使用する最初のステップは、コンポーネントを登録することです。コンポーネントは、 [[yii\di\ServiceLocator::set()]]
を通じて登録することができます。次のコードは、コンポーネントを登録するさまざまな方法を示しています。
```php
use yii\di\ServiceLocator;
use yii\caching\FileCache;
$locator = new ServiceLocator;
// コンポーネントの作成に使われるクラス名を使用して "cache" を登録
$locator->set('cache', 'yii\caching\ApcCache');
// コンポーネントの作成に使われる構成情報配列を使用して "db" を登録
$locator->set('db', [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=demo',
'username' => 'root',
'password' => '',
]);
// コンポーネントを構築する匿名関数を使って "search" を登録
$locator->set('search', function () {
return new app\components\SolrService;
});
// コンポーネントを使って "pageCache" を登録
$locator->set('pageCache', new FileCache);
```
いったんコンポーネントが登録されたら、次の 2 つの方法のいずれかで、その ID を使ってそれにアクセスすることができます:
```php
$cache = $locator->get('cache');
// または代わりに
$cache = $locator->cache;
```
以上のように、 [[yii\di\ServiceLocator]] はコンポーネント ID を使用したプロパティのように、コンポーネントにアクセスすることができます。
あなたが最初にコンポーネントにアクセスしたとき、 [[yii\di\ServiceLocator]] はコンポーネントの登録情報を使用してコンポーネントの新しいインスタンスを作成し、
それを返します。後でそのコンポーネントが再度アクセスされた場合、サービスロケータは同じインスタンスを返します。
[[yii\di\ServiceLocator::has()]] を使って、コンポーネント ID がすでに登録されているかをチェックできます。
無効なIDで [[yii\di\ServiceLocator::get()]] を呼び出した場合、例外がスローされます。
サービスロケータは多くの場合、 [構成情報](concept-configurations.md) で作成されるため、
[[yii\di\ServiceLocator::setComponents()|components]] という名前の書き込み可能プロパティが提供されています。
これで一度に複数のコンポーネントを設定して登録することができます。次のコードはアプリケーションを構成する構成情報配列を示しており、
"db" と "cache" と "search" コンポーネントの登録もしています:
```php
return [
// ...
'components' => [
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=demo',
'username' => 'root',
'password' => '',
],
'cache' => 'yii\caching\ApcCache',
'search' => function () {
return new app\components\SolrService;
},
],
];
```
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment