CMS設計におけるMVCEアーキテクチャの概要

# CMSの設計

CMSを作るにあたって、アーキテクチャが必要になった。既存のアーキテクチャを採用するのも退屈なのでイベントドリブンを組み合わせたMVCEアーキテクチャというものを編み出した。MVCE自体はすでにあるかもしれないが、思いのほかすっぽりハマってくれたのでそのドキュメントを書くことにする。

## MVCEアーキテクチャ

MVCEは以下の要素で成り立つ。

* Model
* View
* Controller
* Event

既存のMVCにEventを足しただけのアーキテクチャである。なぜEventが必要なのかと言うと、CMSではプラグインとテーマと連携を取る必要がある。つまりユーザーがプラグインやテーマをCMSに導入してそれで機能を拡張すると言うことである。その連携にEventを使う。つまりイベント駆動でプラグインとテーマを管理する、というものである。

### Model

モデルはビジネスロジックを書く。

### View

ビューはイベントの発信や発送を行う。

### Controller

コントローラーはリクエストに応じてビューやモデルを呼び出す。

### Event

イベントはプラグインとテーマに送る。

## Webサービスのライフサイクル

HTTPとその上に乗っかるWebサービスのライフライクルは非常に単純である。

1. 初期化
2. 描画
3. 終了

一回のリクエストで上記のライフサイクルが流れていく。Webサービス、HTTPなWebサービスというのは1つのリクエストで1つのレスポンスを返す、ステートレスなモデルであるから、デスクトップアプリのように無限ループで更新処理などはしないため、一回ごとにライフサイクルが必ず終了する。このライフサイクルにイベント駆動を組み込むとどうなるか。

## HTTPにイベント駆動を導入する

イベント駆動というのは誰かがイベントを発信し、そのイベントがウィジェットに流れて、ウィジェットがそのイベントによって振る舞いを変えると言うものである。アプリケーションが描画せよというイベントを発信して、テーマがそのイベントを受信して、テーマが描画を行う。このイベントはHTTPの上に乗っかるため、HTTPのライフサイクルの影響を受ける。つまりイベントに必要な要素は以下になる。

* いつ (When)
* だれが (Who)
* だれに (Whom)
* なにを (What)
* やらせるか

この4つの要素の内「いつ」というのはHTTPのライフサイクルの中でいつイベントを発送するか、というものである。
よって例えば初期化を「setup」というWhen、描画を「draw」というWhen、終了を「exit」というWhenに対応させるとする。そうすると以下のようなライフサイクルになる。

1. setup
2. draw
3. exit

ここで重要なのはsetupで発信したイベントは、drawやexitの時に送信し相手に受信させることができる。だが、drawで発信したイベントはsetupで送信し受信させることはできないということである。時間は上から下に流れていくので、時間を巻き戻すことはできない。これがHTTPにおけるイベント駆動の制限であると言える。
つまりイベントは早い時間に発信し、それを遅い時間で発送して受信させる、といのが基本的な考え方である。

## イベントの発信、発送、受信

イベントは発信を行い、それを発送して、相手に受信させる。発信と発送が分かれているのがキモである。つまり、「いつ」相手に届けるかは発送のタイミングであり、発信は「いつ」に関係なく行えると言うことである。

### イベントの発信

イベントの発信では、イベントの4要素（いつ、だれが、だれに、なにを）を格納したオブジェクトを配列に格納する。要素の内「いつ」というのはいつイベントを発送させるか、というタイミングを表す文字列である。「だれが」はイベントの発信者、「だれに」はだれにイベントを受信させるかという文字列。この文字列はたとえば「all」であればすべてのプラグインとテーマに受信させることになるし、個別のプラグインの名前であればそのプラグイン単体に受信させることになる。「なにを」はイベントの受信者になにをやらせるか、ということを表す文字列で、受信者はこの文字列によって対応する関数を呼び出す。

### イベントの発送

「いつ」イベントを発送するかが大事である。たとえばページのヘッダーを描画するタイミングである「draw_page_header」というWhenを考えてみよう。アプリケーションはControllerから呼び出されたViewによってイベントを発送する。ページのヘッダーを描画したいタイミングで「draw_page_header」というWhenに対応付けられているイベントを「だれに」送信する。これによって「draw_page_header」のタイミングで受信者はイベントを受信でき、「なにを」するべきか知る。受信者はHTMLを描画すれば、それは「draw_page_header」のタイミングの描画になる。
イベントの発送はViewで行うが、プラグインやテーマがイベントの発送を行うこともできる。

## 管理サイトの描画

CMSの管理サイトはプラグインで実装するとする。プラグインで実装すると言うことは、プラグインにイベントを発信すると言うことである。そのためViewでは管理サイトへのイベントの発信と発送を行う。管理サイトのプラグインはそのイベントを受信して、必要な描画を行うことになる。

## イベントを管理するHub

イベントを保持し管理するクラスの名前は何でもいいがここでは「Hub」と名付けることにする。このHubのインスタンスはシングルトンでもいいし、コントローラーからビューに渡して行ってもいい。PHPのサンプルコード。

```php:
// イベントの発信
$hub->emit('when', 'who', 'whom', 'what', $args);

// イベントの発送
$hub->send('when');
```

Hubではプラグインとテーマのインスタンスを参照できる必要がある。プラグインとテーマのクラスには「receive」というメソッドの実装を義務付ける。Hubはプラグインなどのreceiveメソッドを呼び出し、イベントを受信させる。PHPのサンプルコード。

```php:
for ($this->plugins as $plugin) {
  if ($plugin->name === $whom) {
    $plugin->receive($event);
  }
}
```

Hubの実装自体はハッシュマップを使えば簡単に行なえる。非常にシンプルな仕組みである。

## プラグイン間のイベントの送受信

このアーキテクチャの特徴としてプラグインやテーマがそれぞれイベントで連携を取れると言うものがある。たとえばプラグインBobからイベントを発信する。

```php:
function setup($hub) {
  // ボブからアリスにイベントを発信
  // after_setupのタイミングで発送される
  $hub->emit('after_setup', 'bob', 'alice', 'hello_world');
}
```

プラグインAliceではこれをreceiveしてイベントを受信する。

```php:
function receive($event) {
  switch ($event->what) {
  case 'hello_world': $this->hello_world(); break;
  }
}
```

イベントを受信したAliceはBobにイベントを返したくなる。これは人の心情である。だが、これはうまくいかない。なぜなら`after_setup`はAliceがイベントを受信した段階で処理中で、もうすぐ終了するからだ。
だからイベントで返事をしたい場合は、コールバックを使うか、プラグイン内でほかのプラグインを参照できるようにするか、違うタイミングにイベントを発信しなければならない。