【CodeIgniter】CodeIgniterの動き

CodeIgniter Advent Calendar 2015 - Qiita 4日目の記事です。

まーくあっぷenジニアの です。

CodeIgniter3.0 の動き

概要

CodeIgniterってControllerに何か書くと動くけど中を読んでいないからどんな処理してるかわからない!読む時間無い!
という方の為のCodeIgniterのコードのざっくばらんとどんな処理してControllerが呼ばれているかという内容です。

※この記事はCodeIgniter 3.1.0-devを見て書きました。

1.index.phpの動き

1-1 CodeIgniterを設置したときの構成

githubからcloneしてCodeIgnierを設置したディレクトリを見ると
複数のファイルやディレクトリがありますがControllerが動くまでに重要なのは下の三つ。

CodeIgniter
├── application
├── index.php
└── system

CodeIgniterで一番最初に動くのがindex.phpです。
index.phpがどんな動きをしているかというとざっくりと


・エラーの表示をどうするのか定義 (ENVIRONMENT定数)
・system、applicationとかがどこにあるのかの設定(APPPATHとかの定数)
・system/core/CodeIgniter.phpを読み込み


を行っています。

index.phpで/system/core/CodeIgniter.phpを読み込んでいるので当たり前ですがapplication/core/MY_CodeIgniter.phpとファイルを作ってもoverride(置き換え)したりはしません。


2.core/CodeIgniter.phpの動き

index.phpでは/system/core/CodeIgniter.phpを読み込みます。(以下CodeIgniter.php)

CodeIgniter.phpではざっくりと以下の動きをします。

・環境の差分を吸収
・coreクラスの読み込み
・Controllerを動かす
・出力

余談ですが、動いているCodeIgniterのバージョンがわからなければCodeIgniter.phpを見れば定数としてバージョンが定義されています。

2-1 環境の差分を吸収

CodeIgniter.phpでは環境設定の差をある程度吸収しています。
我々ではなじみ深いmbstring系の関数「mb_strlen」では、マルチバイト文字列の文字数をカウントしてくれます。
mbstring系の関数が使えない環境でもエラーがでないように「mb_strlen」関数等が動くように定義されています。
mb_strlen以外にもCodeIgniterを動かす上で必要な差分をある程度吸収しています。


2-2 coreクラスの読み込み

CodeIgniter.phpではCodeIgniterを動かす上で必要なcoreクラスの読み込みを行っています。
もちろん、表でまとめられたcoreクラス ファイルはapplication/core/で上書きが行えます。

読み込み順から

Benchmark パフォーマンス取る
Hooks いろいろなところに処理がはさめる
Config Configデータの管理
Utf8 文字コードUTF-8をサポートしてくれる
URI URIのセグメントとかを管理
Router どのコントローラーのどのメソッドを呼び出すとかのルーティングを管理
Output どんな風に出力するを管理
Security クロスサイトするクリプティングを抑制*1
Input 入力データの管理
Lang 言語設定の管理
Controller 説明不要な便利なやつ

を読み込んでいます。

また、クラスではないので表として記述してていませんがCommon.phpという物も読み込んでいます。
このCommon.phpは関数が多数定義されたファイルですが、core クラス ファイルを読み込む為に必要な関数以外にも便利な関数が揃っているので時間があれば読んでみると良いかもしれません。


2-3 Controllerを動かす

coreクラスを読み込みしたらControllerが動きます。
Controllerを動かすための動作:7

  1. コントローラのどのファイルを呼び出すのかをRouterクラスから取得
  2. ファイル名からControllerのクラス名を先頭1文字のみ大文字として読み込む準備
  3. Routerクラスから読み込む予定のメソッド名の準備
  4. 呼び出せるか確認
    1. Controllerのクラス名が正しい
    2. 呼び出すメソッドの先頭が「_」じゃ無い
    3. 呼び出される予定のメソッドがCI_Controllerに定義されいない
  5. 読み込む準備をされたクラス内に_remapメソッドが定義されている
  6. _remapが定義されていなければController内に読み込む予定のメソッドが存在する
  7. routes configファイルで404エラー用コントローラが定義されていない


フローチャートっぽいもの

7VtLc+I4EP41OiYFfmEfbUJ29rBbW5up2p1TygEHXGMQa0we8+unJXXbku1kSLAdQi0FhWjZenzd/XVLMsyerp9+y+Pt6g++SDJmjRZPzL5iljV2PQ++hORZSXzHUYJlni7wokpwk/5IUDhC6T5dJDvjwoLzrEi3pnDON5tkXhiyOM/5o3nZPc/MXrfxknqsBDfzOGtK/0kXxQqlngtSqviSpMsVdT32AlVzF8+/L3O+32CHzLLv5UtVr2NqDGf6NFI/L7DLZ/yNzW3jjTGiH5yvDUGe7Cr0cLapCcgmfjC6zNLNdxOfO54vkly7yJ6BdnPOoSFRWj9Nk0xomJSnWrp+obZEL082OJRf3IDWsSueSQMSwkTUAyDRqlhnUBxDMXlKi3+F+NLFX9+oZlPkz1qV+CnqRAOq/WTRUHs1RBTt+D6f41Wo7SLOlwleZZfwgPEnfJ1AJ1INWVykD2brMaK8LK+rMIACwtAOCXb9EGd7bJRZXgaDiBbpgwGV999e6CkqkqfiIs7SJVhMKNSc3BdVLZSW4nt8yWY28y0W2bJgVwXfx0J4RVVTkgSyMGbhjAo1ic0CF1sOx1Rw6PYIJYHFZi4LxtQyFKB3l/kTFoUoCRp9+dXtf/N9AZaK7V+bw4NJ0b3lLT5IoP1rFniiEM1YMCEkQQcSTIVMN/haCt9fwgEojJivgDaGO+VgtzzLcJYlDC9NV29IAozTuId2JENmXHl2NVJgJBtezcFLqOANhhCwcASGAGP3BHSgIKgDwwD0atqJYFhQ5bAQqhoXV1CrAVFHqg1lcaDzslXQks/CkGxQNe8LvaHJAEYj0Q/Yi1KpmDWwZX8qtS/fZXWHTMMR0xBwA1xgmeQCpU+VthORS8IbCzXdq7sOxaXGto+rtEhutrEkvkeI6SbnIhclOQD2On82mZFuwIwAEwIK9I9VdHU8lK20wArzO5pKx+5pRhcZSszoIuPgENEFYdUgEaO/wZ88L1Z8yTdxNquk0S9Bo7KEpUKpb9D8gUDDrnsIyRq/1EjkQOKHuyigIqfC7SQJrvsNeJCE6xEL3orNYeBEViGRXjmVKtkA0gOygqihAibxGDAn3q6FB9/5K/z6ha6T8UqEGUmgwVSLJwQUgHkYA+Yrvr7bg3VE/XMheR9xYRsZui1kOB51wYZIxKfGhrgE0B1bjnQINqTh9MyGnQEzFONh1z0w3jFZriK7Fxmi7+z+gPXLUSn4IYlfCVk9XzejAUAGOh7dwod+i1G8kpELKlXECd1NBJo4kr6RtQ9D9nB0/On099ummamYoXJtaHpWLWTLBVsZOGHGeBeN4GUMPiKcYPywnJb4QYmzET8o8BwVP2BNd4rxA8OaTpNyD22I+IGbNZ8vm24BTap3ANCwaz22vB4AjBQxus2TNfjSW/jgDZ4vtiF0ClCJ5AemjJQOosvb5N6ay5cyw+UptzzK5WW+cXouj0ykW6+a7hA+j6vBz+fzLagNlU9i14bPdxz0qUBsgqtpuaz+SAd2aO2GHuxAMtbwYHJW3YPpuqMcGE+TujXWIVwcmcdw8ReA7t7Fm6dSn8TF21CTOcoAqGHfna8ZqZWdOBJtawaONZML8hzRzvhSdF1rBvRTJLtb2LO6T5eX25VKISDi60cVqg+z3z4WPc6I0gvcuSqTHnX2NmEBLMD8xirmqFzmZJczeINHeQwypTtpYcq27bEumBKJUrPedyaaEG5QFSCBDdjhD1zoUB1hnNhNGK22IxeSHRVwsI1+I/0JoOzQ2vs1lEnWOco43FPl2kO47T37MsaBxlBc3REmOpc3jz2ONP83gTK4r0xqi9hhfeX4B2rmME9Q21uj+MmcwI8+kqssPITX8P+TN1AQkJsTl8+3xXfyApEQb3m6KeQ43Ii5VyCJ9wXHZ+DEDaiwK+nFdiQgS+HBvhDFBRfIgn/M083yq/hxdeF0FAsoUaFzPbJ3PXFpgbeLXdmW7bRv8ADlWeFrgZ/p+LY+RNIXvs2ti/MzX3RRgtfD30OYbzPvPjvzHUO+Y+zhEq0OYL6kt1o+3uVu+OfWjW3qxm2hlvLpi86V05LGv0M5xqr+rIip9ryMQ+cRgyinJW9sbjGfFd5ubavabQkEvRFVy0N2Z27e9f2u8uh+CLhbnvD5Py5oS1YyfdSN15LS90c9zSVTd9u+ZxW9R46pJhc2VRpqajtce4ea4Gf1xyhZp/0Hzp79BA==


呼び出すメソッドの先頭が「_」じゃ無いかを見ているので「_」等をつける事でアクセスを防ぐ事もできますので是非とも活用して頂きたいです。
(例 : Form Validationクラスの独自ルールをController内のメソッドとして定義する場合)

また、注意したい点は_remapメソッドは何も処理しなければ、通常通りのメソッドを読み込んでくれるわけではありません。

2-4 出力

CodeIgniter.phpでは、Controllerの処理が完了するとOutputクラスを使って表示情報をレスポンスヘッダーと共に出力します。
このときの表示情報はLoaderでセットされた

<?php
$this->load->view('hoge');


の値です。
上記の記述ではapplication/views/hoge.phpが呼ばれますが、Outputクラス*2に保存されます。

保存した内容やヘッダー情報等を、Outputクラスの_displayメソッドをCodeIgniter.phpが呼び出して出力しています。

その為、

<?php
class Hoge extends CI_Controller{

    public function index()
    {
         $this->load->view('hoge');
         echo 'application/views/hoge.phpよりこの出力が先に表示されますよ';
    }
}

上記の様にviewメソッドの下にechoを書いても先に出力されるのは
echoの内容となります。



3. Controllerの動き

3-1 ControllerがLoaderを呼び出す。

CodeIgniterでmodelやlibrary、view等を読み込む時に大変便利なLoader クラス
このLoader クラスは先ほどのCodeIgniter.phpでは呼び出されません。

CI_Controllerのコンストラクタで呼び出されます。
CI_Controllerのコンストラクタの挙動は

・2-2で読み込んだcoreクラスをCI_Controllerのメンバ変数として代入
・Loaderを呼び出してloadメンバ変数として代入
・Loaderの初期化メソッドを動かす

です。


その為、CodeIgniterでApplicationを作成する時

<?php
defined('BASEPATH') OR exit('No direct script access allowed');
//CI_Controllerをextendするよ
class Hoge extends CI_Controller{

   public function __construct()
   {
        //ここにload書いても、CI_Controllerのコンストラクタが走ってないから動かないよ!
        //$this->load->model('Foo_model');

        //ここでcoreクラスやLoaderをCI_Controllerに読み込むよ
        parent::__construct();

        //ここに書くと、CI_Controllerのコンストラクタが動いているからloaderが使えるよ!
        $this->load->model('Foo_model');
   }

}


このようにCI_Controllerのコンストラクタ読み込み後Loaderが使えるようになります。
また、CodeIgniter.phpで読み込まれたcoreクラスはLoaderで読み込むような記述は必要は無く、

$this->input->post('hoge');

と記述することができます。



3-2 get_instanceってなんだ!

CodeIgniterでは様々なところで

$CI =& get_instance();

というコードを見かけることがあると思います。
これは、Controllerのインスタンスを参照渡ししています。*3

Hoge.php

<?php
Hoge extends CI_Controller{

    public function __construct()
    {
        parent::__construct();
        //このthisはHoge Controllerです。
        $this->load->model('Hoge_model');
    }

    public function index()
    {
        //Hoge Controllere内のhoge_modelメンバ変数にインスタンス化されたHoge_modelのfooメソッド
        $this->hoge_model->foo();
    }

    public function _controller_foo()
    {
         echo 'bar';
    }
}

Hoge_model.php

<?php
class Hoge_model extends CI_Model{
     public function foo()
     {
          //これでも動くけど、ラッパーであるget_instance関数の使用
          //CI_Controller::get_instance()->_controller_foo();

          //Hoge Controllerの_controller_fooが動く
          get_instance()->_controller_foo();
          
     }
}


ModelのfooメソッドではControllerインスタンスの_controller_fooが読み込まれます。
内部的にnew Hoge();とControllerを新たにインスタンス化するような挙動はしません。

つまり

get_instance() と Controller は同じ物

です。

Model内でthis->db->query等が動くのもget_instanceが理由です。
core/Model.php内のCI_Modelでは__getマジックメソッドを使いControllerのdbメンバ変数に入っているDBインスタンスを利用しているだけです。

<?php
class CI_Model{

    //モデル内で定義されていないメンバ変数が呼ばれたらControllerから取ってくる
    public function __get($key)
    {
         return get_instance()->{$key};
    }

}

まとめ

  • index.phpでAPPPATHとか使えそうな定数定義してますよ!
  • CodeIgniterは差分をある程度吸収してますよ!
  • Controllerメソッドのアンダーバーのプレフィックス便利ですよ!
  • _remapメソッド定義するときはshow_404とか定義した方が良いと思いますよ!
  • $this->load->viewはその場で出力じゃないよです!
  • get_instance超便利です
  • 端折って書いてる部分あるので困ったらCodeIgniterのソース読みやすいので読んでみるといいですよ!


次回は@rdlaboさんです。
お楽しみに。