Ethna v2.5.0のソースコードを読み解く Part1 【Ethna_ActionForm.php】

ethnaで書いてて、ふと思った

$hoge = $this->af->get('hoge');

で、hogeが渡されてこなかった場合、$hogeにはnullが入るのか?空が入るのか?


書けばすぐに分かるけど、せっかくなのでEthnaの元コード読んで見る事に

download

404 Not Found - Ethna
download && install 情報は公式に詳しくて書いてあります。
とりあえず、wgetして落として解凍

[ryoff@ryoff ~/ethna]$ tree -L 2
.
|-- Ethna-2.5.0
|   |-- CHANGES
|   |-- Ethna.php
|   |-- LICENSE
|   |-- README
|   |-- bin
|   |-- class
|   |-- misc
|   |-- skel
|   |-- test
|   `-- tpl
`-- package.xml

ControllerやActionClass、ActionFormなどは、class以下にあります。

Ethna_ActionForm.php

$this->af

とあるように、afはActionFormの略です。
なので、

Ethna-2.5.0/class/Ethna_ActionForm.php

を読みます。

get
 132     /**
 133      *  フォーム値のアクセサ(R)
 134      *
 135      *  @access public
 136      *  @param  string  $name   フォーム値の名称
 137      *  @return mixed   フォーム値
 138      */
 139     function get($name)
 140     {
 141         return $this->_getVarsByFormName($this->form_vars, $name);
 142     }

$nameを受け取って、 $this->form_vars と $name を _getVarsByFormName関数に渡していますね。
その引数を返す、と。

form_vars

_getVarsByFormName関数を見るまえに、form_varsという変数を覗きましょう。
form_varsは40行目に初期値が定義されてます。

  39     /** @var    array   フォーム値 */
  40     var $form_vars = array();

この変数に値をsetしているのは、setFormVars関数です。

 299     /**
 300      *  ユーザから送信されたフォーム値をフォーム値定義に従ってインポートする
 301      *
 302      *  @access public
 303      */
 304     function setFormVars()
 305     {
 306         if (isset($_SERVER['REQUEST_METHOD']) == false) {
 307             return;
 308         } else if (strcasecmp($_SERVER['REQUEST_METHOD'], 'post') == 0) {
 309             $http_vars =& $_POST;
 310         } else {
 311             $http_vars =& $_GET;
 312         }
 313 
 314         //
 315         //  ethna_fid というフォーム値は、フォーム定義に関わらず受け入れる
 316         //  これは、submitされたフォームを識別するために使われる
 317         //  null の場合は、以下の場合である
 318         //
 319         //  1. フォームヘルパが使われていない
 320         //  2. 画面の初期表示などで、submitされなかった
 321         //  3. {form name=...} が未設定である
 322         //
 323         $this->form_vars['ethna_fid'] = (isset($http_vars['ethna_fid']) == false
 324                                       || is_null($http_vars['ethna_fid']))
 325                                       ? null
 326                                       : $http_vars['ethna_fid'];
......
......

なのですが、この関数はEthna_ActionForm内からは呼ばれていません。
呼び出し元は、同ディレクトリ内になるEthna_Controllerからです。

./class/Ethna_Controller.php

_trigger_WWW関数内でsetFormVars関数が呼び出されています。

 897     /**
 898      *  フレームワークの処理を実行する(WWW)
 899      *
 900      *  引数$default_action_nameに配列が指定された場合、その配列で指定された
 901      *  アクション以外は受け付けない(指定されていないアクションが指定された
 902      *  場合、配列の先頭で指定されたアクションが実行される)
 903      *
 904      *  @access private
 905      *  @param  mixed   $default_action_name    指定のアクション名
 906      *  @param  mixed   $fallback_action_name   アクション名が決定できなかった場合に実行されるアクション名
 907      *  @return mixed   0:正常終了 Ethna_Error:エラー
 908      */
 909     function _trigger_WWW($default_action_name = "", $fallback_action_name = "")
 910     {
.....
.....
 949         // アクションフォーム初期化
 950         // フォーム定義、フォーム値設定
 951         $form_name = $this->getActionFormName($action_name);
 952         $this->action_form =& new $form_name($this);
 953         $this->action_form->setFormDef_PreHelper();
 954         $this->action_form->setFormVars();
 955         $backend->setActionForm($this->action_form);

_trigger_WWW関数はethnaのメインの処理を行います。

こちらはまた別途読み解いてみます。
今回は、

Controller 内で関数が呼び出されて、htmlのformに入力された値がform_varsにセットされるんだな

ぐらいに思っておきます。


前置きが長くなりましたが、これで、_getVarsByFormName関数に渡す、$this->form_varsという値には、htmlのFormに入力された値がセットされていることが分かりました。

_getVarsByFormName
 206     /**
 207      *  配列の中からキーで指定された要素を取り出す
 208      *
 209      *  @access private
 210      *  @param  array   &$target    対象とする配列
 211      *  @param  string  $nane       キー
 212      *  @return string  指定された要素
 213      */
 214     function _getVarsByFormName(&$target, $name)
 215     {
 216         $keys = $this->_getFormNameArray($name);
 217         return $this->_getVarsByKeys($target, $keys);
 218     }

_getFormNameArray関数に$nameを渡して$keysを受け取り、
_getVarsByKeys関数に$targetと$keysを渡して、戻り値を返しています。
_getVarsByKeys関数で返される値がそのまま

$this->af->get('hoge');

で返される値なので、この二つの関数を読み解けば、戻り値のパターンが分かりそうです。

_getFormNameArray
 185     /**
 186      *  フォーム名に対応するキーの配列を返す
 187      *
 188      *  @access private
 189      *  @param  string  $name   フォーム名
 190      *  @return array   キーの配列
 191      */
 192     function _getFormNameArray($name)
 193     {
 194         // 多次元配列を指定した場合
 195         if (preg_match('/^.*\[[^\]]+\]$/', $name)) {
 196             $buff = preg_replace('/\]\[/', '[', $name); // hoge[foo][bar] => hoge[foo[bar]
 197             $buff = preg_replace('/\]/', "", $buff);    // hoge][foo[bar] => hoge[foo[bar
 198             $ret = explode('[', $buff);                 // hoge[foo[bar   => array('hoge', 'foo', 'var')
 199         } else {
 200             // 多次元配列を指定していない場合
 201             $ret = array($name);
 202         }
 203         return $ret;
 204     }

多次元配列を指定した場合と指定していない場合で、処理が分かれています。
多次元配列を渡すとは、具体的にどんな事をしてくれるのか。
公式サイトに答えが書いてあります。

404 Not Found - Ethna

フォーム定義を以下のように [] 付きのキーで分類して定義するだけです。例を以下に示します。
[phone][home]や、[phone][mobile] のように、複数の階層を使って定義することも可能です。
※テンプレートの例
<form method="post">
  <input type="text" name="User[name]" value="宮崎あおい" />
  <input type="text" name="User[phone][home]" value="01-2345-6789" />
  <input type="text" name="User[phone][mobile]" value="090-1234-5678" />
  <input type="submit">
</form>

サンプルに宮崎あおい、と使用するあたり、非常に好感が持てます。


そして、この方法で、User[name] User[phone]などと階層の途中からでも値を取得できます。

$this->af->get('User[name]')
$this->af->get('User[phone]')

こんな感じ。
196〜198行目で多次元配列をexplodeして、配列にしてますね。

_getVarsByKeys

引き続き、_getVarsByKeys関数です。
_getFormNameArray関数で配列になって戻った$keysを順次処理していきます。

 235     /**
 236      *  配列の中からキーで指定された要素を取り出す
 237      *
 238      *  @access private
 239      *  @param  array   &$target    対象とする配列
 240      *  @param  array   $keys       キーの配列
 241      *  @return string  指定された要素
 242      */
 243     function _getVarsByKeys(&$target, $keys)
 244     {
 245         $count = count($keys);
 246         if ($count == 0) { // 探索完了
 247             return $target;
 248         } elseif ($this->max_form_deps + 1 <= $count) { // 深すぎる配列を制限する
 249             return null;
 250         }
 251 
 252         // まだ探索するキーが残っている
 253         $curval = array_shift($keys);
 254         if (is_array($target) && array_key_exists($curval, $target)) {
 255             return $this->_getVarsByKeys($target[$curval], $keys);
 256         }
 257         return null;
 258     }

max_form_deps はデフォルトでは10階層となっているようです。
$target(=form_vars) 内から、$keysに相当するデータを取得しています。
$keysがなくなるまで、再帰処理してますね。


つまり結果は、

それぞれ帰ります。


関数まねして動かしてみると、

  1 <?php
  2 
  3 $param = array(
  4         'a' => 1,
  5         'b' => array(
  6             'c' => 2,
  7             'd' => array(
  8                 'e' => 3,
  9             ),
 10         ),
 11     );
 12 function getVarsByKeys(&$param, $keys) {
 13     $count = count($keys);
 14     if ($count == 0) {
 15         return $param;
 16     }
 17 
 18     $curval = array_shift($keys);
 19     if (is_array($param) && array_key_exists($curval, $param)) {
 20         return getVarsByKeys($param[$curval], $keys);
 21     }
 22     return null;
 23 }
 24 
 25 var_dump(getVarsByKeys($param, array('a')));
 26 // int(1)
 27 var_dump(getVarsByKeys($param, array('b')));
 28 // array(2) {
 29 //  ["c"]=>
 30 //      int(2)
 31 //      ["d"]=>
 32 //      array(1) {
 33 //          ["e"]=>
 34 //              int(3)
 35 //      }
 36 // }
 37 var_dump(getVarsByKeys($param, array('b','d','e')));
 38 // int(3)
 39 var_dump(getVarsByKeys($param, array('b','d','f')));
 40 // NULL
 41 
 42 ?>

な感じ。