LoginSignup
17
21

More than 5 years have passed since last update.

CakePHP 2.7.5 のリクエストの受け付けからビューのレンダリングまでのおおまかな流れ

Posted at

.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.phplib/Cake/Core/App.phplib/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;
    }
17
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
21