.htaccess
.htaccess によりリクエストは app/webroot/index.php
に集約されます。
.htaccess
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule ^$ app/webroot/ [L]
RewriteRule (.*) app/webroot/$1 [L]
</IfModule>
app/webroot/index.php
いくつかの定数の定義が行われたのちに lib/Cake/bootstrap.php
が include されます。処理の最後に Dispatcher::dispatch メソッドに CakeRequest クラスと CakeResponse クラスのインスタンスを引数として渡し実行します。
app/webroot/index.php
<?php
// Some processes
if (!defined('CAKE_CORE_INCLUDE_PATH')) {
if (function_exists('ini_set')) {
ini_set('include_path', ROOT . DS . 'lib' . PATH_SEPARATOR . ini_get('include_path'));
}
if (!include 'Cake' . DS . 'bootstrap.php') {
$failed = true;
}
} else {
if (!include CAKE_CORE_INCLUDE_PATH . DS . 'Cake' . DS . 'bootstrap.php') {
$failed = true;
}
}
if (!empty($failed)) {
trigger_error("CakePHP core could not be found. Check the value of CAKE_CORE_INCLUDE_PATH in APP/webroot/index.php. It should point to the directory containing your " . DS . "cake core directory and your " . DS . "vendors root directory.", E_USER_ERROR);
}
App::uses('Dispatcher', 'Routing');
$Dispatcher = new Dispatcher();
$Dispatcher->dispatch(
new CakeRequest(),
new CakeResponse()
);
bootstrap.php
いくつかの定数の定義が行われたのちに lib/Cake/basics.php
、lib/Cake/Core/App.php
、lib/Cake/Error/exceptions.php
が require されます。また spl_autoload_register 関数で、まだ読み込みされていないクラスが呼ばれた場合に自動的に App::load メソッドが実行され、事前に App::uses メソッドで登録されたクラスが読み込まれるようにしています。
lib/Cake/basics.php
<?php
// Some processes
require CAKE . 'basics.php';
require CAKE . 'Core' . DS . 'App.php';
require CAKE . 'Error' . DS . 'exceptions.php';
spl_autoload_register(array('App', 'load'));
// Some processes
lib/Cake/Core/App.php
public static function uses($className, $location) {
static::$_classMap[$className] = $location;
}
public static function load($className) {
if (!isset(static::$_classMap[$className])) {
return false;
}
if (strpos($className, '..') !== false) {
return false;
}
$parts = explode('.', static::$_classMap[$className], 2);
list($plugin, $package) = count($parts) > 1 ? $parts : array(null, current($parts));
$file = static::_mapped($className, $plugin);
if ($file) {
return include $file;
}
$paths = static::path($package, $plugin);
if (empty($plugin)) {
$appLibs = empty(static::$_packages['Lib']) ? APPLIBS : current(static::$_packages['Lib']);
$paths[] = $appLibs . $package . DS;
$paths[] = APP . $package . DS;
$paths[] = CAKE . $package . DS;
} else {
$pluginPath = CakePlugin::path($plugin);
$paths[] = $pluginPath . 'Lib' . DS . $package . DS;
$paths[] = $pluginPath . $package . DS;
}
$normalizedClassName = str_replace('\\', DS, $className);
foreach ($paths as $path) {
$file = $path . $normalizedClassName . '.php';
if (file_exists($file)) {
static::_map($file, $className, $plugin);
return include $file;
}
}
return false;
}
Dispatcher::dispatch メソッド
まず Dispatcher.beforeDispatch
という名前のイベントを実行します。Dispatcher.beforeDispatch
イベントには Dispatcher::implementedEvents
メソッドで返される配列にて Dispatcher::parseParams
メソッドが実行されるように関連付けされています。この段階で Dispatcher::parseParams
メソッドが実行されます。Dispatcher::parseParams
はルーティングを反映して実行するコントローラーとアクションを決定します。
実行するべきコントローラーとアクションを決定したら Dispatcher::_getController
メソッドと Dispatcher::_invoke
メソッドを実行してコントローラーにアクションの実行を依頼します。
lib/Cake/Routing/Dispatcher.php
public function dispatch(CakeRequest $request, CakeResponse $response, $additionalParams = array()) {
$beforeEvent = new CakeEvent('Dispatcher.beforeDispatch', $this, compact('request', 'response', 'additionalParams'));
$this->getEventManager()->dispatch($beforeEvent);
$request = $beforeEvent->data['request'];
if ($beforeEvent->result instanceof CakeResponse) {
if (isset($request->params['return'])) {
return $beforeEvent->result->body();
}
$beforeEvent->result->send();
return null;
}
$controller = $this->_getController($request, $response);
if (!($controller instanceof Controller)) {
throw new MissingControllerException(array(
'class' => Inflector::camelize($request->params['controller']) . 'Controller',
'plugin' => empty($request->params['plugin']) ? null : Inflector::camelize($request->params['plugin'])
));
}
$response = $this->_invoke($controller, $request);
if (isset($request->params['return'])) {
return $response->body();
}
$afterEvent = new CakeEvent('Dispatcher.afterDispatch', $this, compact('request', 'response'));
$this->getEventManager()->dispatch($afterEvent);
$afterEvent->data['response']->send();
}
lib/Cake/Routing/Dispatcher.php
public function parseParams($event) {
$request = $event->data['request'];
Router::setRequestInfo($request);
$params = Router::parse($request->url);
$request->addParams($params);
if (!empty($event->data['additionalParams'])) {
$request->addParams($event->data['additionalParams']);
}
}
Dispatcher::_invoke メソッド
Controller::invokeAction
メソッドでアクションの実行、 Controller::render
メソッドでレンダリング内容の組み立てを行います。コントローラーのアクションに記述されているモデルはマジックメソッドである Controller::__isset
メソッドにて Controller::loadModel
メソッドを使い、呼びだされた時点で読み込まれます。
lib/Cake/Routing/Dispatcher.php
protected function _invoke(Controller $controller, CakeRequest $request) {
$controller->constructClasses();
$controller->startupProcess();
$response = $controller->response;
$render = true;
$result = $controller->invokeAction($request);
if ($result instanceof CakeResponse) {
$render = false;
$response = $result;
}
if ($render && $controller->autoRender) {
$response = $controller->render();
} elseif (!($result instanceof CakeResponse) && $response->body() === null) {
$response->body($result);
}
$controller->shutdownProcess();
return $response;
}
Controller::render メソッド
Controller::_getViewObject
メソッドで View のオブジェクトを取得し、View::render
メソッドでビューのレンダリング内容の組み立てを行います。
lib/Cake/Controller/Controller.php
public function render($view = null, $layout = null) {
$event = new CakeEvent('Controller.beforeRender', $this);
$this->getEventManager()->dispatch($event);
if ($event->isStopped()) {
$this->autoRender = false;
return $this->response;
}
if (!empty($this->uses) && is_array($this->uses)) {
foreach ($this->uses as $model) {
list($plugin, $className) = pluginSplit($model);
$this->request->params['models'][$className] = compact('plugin', 'className');
}
}
$this->View = $this->_getViewObject();
$models = ClassRegistry::keys();
foreach ($models as $currentModel) {
$currentObject = ClassRegistry::getObject($currentModel);
if ($currentObject instanceof Model) {
$className = get_class($currentObject);
list($plugin) = pluginSplit(App::location($className));
$this->request->params['models'][$currentObject->alias] = compact('plugin', 'className');
$this->View->validationErrors[$currentObject->alias] =& $currentObject->validationErrors;
}
}
$this->autoRender = false;
$this->response->body($this->View->render($view, $layout));
return $this->response;
}
CakeResponse::send メソッド
アクションを実行して、ビューのレンダリング内容の組み立てを行ったのち、Dispatcher::dispatch
メソッドの最後に記述されている CakeResponse::send
メソッドでレンダリング内容を出力します。
lib/Cake/Network/CakeResponse.php
public function send() {
if (isset($this->_headers['Location']) && $this->_status === 200) {
$this->statusCode(302);
}
$codeMessage = $this->_statusCodes[$this->_status];
$this->_setCookies();
$this->_sendHeader("{$this->_protocol} {$this->_status} {$codeMessage}");
$this->_setContent();
$this->_setContentLength();
$this->_setContentType();
foreach ($this->_headers as $header => $values) {
foreach ((array)$values as $value) {
$this->_sendHeader($header, $value);
}
}
if ($this->_file) {
$this->_sendFile($this->_file, $this->_fileRange);
$this->_file = $this->_fileRange = null;
} else {
$this->_sendContent($this->_body);
}
}
protected function _sendContent($content) {
echo $content;
}