目次
中編に続いて処理内容を見ていきます。 webroot/index.php
に残されたのは次の4行でした。
1 2 3 4 |
$dispatcher->dispatch( Request::createFromGlobals(), new Response() ); |
Request::createFromGlobals()
はメソッド名から明らかですが、 グローバル変数 $_GET
等 からリクエストオブジェクを作成します。
dispatch
1 2 3 4 5 6 7 8 9 10 11 12 13 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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
/** * Dispatches and invokes given Request, handing over control to the involved controller. If the controller is set * to autoRender, via Controller::$autoRender, then Dispatcher will render the view. * * Actions in CakePHP can be any public method on a controller, that is not declared in Controller. If you * want controller methods to be public and in-accessible by URL, then prefix them with a `_`. * For example `public function _loadPosts() { }` would not be accessible via URL. Private and protected methods * are also not accessible via URL. * * If no controller of given name can be found, invoke() will throw an exception. * If the controller is found, and the action is not found an exception will be thrown. * * @param CakeNetworkRequest $request Request object to dispatch. * @param CakeNetworkResponse $response Response object to put the results of the dispatch into. * @return string|void if `$request['return']` is set then it returns response body, null otherwise * @throws CakeRoutingExceptionMissingControllerException When the controller is missing. */ public function dispatch(Request $request, Response $response) { $beforeEvent = $this->dispatchEvent('Dispatcher.beforeDispatch', compact('request', 'response')); $request = $beforeEvent->data['request']; if ($beforeEvent->result instanceof Response) { if (isset($request->params['return'])) { return $beforeEvent->result->body(); } $beforeEvent->result->send(); return; } $controller = false; if (isset($beforeEvent->data['controller'])) { $controller = $beforeEvent->data['controller']; } if (!($controller instanceof Controller)) { throw new MissingControllerException([ 'class' => $request->params['controller'], 'plugin' => empty($request->params['plugin']) ? null : $request->params['plugin'], 'prefix' => empty($request->params['prefix']) ? null : $request->params['prefix'], '_ext' => empty($request->params['_ext']) ? null : $request->params['_ext'] ]); } $response = $this->_invoke($controller); if (isset($request->params['return'])) { return $response->body(); } $afterEvent = $this->dispatchEvent('Dispatcher.afterDispatch', compact('request', 'response')); $afterEvent->data['response']->send(); } |
dispatchEvent
でイベントを発生させています。 そしてイベントが発生した後の処理結果がレスポンスオブジェクトになっていたら終了します。
dispatchEvent
dispatchEvent
は、 Dispatcher
では定義されておらず、 Dispathcer
で使用されている EventManagerTrait
で定義されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/** * Default class name for new event objects. * * @var string */ protected $_eventClass = 'CakeEventEvent'; /** * Wrapper for creating and dispatching events. * * Returns a dispatched event. * * @param string $name Name of the event. * @param array|null $data Any value you wish to be transported with this event to * it can be read by listeners. * @param object|null $subject The object that this event applies to * ($this by default). * * @return CakeEventEvent */ public function dispatchEvent($name, $data = null, $subject = null) { if ($subject === null) { $subject = $this; } $event = new $this->_eventClass($name, $subject, $data); $this->eventManager()->dispatch($event); return $event; } |
このメソッドが更に EventManager
の dispatch
を呼んでいます。 引数として渡される $event
は CakeEventEvent
のインスタンスです。
CakeEventEvent
のコンストラクタを確認します。
CakeEventEvent
__construct
コンストラクタの引数は、 'Dispatcher.beforeDispatch'
になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/** * Constructor * * ### Examples of usage: * * ``` * $event = new Event('Order.afterBuy', $this, ['buyer' => $userData]); * $event = new Event('User.afterRegister', $UserModel); * ``` * * @param string $name Name of the event * @param object|null $subject the object that this event applies to (usually the object that is generating the event) * @param array|null $data any value you wish to be transported with this event to it can be read by listeners */ public function __construct($name, $subject = null, $data = null) { $this->_name = $name; $this->data = $data; $this->_subject = $subject; } |
インスタンス変数に引数の値を格納しているだけでした。 今回 $data
は 'request'
と 'response'
をキーにもつ連想配列になります。
作成された $event
を引数にして EventManager
の dispatch
メソッド が実行されます。
EventManager
dispatch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/** * Dispatches a new event to all configured listeners * * @param string|CakeEventEvent $event the event key name or instance of Event * @return CakeEventEvent * @triggers $event */ public function dispatch($event) { if (is_string($event)) { $event = new Event($event); } $listeners = $this->listeners($event->name()); if (empty($listeners)) { return $event; } foreach ($listeners as $listener) { if ($event->isStopped()) { break; } $result = $this->_callListener($listener['callable'], $event); if ($result === false) { $event->stopPropagation(); } if ($result !== null) { $event->result = $result; } } return $event; } |
dispatch
の中で listeners
メソッドが実行されます。 引数は $event->name()
ですので 'Dispatcher.beforeDispatch'
です。
listeners
は 呼び出されるべきリスナを、呼び出される順序で配列にして返します。
listenres
1 2 3 4 5 6 7 8 9 10 11 12 13 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 |
/** * Internal flag to distinguish a common manager from the singleton * * @var bool */ protected $_isGlobal = false; /** * Returns a list of all listeners for an eventKey in the order they should be called * * @param string $eventKey Event key. * @return array */ public function listeners($eventKey) { $localListeners = []; if (!$this->_isGlobal) { $localListeners = $this->prioritisedListeners($eventKey); $localListeners = empty($localListeners) ? [] : $localListeners; } $globalListeners = static::instance()->prioritisedListeners($eventKey); $globalListeners = empty($globalListeners) ? [] : $globalListeners; $priorities = array_merge(array_keys($globalListeners), array_keys($localListeners)); $priorities = array_unique($priorities); asort($priorities); $result = []; foreach ($priorities as $priority) { if (isset($globalListeners[$priority])) { $result = array_merge($result, $globalListeners[$priority]); } if (isset($localListeners[$priority])) { $result = array_merge($result, $localListeners[$priority]); } } return $result; } |
呼び出されているメソッド prioritisedListeners
の中身を見てみます。
prioritisedListeners
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * Returns the listeners for the specified event key indexed by priority * * @param string $eventKey Event key. * @return array */ public function prioritisedListeners($eventKey) { if (empty($this->_listeners[$eventKey])) { return []; } return $this->_listeners[$eventKey]; } |
とくに難しいことは行っていません。 イベントキー すなわち 'Dispatcher.beforeDispatch'
や 'Dispatcher.afterDispatch'
に関係するリスナを返すメソッドです。
そして listeners
では $globaListeners
と $localListeners
のリスナーをマージしています。 Eventmanager
はシングルトンっぽいつくりになっていて、 グローバルに扱えるインスタンスと、 個別の用途に使うインスタンスを分けることができるようになっています。 実際のところ今回の処理では $globalListeners
が空なので、 $localListeners
のキーがそのまま $priorities
に入ります。 リスナのキーは優先度(priority) ですので、 $priorities
には int
型 の 優先度の配列になります。
優先度の数値が低いものから順に、そしてグローバルリスナを優先して、リスナの配列を作成しています。
EventManager
の dispatch
に戻り、 isStopped
が false
の場合、すなわちイベントが終了していない場合に _callListener
でリスナを呼び出します。
_callListener
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/** * Calls a listener. * * Direct callback invocation is up to 30% faster than using call_user_func_array. * Optimize the common cases to provide improved performance. * * @param callable $listener The listener to trigger. * @param CakeEventEvent $event Event instance. * @return mixed The result of the $listener function. */ protected function _callListener(callable $listener, Event $event) { $data = $event->data(); $length = count($data); if ($length) { $data = array_values($data); } switch ($length) { case 0: return $listener($event); case 1: return $listener($event, $data[0]); case 2: return $listener($event, $data[0], $data[1]); case 3: return $listener($event, $data[0], $data[1], $data[2]); default: array_unshift($data, $event); return call_user_func_array($listener, $data); } } |
$listener
に渡された値がメソッドの名前になり、実行されます。 実際に渡されるメソッド名は 'handle'
です。 $event
は new Event('Dispatcher.beforeDispatch')
で作成されたイベントです。 $data[0]
が request
で $data[1]
が response
です。
すると _callListener
の 返り値 は フィルタオブジェクト の handle($event, request, response)
になります。 実際のところ、 登録された 4つのフィルタについて $listener
が実行されます。
handle
メソッドは それぞれのフィルタではなく、 その親クラスの DispathcerFilter
で定義されています。 今まで出てきたすべてのフィルタは DispatcherFilter
を継承していました。
handle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** * Handler method that applies conditions and resolves the correct method to call. * * @param CakeEventEvent $event The event instance. * @return mixed */ public function handle(Event $event) { $name = $event->name(); list(, $method) = explode('.', $name); if (empty($this->_config['for']) && empty($this->_config['when'])) { return $this->{$method}($event); } if ($this->matches($event)) { return $this->{$method}($event); } } |
今、 $event->name()
は 'Dispatcher.beforeDispatch'
になりますから、 $name
には 'beforeDispatch'
が入ります。 つまり、 handle
を実行するそれぞれのクラスで定義された beforeDispatch
メソッド が実行されることになります。
それぞれのクラスの beforeDispatch
を見てみましょう。
実行される順で見ると、 AssetFilter
, RoutingFilter
, ControllerFactoryFilter
です。 (DebugBarFilter
は省略します。)
AssetFilter
beforeDispatch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
/** * Checks if a requested asset exists and sends it to the browser * * @param CakeEventEvent $event containing the request and response object * @return CakeNetworkResponse if the client is requesting a recognized asset, null otherwise * @throws CakeNetworkExceptionNotFoundException When asset not found */ public function beforeDispatch(Event $event) { $request = $event->data['request']; $url = urldecode($request->url); if (strpos($url, '..') !== false || strpos($url, '.') === false) { return null; } $assetFile = $this->_getAssetFile($url); if ($assetFile === null || !file_exists($assetFile)) { return null; } $response = $event->data['response']; $event->stopPropagation(); $response->modified(filemtime($assetFile)); if ($response->checkNotModified($request)) { return $response; } $pathSegments = explode('.', $url); $ext = array_pop($pathSegments); $this->_deliverAsset($request, $response, $assetFile, $ext); return $response; } |
Asset ファイル のリクエストだった場合にレスポンスを返します。 Asset ファイル だった場合、 header
メソッド を呼び出したりして適宜処理をします。
RoutingFilter
beforeDispatch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** * Applies Routing and additionalParameters to the request to be dispatched. * If Routes have not been loaded they will be loaded, and config/routes.php will be run. * * @param CakeEventEvent $event containing the request, response and additional params * @return void */ public function beforeDispatch(Event $event) { $request = $event->data['request']; Router::setRequestInfo($request); if (empty($request->params['controller'])) { $params = Router::parse($request->url); $request->addParams($params); } } |
どのコントローラを使うべきなのかを指定しています。
ControllerFactoryFilter
beforeDispatch
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * Resolve the request parameters into a controller and attach the controller * to the event object. * * @param CakeEventEvent $event The event instance. * @return void */ public function beforeDispatch(Event $event) { $request = $event->data['request']; $response = $event->data['response']; $event->data['controller'] = $this->_getController($request, $response); } |
コントローラをインスタンス化して $event->data['controller']
に代入しています。
このようにして設定されたコントローラが、 Dispatcher
の invoke
メソッド で実行されます。 invoke
が実行されると、 Controller
が render
まで実行します。 そして html の生成まで行われます。
あとは、 dispatch
メソッド のところで afterDispatch
を実行して終わります。