cakephperの日記(CakePHP, Laravel, PHP)


継続的WebセキュリティテストサービスVAddyを始めました!

2010-02-24

フォーム入力画面と確認画面で共通で使えるXformHelper

Cakephp1.2.6

追記(2010/12/16):

コメントでPHP5.3の動作不具合報告を頂いたので対応しました。報告ありがとうございました。現状、PHP5.2.15, PHP5.3.4で確認し、CakePHPは1.3.6, 1.2.9で確認しました。gitの最新版か、githubにあるダウンロードボタンから0.02というバージョンのコードをダウンロードしてご利用ください。


入力項目が多くなると、確認画面を作るのも時間がかかりますよね、面倒ですし。今までは、確認画面で別途 $form->value使うとか、Postの値をそのまま表示するようにしてたのですが、デザインが入力画面とほぼ同じなのに別に実装するのがなんだかなと思ってました。それに嫌気がさして、フォーム入力画面でも確認画面でも同じメソッドで、フォームタグと値の表示を切り替えるXformHelperを開発しました。

機能は色々つけてあります。大体のメソッドはカバーできてるんじゃないかと思いますが、不具合があれば教えてください。


機能一覧

  • その他の入力画面機能
    • passwordフィールドの値は常に空にする(プロパティの変更で回避可能)
    • 日付関係のセパレータを拡張(プロパティにて、日本語の年月日、時分をセパレータに指定可能)
  • その他の確認画面機能
    • 出力はエスケープ、nl2brを自動でかける(プロパティの変更で回避可能)
    • passwordフィールドは、*****の固定文字列を表示(ただしinput()ではなくpassword()メソッドの利用に限り)
    • checkboxなどの複数選択時は、カンマ区切りで表示(プロパティの変更でカンマ以外も指定可能)
    • 日付関係のセパレータを指定可能(2010年2月24日という表示が可能)

前提

入力画面と確認画面の制御方法は、下記の2パターンあり、どちらかを実行ください。

1. コントローラの確認画面のアクションで、$this->params['xformHelperConfirmFlag'] = true;をセット(バリデーション成功後が望ましい)

2. 確認画面のviewファイルの最初に、$xformjp->confirmScreenFlag = true; とする



使い方

http://github.com/ichikaway/xformHelper/downloads

ここからダウンロード、もしくはgit cloneして設置下さい。

views/helpers/xform.php

views/helpers/xformjp.php

xform.phpが基本的な実装で、xformjpはxformを継承して、プロパティのみ日本語仕様(自分仕様で不要なものは極力false)に変更してます。お好きなほうをご利用下さい。xformjpを自分用に変更しても良いですし、myxform.phpみたいな別のクラスを作るのも良いかと思います。

xformを使ってプロパティのみ変更したい場合は、コントローラからhelpersプロパティで宣言する際に、下記の様に配列引数で指定すれば変更できます。

<?php
    var $helpers = array('Html', 'Form',  
          'xform' => array('changeDatetimeSeparator' =>
                    array(
                        'datefmt' => array(
                            'year' => '',
                            'month' => '',
                            'day' => '',
                            'afterDateTag' => '&nbsp;&nbsp;&nbsp;', //dateとtimeの表示の間に入れる文字列
                            ),
                        'timefmt' => array(
                            'hour' => '',
                            'min' => '',
                            'meridian' => '',
                            )
                        )
                    )
    );

上記のように、引数が多くなると面倒なので、自分はxformjpヘルパーを作りました。

これ以降は、xformjpヘルパーを前提に書きますが、xformヘルパーを利用する場合は全てxformjpをxformに置き換えて頂ければ大丈夫です。


コントローラ設定

<?php

var $helpers = array('Html', 'Form', 'xformjp');

//入力画面アクション
function add(){

}

//確認画面アクション
function add_confirm(){
    //確認画面の表示に切り替える(バリデーション成功後が望ましい)
    $this->params['xformHelperConfirmFlag'] = true;
}

Viewの設定

基本的には、使っているViewの$formを$xformjpに置換するだけで動きます(上記のようにコントローラ側で表示切替をする場合)

自分の場合は、$xformjp::input()関係を切り出してエレメント化して、入力画面用add.ctpと確認画面用add_confonfirm.ctpから同じエレメントを呼んでます。

まずは入力画面用add.ctp

<?php echo $xformjp->create('Modelname', array('action' => 'add_confirm'));?>

<?php
//外部ファイル読み込み
//views/elements/base_input.ctp
echo $this->element("base_input");
?>

<?php echo $xformjp->submit('確認', array('name' => 'confirm_action')); ?>
<?php echo $xformjp->end();?>

次に確認画面用add_confirm.ctp



<?php
//外部ファイル読み込み
//views/elements/base_input.ctp
echo $this->element("base_input");
?>

<?php echo $xformjp->create('Modelname', array('action' => 'add_finish'));?>
ここにHiddenでPostデータを出力 or セッションでPostデータを管理
<?php echo $xformjp->submit('送信', array('name' => 'finish')); ?>
<?php echo $xformjp->submit('戻る', array('name' => 'back')); ?>
<?php echo $xformjp->end();?>

hiddenでデータを一括で書き出す場合は、「よくある確認画面でのhiddenデータの持ち回り ver2」を使うと便利です! その更に改良版のformhiddenヘルパーgithubにあげてあります


最後に、共通で呼んでるエレメント base_input.ctp

今回は、text, password, select, checkboxを利用。

名前
<?php echo $xformjp->input('name'); ?>


パスワード
<?php echo $xformjp->password('password'); ?>


メール
<?php echo $xformjp->input('email'); ?>

<?php if($xformjp->checkConfirmScreen() === false) : // 確認画面では非表示  ?>
<?php echo $xformjp->input('email_conf'); ?>(確認)
<?php endif; ?>



チェックボックス
<?php
echo $xformjp->input('checkbox1',
array(
    'type' => 'select',
    'multiple' => 'checkbox',
    'options' => array('key1' => 'checkテスト', 'key2' => 'checkテスト2'),
    )
);
?>


セレクト
<?php echo $xformjp->input( 'select1' ,
array(
    'type' => 'select',
    'options'=> array('key1' => 'selectテスト', 'key2' => 'selectテスト2'),
    'label' => false,
    'empty' => '', 
    )
); ?>

メールの入力確認などは、確認画面で出力したくないので、$xformjp->checkConfirmScreen()を使って判定しています。falseが返ってきたら入力画面、trueが返ってきたら確認画面なので、これを使えば細かく制御できます。


既存のシステムで利用する場合は、views以下のフォルダのctpファイルで、$formを$xformjpに一括置換すればOK。あとはコントローラにちょっと追記するだけ。viewファイルの文字列一括置換は、例えばLinuxなどの場合であれば、下記のようにするだけです。

cd app/views
find . -type f -name "*.ctp" -exec perl -p -i -e 's/\$xform/\$xformjp/g' {} \;

これで面倒な確認画面がちょっと楽に実装できるようになった! 改良や不具合修正があれば連絡してもらえるとうれしいです。Twitterとかgithubのpullリクエストなどで。

Enjoy!

2010-02-18

edit画面でうまくURLが作れなかった

Cake1.2.6

携帯画面で、addはうまくいくのに、edit時だけform::createのurlオプションでうまくurlが作れなかった。

routes.phpはこうなってて、

Router::connect('/mobile/users/:action/*', 
array('controller' => 'user_mobiles',  ));

edit時は勝手にurlの中に/mobile/users/edit/3みたいに最後にidの数字が入ってしまう。これどこからくるの?使わないからいらないんだけど。。。

調査は後回しにして、editとaddの違いはそこだった。それで、form画面で、

<?php echo $form->create('User', 
array('url' => array('controller' => 'user_mobiles', 'action' => 'edit' )));?>

にしてたら、Postのactionに入るurl

/user_mobiles/edit/6

みたいになってしまって、urlがうまく作れなかった。

下記のようにして解決

create('User', array('url' => 
array('controller' => 'user_mobiles', 'action' => 'edit', 'id' => null )));?>

idがいらなかったから、nullにしたけど、いる場合はどうすんだろ? 知ってる人教えてください。


解決

解決しました。routesの方で。:id指定がいるんですね。アスタリスクで全てまかなってくれるかと思ってた。

Router::connect('/mobile/users/:action/:id', 
array('controller' => 'user_mobiles',  ));

こうしておけば、viewのurlオプションでid=nullはいらない。

2009-02-05 ctp以外の拡張子ファイルをviewファイルとして扱う

ctp以外の拡張子ファイルをviewファイルとして扱う

CakePHP 1.2.1です。

cakeの場合、viewファイルはindex.ctpというように、拡張子がctpとなってます。

でもviewファイルは.htmlとか.phpとか、好きな名前でやりたいよって場合どうするか。

結論から言うと、コントローラーで

class HogeController extends AppController {

	var $ext = '.html';

}

とすればOKです。


コントローラで上記の記述をするのが面倒で、全てのviewファイルを変えたい場合は

app/app_contoroller.phpにて

<?php
class AppController extends Controller {

	var $ext = '.html';


}

?>

としてあげればいいです。


Viewファイル名をコントローラのアクッション名ではなく、好きなものに変えたい場合は、コントローラの該当アクションで

$this->render("viewfilename");

としてあげればいいです。


好きなディレクトリ名をviewパスにする場合は下記で色々と書かれています

携帯サイトでキャリアごとにviewを分ける


拡張子を好きなものに変える方法の追い方

CakePHP 1.2.1です

一つ前の記事で、

「ctp以外の拡張子ファイルをviewファイルとして扱う」

http://d.hatena.ne.jp/cakephper/20090205/1233809420

を書きました。

その方法はソースコードを追っていくものだったので、今回はその流れを書いていきます。

まずは、コントローラ

$this->render();

とやってviewファイルをセットしているので、この辺から見ていきます。

まずは、cake/libs/controller/controller.phpを見ていきます。ここで「render(」という文字列で検索をかけると、下記のファンクションがヒットします。重要な行だけ抜粋してます。

	function render($action = null, $layout = null, $file = null) {

		$View =& new $viewClass($this);

		$this->output .= $View->render($action, $layout, $file);

		return $this->output;
	}

Viewクラスにあるrenderメソッドを呼んでいるのが分かります。第一引数の$actionは好きなアクション名を指定できるので、このアクション名に.ctpがどのタイミングで付け加えられるか見ていけばよいかなと思いました。


ということで、次にViewクラスを見ていきます。

cake/libs/view/view.phpのファイルで「render(」という文字列で検索をかけ、該当メソッドに飛びます。

下記の該当箇所で重要なところだけ抜粋しています。

	function render($action = null, $layout = null, $file = null) {

		if ($action !== false && $viewFileName = $this->_getViewFileName($action)) {
			if (substr($viewFileName, -3) === 'ctp' || substr($viewFileName, -5) === 'thtml') {
				$out = View::_render($viewFileName, $this->viewVars);
			} else {
				$out = $this->_render($viewFileName, $this->viewVars);
			}
		}


	}

ここを見ると、_getViewFileNameメソッドでviewファイル名を取得して$viewFileNameに入れて、そのファイル名を使って_renderメソッドでレンダリング処理をしています。


ということで、_getViewFileNameが怪しいので同一ファイル内で「_getViewFileName」で検索してそのメソッドに飛びます。ここも重要な箇所のみ抜粋してます。

	function _getViewFileName($name = null) {
		if ($name === null) {
			$name = $this->action;
		}

		foreach ($paths as $path) {
			if (file_exists($path . $name . $this->ext)) {
				return $path . $name . $this->ext;
			} elseif (file_exists($path . $name . '.ctp')) {
				return $path . $name . '.ctp';
			} elseif (file_exists($path . $name . '.thtml')) {
				return $path . $name . '.thtml';
			}
		}
	}

アクション名が与えられてなければ、$this->actionの値がviewのファイル名となります。

次のforeachでパスを検索して、まず最初に該当パスに該当のアクション名+拡張子($this->ext)のファイルがあるかチェックしてます。ファイルがなければ拡張子ctpを探し、なければ拡張子thtmlを探してます。


ということで、好きな拡張子を$this->extにセットしれやればいいんですね!デフォルトではview.phpの最初の方で

var $ext = '.ctp';

としているのでこれを書き換える方法を探します(直接書き換えるとバージョンアップの時に面倒なので間接的に書き換える方法を模索します。)

「ext」という文字列で検索していくと、下記の箇所が該当します

	var $__passedVars = array(
		'viewVars', 'action', 'autoLayout', 'autoRender', 'ext', 'base', 'webroot',
		'helpers', 'here', 'layout', 'name', 'pageTitle', 'layoutPath', 'viewPath',
		'params', 'data', 'plugin', 'passedArgs', 'cacheAction'
	);

これを使って何か初期値を与えてそうな感じなので、「$__passedVars」で検索すると、Viewクラスをインスタンス化した際に呼ばれるview.phpのコンストラクタがヒットします。

その箇所を見ると

	function __construct(&$controller, $register = true) {
		if (is_object($controller)) {
			$count = count($this->__passedVars);
			for ($j = 0; $j < $count; $j++) {
				$var = $this->__passedVars[$j];
				$this->{$var} = $controller->{$var};
			}
		}
		parent::__construct();

		if ($register) {
			ClassRegistry::addObject('view', $this);
		}
	}

$this->{$var} = $controller->{$var};この箇所で$this->extの値をコントローラの変数で上書きしています。

ということで、View.php

var $ext = '.ctp';

という初期値の箇所を書き換えるには、コントローラ側で同じように

var $ext = '.html';

とセットしてやれば良いことが分かりました。

2008-12-10

よくある確認画面でのhiddenデータの持ち回り ver2

CakePHP 1.2 RC3を利用しています。

前に書いた記事

よくある確認画面でのhiddenデータの持ち回り

で、自作ヘルパーを呼び出す際に、引数にモデル名を入れて、POSTされた該当モデル名のデータを展開してhiddenにセットしていたのですが、引数に何も指定せず、POSTされたデータ全てをhiddenにセットしたほうが良いと思い改良しました。

コメントくれた方ありがとうございました。


下記、改良したヘルパーです。再帰にしようか迷ったけど、3階層以上になることが想定できなかったので、foreach3回にしました。

追記:3階層以上の対応はこのヘルパーをお使いください。https://github.com/ichikaway/cakeplus/blob/master/views/helpers/formhidden.php



app/views/helpers/formhidden.php


<?php
class FormhiddenHelper extends AppHelper {
    var $helpers = array('Form');       


    function hiddenVars() {
        $ret = "";


        foreach ($this->data as $key1 => $val1){

        	foreach ($val1 as $key2 => $val2) {

	        	if(is_array($val2)){
			  foreach( $val2 as $key3 => $val3 ){
				$ret .= $this->Form->hidden("$key1.$key2.$key3")."\n";

			  }
	        	}else{
		            $ret .= $this->Form->hidden("$key1.$key2")."\n";
	        	}

	        }
        }

        return $ret;
    }
}
?>



これを使う場合は、コントローラで、

var $helpers = array('Formhidden');

というようにしてヘルパーを読み込んでおき、確認画面のViewファイルで下記のようにします



<form method="post">

	<?php echo $formhidden->hiddenVars(); ?>

	<input type="submit" />

</form>

2008-10-03

CakePHP1.2 RC3にアップデートしたらSelectでワーニング

CakePHP1.2 RC3がリリースされたので早速 cakeフォルダを入れ替えてアクセスしてみたら、$form->selectタグでワーニングが出てた。

ワーニングはarray_marge関係のエラーで、cake/lib/view/helper/form.phpのエラー行を見ないとSelect関係のエラーだとは分からなかった。

とりあえず下記のサイトを参照して、

http://cakebaker.42dh.com/2008/10/02/migration-from-cakephp-12-rc2-to-rc3/

$form->selectの第4引数がnullだったのが原因と判明。nullをarray()に変更して解決。