トップ  > メモ一覧  > カテゴリ「event dispatcher」の絞り込み結果 : 2件

2件中 1 〜 2 表示  1 

No.2189 Symfony2の秘密兵器:RequestHandler

Symfony 2の秘密兵器: Request Handler


本日第2弾となるこの記事では、今回はリクエストを受けてからレスポンスを返すまでの全体の流れを司る、Request Handlerというコンポーネントをご紹介します。

Request Handlerを知るにあたって、Event Dispatcherコンポーネントを理解しておく必要があります。
先に書いた<Symfony Componentsシリーズ(1)> オブジェクトをつなぐEvent Dispatcherという記事まだ読んでいない方は、そちらから読んでいただければと思います。(読んでもさっぱりわからない!というのであればご連絡ください・・・)

◆ Request Handlerコンポーネントの構成

まず、Request Handlerコンポーネントのディレクトリ構成です。以下のようになります。

  1. Components/RequestHandler:
  2. Exception/  Request.php  RequestHandler.php  RequestInterface.php  Response.php  ResponseInterface.php
  3.  
  4. Components/RequestHandler/Exception:
  5. ForbiddenHttpException.php  HttpException.php  NotFoundHttpException.php  UnauthorizedHttpException.php

symfony 1系には当然RequestクラスとResponseクラスがありますが、それらもまとめてこのRequest Handlerコンポーネントに含まれます。
RequestInterfaceとResponseInterfaceというインターフェースがありますが、現在のところ RequestInterfaceは空っぽ、ResponseInterfaceはsend()メソッドのみのインターフェースなので、Requestや Responseを自前のクラスや他のフレームワークのクラスを使いたい場合も大して難しくはないでしょう。
ただSymfony上で使う場合は、RequestHandler以外の部分で様々なメソッドを使いますので、RequestやResponseを置き換えたい場合は、Request Handlerコンポーネント内のクラスを継承するのが無難かと思われます。

Request Handler用の例外クラスが4つ定義されています。HttpExceptionは他3つの親となる例外クラスです。ただ、定義こそされていますが RequestHandlerが実際に処理中に投げるのはNotFoundHttpExceptionくらいです。その他にSPL例外を投げたりはしま す。


◆ RequestHandlerクラス

ではいよいよ、RequestHandlerクラスを見ていきましょう。全体の流れを司るクラスと聞くと非常に大きなクラスかと思われますが、実際にはコメントを含めても200行に満たない程度の、非常にシンプルなクラスです。
実際に様々な処理を行っているわけではなく、本当に「アプリケーションの流れを形成」しているだけです。メソッドもコンストラクタを含めて4つだけしか定義されていません。


* public function __construct(EventDispatcher $dispatcher)
* public function handle(RequestInterface $request, $main = true)
* public function handleRaw(RequestInterface $request, $main = true)
* protected function filterResponse($response, $message, $main)

これだけです。重要なのがhandle()メソッドです。引数をみると、RequestInterfaceを実装したクラスを受け取っているのがわかります。
Symfony 2ではこのRequestHandlerをKernelが制御します。といっても、RequestHandlerのhandle()メソッドを呼び出す前 にBundleを読み込んで、DIコンテナの設定をする程度で、後はRequest Handlerによって処理が流れていきます。

ではより突っ込んでみていきましょう。


◆ RequestHandler::handle()

  1. <?php
  2.  
  3. public function handle(RequestInterface $request, $main = true)
  4. {
  5.   $main = (Boolean) $main;
  6.  
  7.   try
  8.   {
  9.     return $this->handleRaw($request, $main);
  10.   }
  11.   catch (\Exception $e)
  12.   {
  13.     // exception
  14.     $event = $this->dispatcher->notifyUntil(new Event($this, 'core.exception', array('main_request' => $main, 'request' => $request, 'exception' => $e)));
  15.     if ($event->isProcessed())
  16.     {
  17.       return $this->filterResponse($event->getReturnValue(), 'A "core.exception" listener returned a non response object.', $main);
  18.     }
  19.  
  20.     throw $e;
  21.   }
  22. }

handleRaw()を呼び出して、戻り値をそのまま返すだけのメソッドです。handleRaw()の戻り値はResponseInterfaceを実装したオブジェクトになります。
また、例外が発生した場合はcore.exceptionイベントを通知しています。ここでEventに対してisProcessed()と getReturnValue()という2つのメソッドを実行しています。isProcessed()は登録してあるリスナーのどれかが処理を行って trueを返した場合にtrueを返します。つまりイベントに対して何かしらの処理がされたかをチェックするためのものです。 getReturnValue()はリスナーが指定した戻り値を取得するためのものです。リスナーがreturnした値ではなく、リスナー側 で$event->setReturnValue()を実行してセットされたものになります。

Symfonyはここで、例外に対して例外用の画面を表示するためのレスポンスを作成して返します。filterResponse()メソッドを呼び出し ていますが、これはレスポンスを返す際に必ず通す処理で、レスポンスを返すためのイベントを通知するためのものです。このメソッドには ResponseInterfaceを実装したオブジェクトを渡さないと例外となるようです。詳しくは後ほど見ていきましょう。


◆ RequestHandler::handleRaw()

例外が起きた場合の処理はhandle()メソッドに任せるとして、正常なフローを一通り行うのがこのhandleRaw()です。

ここでいうフローとは、ずばりイベントです。順番に次のようにイベントが呼び出されます。

* core.request
* core.load_controller
* core.controller
* core.view

実際に処理を分割しながらみていきましょう。


### core.request

  1. <?php
  2. // request
  3. $event = $this->dispatcher->notifyUntil(new Event($this, 'core.request',
  4.    array('main_request' => $main, 'request' => $request)));
  5. if ($event->isProcessed())
  6. {
  7.   return $this->filterResponse($event->getReturnValue(),
  8.    'A "core.request" listener returned a non response object.', $main);
  9. }

Symfony側ではこのイベントが発生したら、まずルーティングが行われます。
次のイベントがcore.load_controllerですので、コントローラを読み込む処理だと思われます。それまでに必要なことはこのイベントで行います。

isProcessed()がtrueの場合は、イベントの戻り値をfilterResponse()に渡しています。
filterResponse()に戻り値を渡しているということは、Responseオブジェクトを期待していることになります。
コントローラを読み込む必要がないようなリクエストや、コントローラの処理そのものがキャッシュ場合などは処理を止めてレスポンスを返すのかなと思います。


### core.load_controller

  1. <?php
  2. // load controller
  3. $event = $this->dispatcher->notifyUntil(new Event($this, 'core.load_controller',
  4.    array('main_request' => $main, 'request' => $request)));
  5. if (!$event->isProcessed())
  6. {
  7.   throw new NotFoundHttpException('Unable to find the controller.');
  8. }
  9.  
  10. list($controller, $arguments) = $event->getReturnValue();
  11.  
  12. // controller must be a callable
  13. if (!is_callable($controller))
  14. {
  15.   throw new \LogicException(sprintf('The controller must be a callable (%s).',
  16.      var_export($controller, true)));
  17. }

コントローラをロードする処理ですね。ここでisProcessed()がfalseの場合、つまりコントローラが読み込まれなかった場合はNotFoundHttpExceptionがスローされます。
読み込んだ後は、イベントの戻り値にコントローラとその引数を設定する必要があるようです。
コントローラは関数呼び出し可能でなければならないようですが、Symfonyでは通常、アクションとなるメソッドが指定されると思われます。
シンプルにやりたい場合は、無名関数が戻り値でも十分だということですね。


### core.controller

  1. <?php
  2. // controller
  3. $event = $this->dispatcher->notifyUntil(new Event($this, 'core.controller',
  4.    array('main_request' => $main, 'request' => $request,
  5.    'controller' => &$controller, 'arguments' => &$arguments)));
  6. if ($event->isProcessed())
  7. {
  8.   try
  9.   {
  10.     return $this->filterResponse($event->getReturnValue(),
  11.        'A "core.controller" listener returned a non response object.', $main);
  12.   }
  13.   catch (\Exception $e)
  14.   {
  15.     $retval = $event->getReturnValue();
  16.   }
  17. }
  18. else
  19. {
  20.   // call controller
  21.   $retval = call_user_func_array($controller, $arguments);
  22. }

core.load_controllerで返ってきた$controllerを実際に実行するのが、core.controllerです。
このイベントにリスナーを指定しなくても、コントローラの呼び出しは行われるようです。


### core.view

  1. // view
  2. <?php
  3. $event = $this->dispatcher->filter(new Event($this, 'core.view',
  4.    array('main_request' => $main)), $retval);
  5.  
  6. return $this->filterResponse($event->getReturnValue(),
  7.    sprintf('The controller must return a response (instead of %s).',
  8.      is_object($event->getReturnValue()) ?
  9.        'an object of class '.get_class($e->getReturnValue()) :
  10.        str_replace("\n", '', var_export($event->getReturnValue(), true))
  11.     ),
  12.    $main);

handleRaw()から通知される最後のイベントです。先ほどcore.controllerを行った結果をフィルタリングしています。その後、イベントの戻り値をfilterResponse()に渡しています。
filter()にリスナーが登録されていない場合、filter()に指定した第2引数がそのまま戻り値として設定されます。


説明を一切しなかったのですが、handleとhandleRaw()には第2引数に$mainというものがあります。これはデフォルトではtrueです。falseになるのはどういった状況なのでしょうか。
symfonyをよく知っている方はもしかしたらなんとなくわかるかもしれません。これがfalseで呼び出されるのは、forwardが行われた時です。
Symfonyのforwardとは、同一リクエストの中で、特定のアクションから別のアクションを呼び出す処理のことです。forwardが行われた場合は、再度handle()が呼び出されるようになります。


◆ RequestHandler::filterResponse()

  1. <?php
  2.  
  3. protected function filterResponse($response, $message, $main)
  4. {
  5.   if (!$response instanceof ResponseInterface)
  6.   {
  7.     throw new \RuntimeException($message);
  8.   }
  9.  
  10.   $event = $this->dispatcher->filter(new Event($this, 'core.response', array('main_request' => $main)), $response);
  11.   $response = $event->getReturnValue();
  12.  
  13.   if (!$response instanceof ResponseInterface)
  14.   {
  15.     throw new \RuntimeException('A "core.response" listener returned a non response object.');
  16.   }
  17.  
  18.   return $response;
  19. }

最後にご紹介するのが、散々出てきたfilterResponse()です。まず第1引数がResponseInterfaceを実装していなければ、実行時例外であるRuntimeExceptionが投げられます。
その後、レスポンスオブジェクトに対してfilterを通知しています。handle()の戻り値は例外が最終的にキャッチされなかった場合を除き、常にこのメソッドを通るため、確実にResponseInterfaceを実装したオブジェクトになります。


Request Handlerコンポーネントの紹介は以上です。説明の途中でSymfonyがイベントに対する処理についていくつか記述している箇所があります。
実際にフレームワークとしてコントローラまわりなどの処理は、Symfony\Framework\WebBundleというBundleに含まれていま す。このBundleの中にListenerというディレクトリがあり、そこにいくつかのイベントリスナーが用意されています。これらがSymfonyと しての流れを実際に処理しています。
そこを具体的に話し出すと、この記事が終わらなくなってしまうので本日はご紹介しませんが、そこで行っていること基本的な内容は書いてある通りです。


さて、長くなりましたが、Symfony 2の秘密兵器であるRequest Handlerコンポーネントの説明は以上になります。
イベントによって流れが作られているため、非常に柔軟になっています。この仕組みを知っておくことで、多少は全体が見えやすくなるかと思います。


さて、今回タイトルに、Symfony Componentsシリーズと入れています。今後も様々なコンポーネントを紹介していきたいと思っています。次は重要性を考えるとDependency Injectionでしょうか。Templatingは自分があまり触っていないため、もっと勉強してからになりそうです。それでは皆さま、次回をお楽し みに!

引用元

更新:2010/03/12 10:25 カテゴリ: symfony  > event dispatcher ▲トップ

No.2188 オブジェクトをつなぐEventDispatcher

オブジェクトをつなぐEvent Dispatcher


先日Symfony 2のアプリケーション構成を読むと いう記事で、Symfony 2の大まかなアプリケーションのディレクトリ構成と、KernelやBundleという存在について書きました。Symfony 2を語る上でSymfony Componentsの存在はかかせません。本日は一挙2本立て!Symfony Componentsの中でも特に重要になるEvent DispatcherコンポーネントとRequest Handlerコンポーネントをご紹介します。今回ご紹介するのはEvent Dispatcherコンポーネントです。

ちなみに本日2つ目の記事はこちらです
<Symfony Componentsシリーズ> Symfony 2の秘密兵器: Request Handler

◆ Symfony Components

はじめに、コンポーネントについてご存じない方のためにコンポーネントについて説明します。symfony 1系はオールインワンのフレームワークでした。その中から単体でも使える機能、symfony 1.4の中からいくつか挙げると、Yamlパーサー/ダンパー、ルーティング、フォームなどといったものを独立させたものがコンポーネントです。現在、 GitHubにて開発されているSymfonyのブランチの中には、次のようなコンポーネントが含まれています。

* Console
- コマンドライン・アプリケーション
* DependencyInjection
- DIコンテナ
* EventDispatcher
- Observerパターンの実装
* OutputEscaper
- 出力の自動エスケープ
* RequestHandler
- RequestからResponseまでの流れを統括
* Routing
- ルーティングの制御
* Templating
-フレキシブルなテンプレートエンジン
* Yaml
-YAMLパーサー/ダンパー

その他にも、FormやI18N、Fileなどが今後追加されると思われます。ただ、基本的には単体で動作するものの、一部のコンポーネントは他のコン ポーネントを利用する場合があります。Dependency InjectionやRoutingにて設定ファイルをYAML形式で利用する場合はYAMLコンポーネントが必要になります。次の記事で紹介する Request HandlerはEvent Dispatcherが必須になります。
また、クラスのオートロードを前提とした作りになっています。PHPの名前空間と主要フレームワークの対応についてという記事で大まかな名前空間のルールを定めてるお話をしましたが、それに準拠したオートロードの設定が必要です。もしないのであれば、Symfony\Foundation\UniversalClassLoaderクラスを利用するといいでしょう。

これらのコンポーネントはそれぞれが非常に有用なものになっています。SymfonyがログやキャッシュにZend Frameworkを用いるように、Symfony以外のところで、Symfonyの超便利機能をつまみ食いできるようになっていますので、ぜひぜひ活用 してほしいなあと思います。

Symfonyの開発者であるFabien氏のプレゼン資料も公開されていますので、そちらもぜひ参考にしてみてください。


◆ Event Dispatcher

Event Dispatcherはsymfony 1.2から実装された機能です。内容は現在の1.4に入ってるものも、2.0で搭載されるものも同じです。というのも、このEvent Dispatcherは2つのクラスのみで構成されます。詳細は後述します。

みなさんJavaScriptはご存知ですか?例えば次のようなコードがあるとしましょう。

  1. <!-- (1) -->
  2. <input type="button" id="some_button" value="click me!" />
  3.  
  4. <script type="text/javascript">
  5. //<![CDATA[
  6.  
  7.   // (2)
  8.   function myListener(event) {
  9.     alert('Event [' + event.type + '] has happened!'
  10.       + ' Button#' + event.target.id + ' was clicked.');
  11.   }
  12.  
  13.   // (3)
  14.   document.getElementById('some_button').onclick = myListener;
  15.  
  16. //]]>
  17. </script>

内容としては、(1)で定義したボタンをクリックすると、ポップアップが出てくる、というものです。この仕組みがわかるのであれば、Event Dispatcherもすぐわかることでしょう。
流れを説明しましょう。まず(1)です。これはただのinput要素です。idはsome_buttonになります。次に(2)と(3)です。(2)で myListener()という関数を定義しています。これを(3)の部分で、idがsome_buttonの要素がクリックされたときに呼び出されるよ う設定します。この場合、クリックをイベント、イベントに対して登録したmyListenerをイベントリスナーと呼びます。
(3)で、関数そのものをonclick属性に登録しています。こうすることによって、clickされたときに自動的にmyListenerが呼び出され、引数としてeventオブジェクトが渡されます。
JavaScriptでいうイベントとは、クリックやキーボード操作、ウィンドウの読み込みなど様々なものが定義されており、このeventオブジェクト には、何の操作が行われたか、どの要素に対して行われたか、マウスの座標はどこか、キーボードのどのキーが押されたのかなど、イベントによって様々なプロ パティが定義されます。

ここまでの仕組みをわかっていれば、Event Dispatcherもすぐ理解できるでしょう。Event Dispatcherには以下の2つのクラスが定義されています。

* Event: (2)でmyListenerに渡している引数eventに当たる
* EventDispatcher: イベントリスナーの管理やイベントの通知を行う、JavaScriptに当たる

どのように使われるかみてみましょう。

  1. <?php
  2. use Symfony\Components\EventDispatcher\Event;
  3. use Symfony\Components\EventDispatcher\EventDispatcher;
  4.  
  5. class Button
  6. {
  7.   private $id;
  8.   private $dispatcher;
  9.  
  10.   public function __construct(EventDispatcher $dispatcher, $id)
  11.   {
  12.     $this->dispatcher = $dispatcher;
  13.     $this->id = $id;
  14.   }
  15.  
  16.   public function getId()
  17.   {
  18.     return $this->id;
  19.   }
  20.  
  21.   public function click()
  22.   {
  23.     // notify button.click
  24.     $this->dispatcher->notify(new Event($this, 'button.click', array('foo' => 'bar')));
  25.   }
  26. }
  27.  
  28. // (2)
  29. function myListener(Event $event)
  30. {
  31.   echo sprintf("Event [%s] has happend!", $event->getName());
  32.  
  33.   $button = $event->getSubject();
  34.   echo sprintf(" Button#%s was clicked.", $button->getId());
  35.  
  36.   // custom property
  37.   $foo = $event['foo']; // alias: $event->getParameter('foo')
  38.   echo sprintf("(foo: %s)", $foo);
  39. }
  40.  
  41. $dispatcher = new EventDispatcher();
  42.  
  43. // (1)
  44. $button = new Button($dispatcher, 'some_button');
  45.  
  46. $button->click();
  47. # nothing happens...
  48.  
  49. // (3)
  50. $dispatcher->connect('button.click', 'myListener');
  51.  
  52. $button->click();
  53. # => Event [button.click] has happend! Button#some_button was clicked.(foo: bar)

クラスを書いたりしてたので長くなりました。先ほどのJavaScriptと極力同じようなものにしてみました。Buttonというクラスはボタン要素を表すイメージです。コンストラクタでEventDispatcherを受け取るようにしています。
次にmyListener()関数を定義しています。これはJavaScriptの(2)と同じです。

(1)ではButtonオブジェクトを生成しています。第2引数にボタンのIDを指定しています。先ほどと同じsome_buttonにします。
この直後にclick()メソッドを呼び出しています。click()メソッドの内部で、 $this->dispatcher->notify(new Event($this, 'button.click')) としていますが、これはbutton.clickというイベントが発生したことをEventDispatcherに通知(notify)するためのもので す。Eventオブジェクトを作る際、第1引数が通知元となるオブジェクト(サブジェクト)、第2引数がイベント名です。イベント名は通常.(ドット)で 区切って名前空間を持たせます。第3引数は必須ではありませんが、好きなパラメータを連想配列で渡すことが可能です。

その後、(3)で先ほど作ったEventDispatcherオブジェクトからconnect()というメソッドを実行しています。 button.clickというイベントが実行されたらイベントリスナーとしてmyListener()関数を呼び出すための設定です。(3)でイベント リスナーの登録を行った後、再度click()メソッドを実行すると、またbutton.clickというイベントが発生します。今度はイベントリスナー としてmyListener()が定義されていますので、myListener()が実行され、メッセージが出力されます。イベントリスナーの内部で引 数$eventに対してgetName()とやるとイベント名の取得が、getSubject()メソッドを実行すると呼び出し元のオブジェクトが取得可 能です。また配列形式でアクセスすると任意のパラメータが取得可能です。


なんとなくEvent Dispatcherの仕組みがわかりましたか?注目してほしいのは、Buttonクラスの定義は一切変えずに、リスナー(myFunction()関数)を登録する前と後で処理の内容が変わるところです。
通常Symfonyではフレームワーク全体で1つのEventDispatcherオブジェクトを共有しています。そしてSymfonyの内部の様々なところでイベントの通知が行われています。
イベントを定義することで、要するにクラスの定義を変えずに処理を外部から組み込むことが可能になります。つまり、SymfonyにはSymfonyそのものを拡張するポイントがいくつも設置されています。

通知方法は先ほどのnotify()も含めて3種類あります。

* notify($event)
- 登録されている全てのリスナーに順番に通知
* notifyUntil($event)
- 登録されているリスナーに順番に通知するが、リスナーがtrueを返した場合は通知を中断する
* filter($event, $value)
- 登録されているリスナーに順番に$valueを渡して値のフィルタリングを行う

Eventには戻り値の設定が可能です。例えばsymfony 1.4には、request.method_not_foundというイベントが定義されています。このイベントはRequestの__call()メ ソッドから通知されます。__call()は定義されていないメソッド呼び出しによって呼び出されるものです。 request.method_not_foundイベントに対して、呼び出されたメソッド名に応じて特定の処理をして返すリスナーを定義しておけば、つ まりはクラス定義をそのままに、メソッドの追加が可能になるわけです。この場合、メソッド名に対する処理が済んでおり後続のリスナーに通知する必要がない ので、通知方法はnotifyUntilが用いられています。
method_not_foundというイベントはこの他にも様々なところで定義されています。sfComponent(sfActionの親クラス)に はcomponent.method_not_found、responseにはresponse.method_not_foundが定義されていま す。

filterは特定の値をフィルタリングするためのものです。例えばresponse.filter_contentイベントが挙げられます。このイベン トではレスポンスとして返すHTMLなどの内容に対してフィルタリングをかけることが可能です。実際にsymfony 1.4ではこのイベントを利用して行っていることがあります。symfonyユーザーおなじみのWebデバッグツールバーをHTMLに組み込む処理です。 この他だと、Formにbind()したときに、バインドした値をフィルタリングするためのform.filter_valuesなどがあります。

Event Dispatcherを利用することにより、継承を使わずに様々な拡張が可能です。
symfonyのドキュメントにイベントの一覧が用意されていますので、ぜひ1度見てみるといいかと思います。

このEvent Dispatcherの仕組みを知ることで、symfonyでできることがぐっと広がると思います。


では次に、Request Handlerコンポーネントをみていきましょう!
<Symfony Componentsシリーズ(2)> Symfony 2の秘密兵器: Request Handler

引用元

更新:2010/03/12 10:24 カテゴリ: symfony  > event dispatcher ▲トップ
2件中 1 〜 2 表示  1 

FuelPHP

Mac

web開発

プロマネ

マネタイズ

プレゼン

webサービス運用

webサービス

Linux

サーバ管理

MySQL

ソース・開発

svn・git

PHP

HTML・CSS

JavaScript

ツール, ライブラリ

ビジネス

テンプレート

負荷・チューニング

Windows

メール

メール・手紙文例

CodeIgniter

オブジェクト指向

UI・フロントエンド

cloud

マークアップ・テキスト

Flash

デザイン

DBその他

Ruby

PostgreSQL

ユーティリティ・ソフト

Firefox

ハードウェア

Google

symfony

OpenPNE全般

OpenPNE2

Hack(賢コツ)

OpenPNE3

リンク

個人開発

その他

未確認

KVS

ubuntu

Android

負荷試験

オープンソース

社会

便利ツール

マネー

Twig

食品宅配

WEB設計

オーディオ

一般常識

アプリ開発

サイトマップ

うずら技術ブログ

たませんSNS

rss2.0