PHPフレームワーク「CodeIgniter」のLoaderClassを読み解く

最近、PHPのフレームワークを自作しており、参考にCodeIgniterのコードを読んでいる。

しかし、ただ読んでも完全に理解できないと思うので、ブログに備忘録として書き残していく。

今回は、様々なディレクトリのファイルを読み込む役割をしているLoaderクラスを読んでいく。

Loaderクラスの役割

Loaderクラスとは、名前の通り様々なファイルを読み込むのに使われるクラス。

例えば、CodeIgniterのコードでよく見かけるのが、Controllerファイル内で以下のように記述する時だろう。

<?php
$this->load->view('index',$vars);

上記のコードでは、Loaderクラスにあるviewメソッドを読み出している。他にもLoadクラスはhelperやlibraryを呼び出すことができる。

Loaderのviewメソッドの流れを追う

では、実際にLoaderクラスがどのようにviewメソッドを使っているかの流れを追っていく。

参考:CodeIgniter/Loader.php at develop · bcit-ci/CodeIgniter

construct

まずはconstructから。

CodeIgniter/Loader.php at develop · bcit-ci/CodeIgniter

<?php
public function __construct()
{
$this->_ci_ob_level = ob_get_level();
$this->_ci_classes =& is_loaded();
log_message('info', 'Loader Class Initialized');
}

constructで行なっていることは、以下の3つ。

  • 出力バッファのネストレベルを格納する(基本的に0が格納される)
  • すでに読み込まれているクラスを格納する
  • ログを出力する

出力バッファとは、一時的にデータを溜め込む場所みたいなもので、フレームワークでは、viewファイルをバッファに出力して変数展開をした物を後で取り出すことで、HTMLファイルを作成している。

is_loaded関数は、Common.php(CodeIgniter/Common.php at develop · bcit-ci/CodeIgniter)で定義されている関数で、すでに読み込んだクラスを保存したり、出力したりしてくれる。

viewメソッド

viewメソッドは以下の通り。行なっていることは、引数を元に配列を作成し、その配列を引数として_ci_loadを呼び出している。

<?php
public function view($view, $vars = array(), $return = FALSE)
{
return $this->_ci_load(array('_ci_view' => $view, '_ci_vars' => $this->_ci_prepare_view_vars($vars), '_ci_return' => $return));
}

参考:CodeIgniter/Loader.php at develop · bcit-ci/CodeIgniter

では、_ci_loadはどのような実装になっているかを順番に見ていこう。(CodeIgniter/Loader.php at develop · bcit-ci/CodeIgniter)

_ci_loadでは、最初に引数を元に変数を作成している。

<?php
foreach (array('_ci_view', '_ci_vars', '_ci_path', '_ci_return') as $_ci_val)
{
$$_ci_val = isset($_ci_data[$_ci_val]) ? $_ci_data[$_ci_val] : FALSE;
}
$file_exists = FALSE;

上記のコードでは、「可変変数」と言うものを使って変数定義をしている。具体的には、'_ci_view', '_ci_vars', '_ci_path', '_ci_return'と言う4つの変数を作成して、その変数には引数である配列$_ci_dataのvalueまたはfalseが格納される。

そして、次はファイル名をいじる処理をする。

<?php
// Set the path to the requested file
if (is_string($_ci_path) && $_ci_path !== '')
{
$_ci_x = explode('/', $_ci_path);
$_ci_file = end($_ci_x);
}
else
{
$_ci_ext = pathinfo($_ci_view, PATHINFO_EXTENSION);
$_ci_file = ($_ci_ext === '') ? $_ci_view.'.php' : $_ci_view;
foreach ($this->_ci_view_paths as $_ci_view_file => $cascade)
{
if (file_exists($_ci_view_file.$_ci_file))
{
$_ci_path = $_ci_view_file.$_ci_file;
$file_exists = TRUE;
break;
}
if ( ! $cascade)
{
break;
}
}
}

viewメソッドを呼び出した場合は$_ci_path変数は存在しないので、elseの方の処理が実行される。

最初の$_ci_ext = pathinfo($_ci_view, PATHINFO_EXTENSION);では、viewファイルの拡張子を格納している。そして、次の$_ci_file = ($_ci_ext === '') ? $_ci_view.'.php' : $_ci_view;の処理で、拡張子phpが存在していなければファイル名に.phpを追加する処理を行う。

次のforeach ($this->_ci_view_paths as $_ci_view_file => $cascade)以下は、viewディレクトリと指定したディレクトリを順番に見て回って、ファイルが存在するかをチェックする。


<?php if ( ! $file_exists && ! file_exists($_ci_path)) { show_error('Unable to load the requested file: '.$_ci_file); } // This allows anything loaded using $this->load (views, files, etc.) // to become accessible from within the Controller and Model functions. $_ci_CI =& get_instance(); foreach (get_object_vars($_ci_CI) as $_ci_key => $_ci_var) { if ( ! isset($this->$_ci_key)) { $this->$_ci_key =& $_ci_CI->$_ci_key; } }

次のコードは、$_ci_CI =& get_instance();でControllerクラスのオブジェクトを読み込んでおり、foreach以下の処理でControllerクラスのプロパティをLoaderクラスのプロパティにコピーしている。

<?php
empty($_ci_vars) OR $this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars);
extract($this->_ci_cached_vars);

次にempty($_ci_vars) OR$_ci_vars変数が空でなければ、OR以下の処理を行う。そして、extract($this->_ci_cached_vars);$this->_ci_cached_varsの連想配列を各変数に分解する。