concept-events.md 14.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
イベント
======

イベントを使うと、既存のコードの特定の実行ポイントに、カスタムコードを挿入することができます。イベントにカスタムコードを添付すると、
イベントがトリガされたときにコードが自動的に実行されます。たとえば、メーラーオブジェクトがメッセージを正しく送信できたとき、
`messageSent` イベントをトリガするとします。もしメッセージの送信がうまく行ったことを知りたければ、単に `messageSent`
イベントにトラッキングコードを付与すするだけで、それが可能になります。

Yiiはイベントをサポートするために、 [[yii\base\Component]] と呼ばれる基底クラスを導入してします。クラスがイベントをトリガする必要がある場合は、
[[yii\base\Component]] もしくはその子クラスを継承する必要があります。


13
イベントハンドラ <span id="event-handlers"></span>
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
--------------

イベントハンドラとは、関連するイベントがトリガされたときに実行される、 [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|カスタムデータ]]: イベントハンドラを接続するときに提供されたデータ (後述)


39
イベントハンドラのアタッチ <span id="attaching-event-handlers"></span>
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
------------------------

イベントハンドラは [[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);
```

101
イベントのトリガー <span id="triggering-events"></span>
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
-----------------

イベントは、 [[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()]] メソッドが呼び出されたとき、この名前を付けられたイベントに
アタッチされたハンドラがすべて呼び出されます。


165
イベントハンドラのデタッチ <span id="detaching-event-handlers"></span>
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
------------------------

イベントからハンドラを取り外すには、 [[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);
```


194
クラスレベル・イベントハンドラ <span id="class-level-event-handlers"></span>
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
--------------------------

ここまでの項では、 *インスタンスレベル* でのイベントにハンドラをアタッチする方法を説明してきました。
場合によっては、特定のインスタンスだけではなく、クラスのすべてのインスタンスがトリガした
イベントに応答したいことがあります。すべてのインスタンスにイベントハンドラをアタッチする代わりに、静的メソッド
[[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);
```


252
グローバル・イベント <span id="global-events"></span>
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
-------------

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" など)
を導入するというような、賢いグローバルイベントの名前付けをする必要があります。