Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

.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;
    }
suzuki86
ソフトウェアエンジニアです。 最近では、企業のテックブログの新着記事を横断的に見られるサービスを作りました。 https://techpost.wbsrv.net/
https://suzuki86.com
timers
グローバルな家族アプリFammを開発運営しているスタートアップです。
https://timers-inc.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away