PHPの関数リファレンスを見ていると、見たことも聞いたこともない関数がよく出てきます。
今回の生贄はこちら、Filter。
filter_var()こそ入力値検証として使っている人がみつかりますが、他の関数については一体何のために存在するのかわかりませんし使っている人も見あたりません。
http://d.hatena.ne.jp/moriyoshi/20090319/1237486066
http://d.hatena.ne.jp/koyhoge/20110822/llplanets
誰かfilter_id()の有効な使い方を教えてください。
つうかマニュアルのコピペサイト邪魔すぎる。
ていうかfilter_input()の第4引数の詳しい使い方はfilter_var()のほうにしか載っていません。
ややこしいのが、フィルタに与える値によって処理内容が全く変わってくるところです。
上記filter_var()の例ではFILTER_SANITIZE_NUMBER_INTを与ますが、この引数は単純に数値以外の文字が削除されます。
「?hoge=0x99」としたらfalseでも0でもなく、「099」が返ります。
これをFILTER_VALIDATE_INTにすると、"0x99"が数値形式か否かをチェックしてfalseが返ってくるようになります。
FILTER_VALIDATE_INTのオプションにさらにFILTER_FLAG_ALLOW_HEXを与えると、今度は16進数形式の数値も許されるようになり、「153」が返ってきます。
上記filter_input()の例ではFILTER_VALIDATE_INT、FILTER_FLAG_ALLOW_HEXに加えてさらに、整数値の範囲1から100を追加しています。
"0x99"は153になり100を超えるので、falseが返ります。
使い慣れれば便利なのかもしれませんが、なんかどうにも使い方が疲れる関数です。
今回の生贄はこちら、Filter。
filter_var()こそ入力値検証として使っている人がみつかりますが、他の関数については一体何のために存在するのかわかりませんし使っている人も見あたりません。
http://d.hatena.ne.jp/moriyoshi/20090319/1237486066
http://d.hatena.ne.jp/koyhoge/20110822/llplanets
誰かfilter_id()の有効な使い方を教えてください。
つうかマニュアルのコピペサイト邪魔すぎる。
<?php //$_GET['hoge']が存在するかチェック $filterHasVar = filter_has_var(INPUT_GET, 'hoge'); //$_GET['hoge']から数値と+-以外を削除 $filterVar = filter_var($_GET['hoge'], FILTER_SANITIZE_NUMBER_INT); //$_GET['hoge']が1から100までの整数値かチェック $filterInput = filter_input( INPUT_GET, 'hoge', FILTER_VALIDATE_INT, array( 'options'=> array('min_range'=>1, 'max_range'=>100), 'flags'=> FILTER_FLAG_ALLOW_OCTAL | FILTER_FLAG_ALLOW_HEX ) );filter_input()はfilter_has_var()とfilter_var()を合わせたような関数で、第一引数と第二引数はfilter_has_var()のもの、第三引数と第四引数はfilter_var()のものと思えばいいです。
ていうかfilter_input()の第4引数の詳しい使い方はfilter_var()のほうにしか載っていません。
ややこしいのが、フィルタに与える値によって処理内容が全く変わってくるところです。
上記filter_var()の例ではFILTER_SANITIZE_NUMBER_INTを与ますが、この引数は単純に数値以外の文字が削除されます。
「?hoge=0x99」としたらfalseでも0でもなく、「099」が返ります。
これをFILTER_VALIDATE_INTにすると、"0x99"が数値形式か否かをチェックしてfalseが返ってくるようになります。
FILTER_VALIDATE_INTのオプションにさらにFILTER_FLAG_ALLOW_HEXを与えると、今度は16進数形式の数値も許されるようになり、「153」が返ってきます。
上記filter_input()の例ではFILTER_VALIDATE_INT、FILTER_FLAG_ALLOW_HEXに加えてさらに、整数値の範囲1から100を追加しています。
"0x99"は153になり100を超えるので、falseが返ります。
使い慣れれば便利なのかもしれませんが、なんかどうにも使い方が疲れる関数です。
PR
get_defined_functions()で使用できる関数の一覧を取得できますが、
先日get_defined_functions()の出力を眺めていたところ、妙なものを発見。
string(1) "_"
なんだこれ。
こんな関数ググりようがないので、とりあえず使ってみる。
よくわからないのでソースを見てみる。
Cは正直得意じゃないんですが、というかさっぱりわからないんですが。
ていうか、どう検索すればいいんだか。
色々触ってたら、ext/mbstring/oniguruma/st.hに
# define _(args) args
というのを発見しました。
これか?と思ったけどなんかちがうっぽい。よくわかんないけど。
動作的には_(args)を以後argsとして扱うって内容みたいなのでそれっぽいんだけど。
結局なんなのかわからなかったんですが、これ何なんですかね。
先日get_defined_functions()の出力を眺めていたところ、妙なものを発見。
string(1) "_"
なんだこれ。
こんな関数ググりようがないので、とりあえず使ってみる。
<?php _(); print(_('a')); print(_(new DateTime())); ReflectionFunction::export("_");
Warning: _() expects exactly 1 parameter, 0 given in C:\xampp\htdocs\hoge.php on line 2 a Warning: _() expects parameter 1 to be string, object given in C:\xampp\htdocs\hoge.php on line 4 Function [ <internal:gettext> function _ ] { - Parameters [1] { Parameter #0 [ <required> $msgid ] } }引数がそのまま返ってくるんですかね。
よくわからないのでソースを見てみる。
Cは正直得意じゃないんですが、というかさっぱりわからないんですが。
ていうか、どう検索すればいいんだか。
色々触ってたら、ext/mbstring/oniguruma/st.hに
# define _(args) args
というのを発見しました。
これか?と思ったけどなんかちがうっぽい。よくわかんないけど。
動作的には_(args)を以後argsとして扱うって内容みたいなのでそれっぽいんだけど。
結局なんなのかわからなかったんですが、これ何なんですかね。
前回Zend_ControllerでSmartyを使えるようにしましたが、フロントにごりごりコードを詰め込んでいて見た目がいまいちです。
もっとコントローラで処理する方法はないでしょうか。
Zend_Controller_ActionにはURLから呼び出されるアクション以外にも、必ず呼ばれるメソッドが幾つか用意されています。
Zend_Controller_Action::init()、preDispatch()、postDispatch()のみっつで、init()はコンストラクタで、preDispatch()はアクションの実行前、postDispatch()は実行後に呼ばれます。
そこで毎回使用するデータベース接続などはinit()に書くと便利、ということになります。
ところが普通にIndexController::init()に実装すると、別のコントローラHogeControllerを呼び出した場合は実行されない、という事態になってしまいます。
各コントローラのinit()はあくまでコントローラ単位の共通要素となります。
では全コントローラで共通して行える処理はないのかというと、デフォルトではありません。
Zend_Controller_Actionと各コントローラの間にひとつ基底クラスを噛ますことになります。
html/index.php
今後コントローラが増えても、同様にBaseControllerを継承すればZend_View_Smartyが使用できるようになります。
他にもDB接続や定義ファイルの読み込みなど、共通して使うものはここに書くとよいでしょう。
つうかこのくらい最初から用意してほしい気がするんだが。
ちなみにこれもコントローラなんでhttp://zend.localhost/base/index/とか入れられると直接呼ばれてしまいます。
もしかしたらcontrollersフォルダ外に出すとかした方がいいかもしれません。
もっとコントローラで処理する方法はないでしょうか。
Zend_Controller_ActionにはURLから呼び出されるアクション以外にも、必ず呼ばれるメソッドが幾つか用意されています。
Zend_Controller_Action::init()、preDispatch()、postDispatch()のみっつで、init()はコンストラクタで、preDispatch()はアクションの実行前、postDispatch()は実行後に呼ばれます。
そこで毎回使用するデータベース接続などはinit()に書くと便利、ということになります。
ところが普通にIndexController::init()に実装すると、別のコントローラHogeControllerを呼び出した場合は実行されない、という事態になってしまいます。
各コントローラのinit()はあくまでコントローラ単位の共通要素となります。
では全コントローラで共通して行える処理はないのかというと、デフォルトではありません。
Zend_Controller_Actionと各コントローラの間にひとつ基底クラスを噛ますことになります。
html/index.php
<?php define('APP_DIR', dirname(__FILE__).'/../application/'); require_once('Zend/Controller/Front.php'); Zend_Controller_Front::run(APP_DIR.'controllers');application/controllers/BaseController.php
<?php //require require_once('Zend/Controller/Front.php'); require_once('Zend/View/Smarty.php'); require_once(APP_DIR.'smarty/libs/Smarty.class.php'); class BaseController extends Zend_Controller_Action{ public function init(){ define('SMARTY_DIR', APP_DIR.'smarty/libs/'); define('SMARTY_TEMPLATE_DIR', APP_DIR.'templates/'); define('SMARTY_COMPLIE_DIR', APP_DIR.'smarty/templates_c/'); define('SMARTY_CACHE_DIR', APP_DIR.'smarty/cache/'); define('SMARTY_CONFIG_DIR', APP_DIR.'smarty/config/'); $this->view = new Zend_View_Smarty(SMARTY_TEMPLATE_DIR, array( 'compile_dir'=>SMARTY_COMPLIE_DIR , 'cache_dir'=>SMARTY_CACHE_DIR , 'config_dir'=>SMARTY_CONFIG_DIR )); $viewRenderer = $this->_helper->getHelper('viewRenderer'); $viewRenderer->setView($this->view) ->setViewBasePathSpec(SMARTY_TEMPLATE_DIR) ->setViewScriptPathSpec(':controller/:action.:suffix') ->setViewScriptPathNoControllerSpec(':action.:suffix') ->setViewSuffix('html'); } }application/controllers/IndexController.php
<?php require_once(APP_DIR.'controllers/BaseController.php'); class IndexController extends BaseController{ public function indexAction(){} public function hogeAction(){} }IndexControllerでは、Zend_Controller_ActionではなくBaseControllerをextendsするように変更しました。
今後コントローラが増えても、同様にBaseControllerを継承すればZend_View_Smartyが使用できるようになります。
他にもDB接続や定義ファイルの読み込みなど、共通して使うものはここに書くとよいでしょう。
つうかこのくらい最初から用意してほしい気がするんだが。
ちなみにこれもコントローラなんでhttp://zend.localhost/base/index/とか入れられると直接呼ばれてしまいます。
もしかしたらcontrollersフォルダ外に出すとかした方がいいかもしれません。
Zend_Controller標準のビューはZend_Viewですが、機能は貧弱です。
というかサンプルのテンプレートを見ればわかりますが、ほとんど素のPHPです。
ヘルパーはかなり用意されていますが、使い方を見てもどうも全然便利そうに見えない不思議。
というわけでSmartyに差し替えましょう。
幸い公式自らSmarty用ラッパを公開してくれています。
まずはSmartyの準備。
applicationディレクトリ内にSmarty用ディレクトリを作成。
application/
├templates/
└smarty/
├libs/
├cache/
├configs/
└templates_c/
templatesフォルダにテンプレートを置き、それ以外の本体やキャッシュなんかはsmartyフォルダに突っ込むことにします。
Smarty公式サイトからダウンロードして解凍、中身のlibsフォルダをapplication/smarty/libsに設置。
次にZend_View_Smartyクラスをさくっとコピーして、Zend/View/Smarty.phpに設置します。
これはZend_ViewからSmartyを呼び出せるように連携させるクラスです。
ダミーのindex.htmlを設置。
application/templates/index/index.html
html/index.php
Fatal error: Cannot access protected property Zend_View_Smarty::$_smarty in C:\hoge\fuga\html\index.php on line 21
あら?
具体的に死んでる行はここ。
setViewBasePathSpec($view->_smarty->template_dir)
なんでかというとZend_View_Smartyでprotected $_smarty;ってやってるせい。
http://helog.jp/php/zend-framework/984/とかPHP5.3.0なのにどうして動いてるんだろう?ふしぎ。
今回はテンプレートディレクトリをSMARTY_TEMPLATE_DIRで定義してるので素直にsetViewBasePathSpec(SMARTY_TEMPLATE_DIR)と書き直して実行。
見事にSmartyの適用に成功しました。
setViewScriptPathSpecとかsetViewScriptPathNoControllerSpecとかはリクエストパラメータの処理方法みたいです。
よくわからないのでとりあえずそのままで。
setViewSuffixはテンプレートの拡張子です。
デフォルトは'phtml'とかいう変な拡張子になっているので'html'に変更します。
以上で、Zend_ControllerでSmartyを利用することができるようになりました。
というかサンプルのテンプレートを見ればわかりますが、ほとんど素のPHPです。
ヘルパーはかなり用意されていますが、使い方を見てもどうも全然便利そうに見えない不思議。
というわけでSmartyに差し替えましょう。
幸い公式自らSmarty用ラッパを公開してくれています。
まずはSmartyの準備。
applicationディレクトリ内にSmarty用ディレクトリを作成。
application/
├templates/
└smarty/
├libs/
├cache/
├configs/
└templates_c/
templatesフォルダにテンプレートを置き、それ以外の本体やキャッシュなんかはsmartyフォルダに突っ込むことにします。
Smarty公式サイトからダウンロードして解凍、中身のlibsフォルダをapplication/smarty/libsに設置。
次にZend_View_Smartyクラスをさくっとコピーして、Zend/View/Smarty.phpに設置します。
これはZend_ViewからSmartyを呼び出せるように連携させるクラスです。
ダミーのindex.htmlを設置。
application/templates/index/index.html
<html><body>Hello, World!{$smarty.now}</body></html>最後にZend_Viewを呼び出すかわりにZend_View_Smartyを呼び出すように設定変更します。
html/index.php
<?php //define define('APP_DIR', dirname(__FILE__).'/../application/'); define('SMARTY_DIR', APP_DIR.'smarty/libs/'); define('SMARTY_TEMPLATE_DIR', APP_DIR.'templates/'); define('SMARTY_COMPLIE_DIR', APP_DIR.'smarty/templates_c/'); define('SMARTY_CACHE_DIR', APP_DIR.'smarty/cache/'); define('SMARTY_CONFIG_DIR', APP_DIR.'smarty/config/'); //require require_once('Zend/Controller/Front.php'); require_once('Zend/View/Smarty.php'); require_once(APP_DIR.'smarty/libs/Smarty.class.php'); //Zend_View_Smarty $view = new Zend_View_Smarty(SMARTY_TEMPLATE_DIR, array('compile_dir'=>SMARTY_COMPLIE_DIR, 'cache_dir'=>SMARTY_CACHE_DIR, 'config_dir'=>SMARTY_CONFIG_DIR)); //Zend_Controller_Action_Helper_ViewRenderer $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer'); $viewRenderer->setView($view) ->setViewBasePathSpec($view->_smarty->template_dir) ->setViewScriptPathSpec(':controller/:action.:suffix') ->setViewScriptPathNoControllerSpec(':action.:suffix') ->setViewSuffix('html'); //実行 Zend_Controller_Front::run(APP_DIR.'controllers');実行。
Fatal error: Cannot access protected property Zend_View_Smarty::$_smarty in C:\hoge\fuga\html\index.php on line 21
あら?
具体的に死んでる行はここ。
setViewBasePathSpec($view->_smarty->template_dir)
なんでかというとZend_View_Smartyでprotected $_smarty;ってやってるせい。
http://helog.jp/php/zend-framework/984/とかPHP5.3.0なのにどうして動いてるんだろう?ふしぎ。
今回はテンプレートディレクトリをSMARTY_TEMPLATE_DIRで定義してるので素直にsetViewBasePathSpec(SMARTY_TEMPLATE_DIR)と書き直して実行。
見事にSmartyの適用に成功しました。
setViewScriptPathSpecとかsetViewScriptPathNoControllerSpecとかはリクエストパラメータの処理方法みたいです。
よくわからないのでとりあえずそのままで。
setViewSuffixはテンプレートの拡張子です。
デフォルトは'phtml'とかいう変な拡張子になっているので'html'に変更します。
以上で、Zend_ControllerでSmartyを利用することができるようになりました。
コードジェネレータ?
自動でプログラム作ってくれるの?仕事が楽になるね!やったね!
と思ったかどうかはわかりませんが、実際問題としてZend_CodeGeneratorは自動プログラミングの一種と言えるかというと全然言えません。
ちょっと試しに使ってみましょう。
上のソースはわざと丁寧に書いており、実際にはメソッドチェーンが効いたり値を配列で纏めて設定できたりもするのですが、それでもインプットよりアウトプットのほうが張るかに小さくなるという結果は揺るぎません。
コードジェネレータを使って楽をしようという試みはほとんど無謀ということがわかるかと思います。
ではこれは何のためにあるのかというと、要はzf create projectとかsymfony generate:projectみたいなものです。
というかzf create projectは実際にZend_CodeGeneratorを使っています。
自動でプログラム作ってくれるの?仕事が楽になるね!やったね!
と思ったかどうかはわかりませんが、実際問題としてZend_CodeGeneratorは自動プログラミングの一種と言えるかというと全然言えません。
ちょっと試しに使ってみましょう。
<?php header('Content-type: text/html; charset=UTF-8'); require_once('Zend/CodeGenerator/Php/Class.php'); //クラス $class = new Zend_CodeGenerator_Php_Class(); $class->setName('Hoge'); //ドキュメント $docblock = new Zend_CodeGenerator_Php_Docblock(array( 'shortDescription' => 'クラスサンプル' ,'longDescription' => 'これはZend_CodeGeneratorで生成されたクラスです。' )); $class->setDocblock($docblock); //プロパティ $property1 = new Zend_CodeGenerator_Php_Property(); $property1->setName('property1'); $property1->setDefaultValue('value1'); $class->setProperty($property1); $class->setProperty(array( 'name'=>'property2', 'defaultValue'=>'value2', 'static'=>true, 'visibility'=>'private' )); //メソッド $method1 = new Zend_CodeGenerator_Php_Method(); $method1->setName('method1'); $method1Param1 = new Zend_CodeGenerator_Php_Parameter(); $method1Param1->setType('string'); $method1Param1->setName('param1'); $method1Param1->setDefaultValue('default'); $method1Param2 = new Zend_CodeGenerator_Php_Parameter(); $method1Param2->setType('int'); $method1Param2->setName('param2'); $method1Param2->setDefaultValue( new Zend_CodeGenerator_Php_Parameter_DefaultValue("null")); $method1->setParameters(array($method1Param1, $method1Param2)); $class->setMethods(array($method1)); //完成したクラス print($class->generate());以上の記述で、できあがるのが下のクラス。
/** * クラスサンプル * * これはZend_CodeGeneratorで生成されたクラスです。 */ class Hoge { public $property1 = 'value1'; private static $property2 = 'value2'; public function method1(string $param1 = 'default', int $param2 = null) { } }うん、まあなんだそのちょっと待て。
上のソースはわざと丁寧に書いており、実際にはメソッドチェーンが効いたり値を配列で纏めて設定できたりもするのですが、それでもインプットよりアウトプットのほうが張るかに小さくなるという結果は揺るぎません。
コードジェネレータを使って楽をしようという試みはほとんど無謀ということがわかるかと思います。
ではこれは何のためにあるのかというと、要はzf create projectとかsymfony generate:projectみたいなものです。
というかzf create projectは実際にZend_CodeGeneratorを使っています。
とりあえず前回の解説から。
最後に開いたURLはhttp://zend.localhost/index/indexでした。
最初のindexはindexコントローラを呼び出すという意味で、後ろのindexはコントローラ内のindexアクションを呼び出すという意味です。
結果としてIndexController.php内のIndexController::indexAction()が呼び出されます。
同様に例えばhttp://zend.localhost/hoge/fugaだったら、HogeController.php内のHogeController::fugaAction()が呼び出されるということになります。
http://zend.localhost/だけでもHello, World!が表示されますが、これはコントローラ名やアクション名が省かれた場合はindexとして扱われるためです。
http://framework.zend.com/manual/1.11/ja/zend.controller.quickstart.html#zend.controller.quickstart.go.controller
特に指定しないかぎりビューについてもファイルが自動的に決定され、application/views/scripts/コントローラ名/アクション名.phtmlになります。
URLがhttp://zend.localhost/hoge/fugaであればapplication/views/scripts/hoge/fuga.phtmlです。
そのscriptsってなんだよ。
さて、試しにhttp://zend.localhost/index/aってするとFatal errorが発生します。
IndexController::aAction()が存在しないためです。
このようなエラーをまとめて捕捉できる仕組みが用意されており、エラーコントローラと呼ばれています。
application/controllers/ErrorController.php
現在はただのダミーですが、errorAction()内で原因を取得したりといったこともできます。
ただこれを実装すると、うっかり例外が出てしまったときにエラーコントローラに奪われてしまってvar_dump()などが効かなくなるので、開発時はオフにしておきましょう。
その後はフロントコントローラとか標準のルータとかが色々書いてあるんだけど何に使うのかよくわからんのでとりあえずスルー。
リクエストオブジェクトだけは大事なのでやっておきます。
といってもまあ単に$_REQUESTを取得するってだけですが。
http://zend.localhost/index/index/?a=b&c=d
http://zend.localhost/index/index/a/b/c/d
両方とも同じ意味になり、indexコントローラのindexアクションを呼び、リクエストパラメータaの値が'b'、cの値が'd'という呼び出しになります。
Zend_Controller_Action::getRequest()でリクエストオブジェクトを取得でき、リクエストオブジェクトを通じてコントローラ名やアクション名、リクエストパラメータなどにアクセスできます。
この場合はコントローラ名は'index'、アクション名も'index'になります。
また、コントローラよりさらに大きい単位でモジュールという区分けができるのですが、そこは触っていないのでデフォルトの'default'になります。
IndexController::indexAction()内で実行したところで全く意味のないメソッドですが、そのうち役に立つことが出てくるでしょう、たぶん。
重要なのが最後のgetParam()とgetParams()です。
getParam('a')でリクエストパラメータaの中身、即ち'b'が取得できます。
getParams()は全てのリクエストパラメータを配列で取得します。
他にもリクエストメソッドとかisXmlHttpRequest()とかあるみたいですが、まだ使わないのでパス。
ディスパッチもちょっとだけ。
_forward()メソッドで別のコントローラ、アクションを実行しなおすことができます。
http://zend.localhost/index/hoge/を指定したかのようにhogeActionが実行されapplication/views/scripts/index/hoge.phtmlが表示されます。
ただしリクエストオブジェクトの中身は'index'コントローラ、'index'アクションのままで変更されず、リクエストパラメータもそのまま引き継がれます。
入力確認画面で入力チェックに引っかかったら入力画面に戻す、といった使い方になるのでしょう。
最後に開いたURLはhttp://zend.localhost/index/indexでした。
最初のindexはindexコントローラを呼び出すという意味で、後ろのindexはコントローラ内のindexアクションを呼び出すという意味です。
結果としてIndexController.php内のIndexController::indexAction()が呼び出されます。
同様に例えばhttp://zend.localhost/hoge/fugaだったら、HogeController.php内のHogeController::fugaAction()が呼び出されるということになります。
http://zend.localhost/だけでもHello, World!が表示されますが、これはコントローラ名やアクション名が省かれた場合はindexとして扱われるためです。
http://framework.zend.com/manual/1.11/ja/zend.controller.quickstart.html#zend.controller.quickstart.go.controller
特に指定しないかぎりビューについてもファイルが自動的に決定され、application/views/scripts/コントローラ名/アクション名.phtmlになります。
URLがhttp://zend.localhost/hoge/fugaであればapplication/views/scripts/hoge/fuga.phtmlです。
そのscriptsってなんだよ。
さて、試しにhttp://zend.localhost/index/aってするとFatal errorが発生します。
IndexController::aAction()が存在しないためです。
このようなエラーをまとめて捕捉できる仕組みが用意されており、エラーコントローラと呼ばれています。
application/controllers/ErrorController.php
<?php
class ErrorController extends Zend_Controller_Action{
public function errorAction(){}
}
application/views/scripts/error/error.phtml<html><body>なんかエラー</body></html>例外が発生するとErrorController::errorAction()が呼ばれ、error/error.phtmlが表示されます。
現在はただのダミーですが、errorAction()内で原因を取得したりといったこともできます。
ただこれを実装すると、うっかり例外が出てしまったときにエラーコントローラに奪われてしまってvar_dump()などが効かなくなるので、開発時はオフにしておきましょう。
その後はフロントコントローラとか標準のルータとかが色々書いてあるんだけど何に使うのかよくわからんのでとりあえずスルー。
リクエストオブジェクトだけは大事なのでやっておきます。
といってもまあ単に$_REQUESTを取得するってだけですが。
class IndexController extends Zend_Controller_Action { public function indexAction() { $request = $this->getRequest(); $moduleName = $request->getModuleName(); $controllerName = $request->getControllerName(); $actionName = $request->getActionName(); $params = $request->getParams(); $request['a'] = $request->getParam('a'); } }適当にリクエストパラメータ付きでURLを呼び出してみます。
http://zend.localhost/index/index/?a=b&c=d
http://zend.localhost/index/index/a/b/c/d
両方とも同じ意味になり、indexコントローラのindexアクションを呼び、リクエストパラメータaの値が'b'、cの値が'd'という呼び出しになります。
Zend_Controller_Action::getRequest()でリクエストオブジェクトを取得でき、リクエストオブジェクトを通じてコントローラ名やアクション名、リクエストパラメータなどにアクセスできます。
この場合はコントローラ名は'index'、アクション名も'index'になります。
また、コントローラよりさらに大きい単位でモジュールという区分けができるのですが、そこは触っていないのでデフォルトの'default'になります。
IndexController::indexAction()内で実行したところで全く意味のないメソッドですが、そのうち役に立つことが出てくるでしょう、たぶん。
重要なのが最後のgetParam()とgetParams()です。
getParam('a')でリクエストパラメータaの中身、即ち'b'が取得できます。
getParams()は全てのリクエストパラメータを配列で取得します。
他にもリクエストメソッドとかisXmlHttpRequest()とかあるみたいですが、まだ使わないのでパス。
ディスパッチもちょっとだけ。
_forward()メソッドで別のコントローラ、アクションを実行しなおすことができます。
<?php class IndexController extends Zend_Controller_Action{ public function indexAction(){ $this->_forward('hoge', null, null, array('e'=>'f')); } public function hogeAction(){ var_dump($this->getRequest()->getParams()); } }
http://zend.localhost/index/hoge/を指定したかのようにhogeActionが実行されapplication/views/scripts/index/hoge.phtmlが表示されます。
ただしリクエストオブジェクトの中身は'index'コントローラ、'index'アクションのままで変更されず、リクエストパラメータもそのまま引き継がれます。
入力確認画面で入力チェックに引っかかったら入力画面に戻す、といった使い方になるのでしょう。
ZendフレームワークはPearライブラリのように使うのが便利すぎて忘れてしまいますが、実はその本分はフレームワークです。
ということでフレームワークとして使ってみようと思いましたが、なんたることか公式マニュアルが和訳されていません。
仕方ないのでZend_Controllerあたりからちまちまと見て行きます。
とりあえずアップグレードを確認。
> pear channel-discover pear.zfcampus.org
> pear install zfcampus/zf
バーチャルホストを設定。
ついでにmod_rewriteがコメントアウトされている場合はコメントを外す。
LoadModule rewrite_module modules/mod_rewrite.so
<VirtualHost *:80>
ServerName zend.localhost
DocumentRoot "C:\hoge\fuga\html"
<Directory "C:\hoge\fuga\html">
AllowOverride All
</Directory>
</VirtualHost>
C:\hoge\fuga\以下に以下のようなディレクトリとファイルを作成。
application/
│ ├controllers/
│ │ └IndexController.php
│ ├models/
│ └views/
│ └scripts/
│ └index/
│ └index.phtml
└html/
├.htaccess
└index.php
hostsに以下の1行を追加。
127.0.0.1 zend.localhost
これで、
http://zend.localhost/
でC:\hoge\fuga\html\index.phpが表示されるようになります。
次にhtaccessを記述。
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]
これで、存在しないファイル、ディレクトリにアクセスした際はindex.phpが呼ばれるようになります。
html/index.phpはZend_Controllerを呼び出すだけです。
application/views/scripts/index/index.phtmlを記述。
さて、最低限の実装は以上で完成です。
http://zend.localhost/index/indexを開いてみると、見事にHello, World!が表示されました。
ということでフレームワークとして使ってみようと思いましたが、なんたることか公式マニュアルが和訳されていません。
仕方ないのでZend_Controllerあたりからちまちまと見て行きます。
とりあえずアップグレードを確認。
> pear channel-discover pear.zfcampus.org
> pear install zfcampus/zf
バーチャルホストを設定。
ついでにmod_rewriteがコメントアウトされている場合はコメントを外す。
LoadModule rewrite_module modules/mod_rewrite.so
<VirtualHost *:80>
ServerName zend.localhost
DocumentRoot "C:\hoge\fuga\html"
<Directory "C:\hoge\fuga\html">
AllowOverride All
</Directory>
</VirtualHost>
C:\hoge\fuga\以下に以下のようなディレクトリとファイルを作成。
application/
│ ├controllers/
│ │ └IndexController.php
│ ├models/
│ └views/
│ └scripts/
│ └index/
│ └index.phtml
└html/
├.htaccess
└index.php
hostsに以下の1行を追加。
127.0.0.1 zend.localhost
これで、
http://zend.localhost/
でC:\hoge\fuga\html\index.phpが表示されるようになります。
次にhtaccessを記述。
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]
これで、存在しないファイル、ディレクトリにアクセスした際はindex.phpが呼ばれるようになります。
html/index.phpはZend_Controllerを呼び出すだけです。
<?php require_once('Zend/Controller/Front.php'); Zend_Controller_Front::run(dirname(__FILE__).'/../application/controllers');application/controllers/IndexController.phpはとりあえずダミー。
<?php class IndexController extends Zend_Controller_Action{ public function indexAction(){} }デフォルトではIndexController::indexAction()のあと自動的にindex.phtmlというテンプレートを呼び出すようになっています。
application/views/scripts/index/index.phtmlを記述。
<html><body>Hello, World!</body></html>DOCTYPEとかtitleとかは後で付け足しゃいいんだよ。
さて、最低限の実装は以上で完成です。
http://zend.localhost/index/indexを開いてみると、見事にHello, World!が表示されました。
リレーショナルデータベースはあらゆるデータを二次元の表で表しますが、これは階層構造のデータとは非常に相性が悪いです。
社員の一覧表、といった形式には圧倒的な威力を発揮するDBですが、そこに上司や部下といった要素が入ってくると途端に見辛くなってしまいます。
データを取得するときも自己結合や外部結合を駆使したアクロバティックなSQL文を書かねばなりません。
SQLで階層構造を扱う方法も色々編み出されていますが、PHPにおいてはPear::DB_NestedSetを使用することでそこらへんを隠蔽し階層構造として扱うことができます。
http://www.geocities.jp/mickindex/database/db_tree_ns.html
http://pear.php.net/package/DB_NestedSet
DB_NestedSet 1.4.0RC1 (beta)
ということらしいのですが使い方がよくわかりません。
とりあえずマニュアルの例「ルートおよびサブノードの作成」を試してみる。
http://pear.php.net/manual/ja/package.DB_NestedSet.php
Fatal error: DB Error: no such table in C:\htdocs\dbNestedSet.php on line 873
あぁ?
実は使用前にnested_setテーブルおよびnested_set_locksテーブルを作成しないといけないのですが、そのことについて全く日本語情報がありません。
どんだけ使われてないんだDB_NestedSet。
英語情報が見つかったのでさくっとコピペでテーブルを作成。
http://trac.seagullproject.org/wiki/Howto/Navigation/HtmlMenu
http://www.php-resource.de/forum/showthread/t-49204.html
子ノードに孫ノードを付け足したい場合も、
む。
普通は
実は$child2は単なる数値(nested_set.id)で、SimpleXMLみたいな再帰構造にはなっていません。
http://jp.php.net/manual/ja/book.simplexml.php
おかげでメソッド再帰呼び出しで値を投入しようか、なんてときに微妙。
ちなみにこのサンプル、$DatabasePointerは全く意味がないので削除してかまいません。
あと実際に本気で使用するのであれば、
社員の一覧表、といった形式には圧倒的な威力を発揮するDBですが、そこに上司や部下といった要素が入ってくると途端に見辛くなってしまいます。
データを取得するときも自己結合や外部結合を駆使したアクロバティックなSQL文を書かねばなりません。
SQLで階層構造を扱う方法も色々編み出されていますが、PHPにおいてはPear::DB_NestedSetを使用することでそこらへんを隠蔽し階層構造として扱うことができます。
http://www.geocities.jp/mickindex/database/db_tree_ns.html
http://pear.php.net/package/DB_NestedSet
DB_NestedSet 1.4.0RC1 (beta)
ということらしいのですが使い方がよくわかりません。
とりあえずマニュアルの例「ルートおよびサブノードの作成」を試してみる。
http://pear.php.net/manual/ja/package.DB_NestedSet.php
Fatal error: DB Error: no such table in C:\htdocs\dbNestedSet.php on line 873
あぁ?
実は使用前にnested_setテーブルおよびnested_set_locksテーブルを作成しないといけないのですが、そのことについて全く日本語情報がありません。
どんだけ使われてないんだDB_NestedSet。
英語情報が見つかったのでさくっとコピペでテーブルを作成。
http://trac.seagullproject.org/wiki/Howto/Navigation/HtmlMenu
http://www.php-resource.de/forum/showthread/t-49204.html
CREATE TABLE `nested_set` ( `id` int(10) unsigned NOT NULL default '0', `parent_id` int(10) unsigned NOT NULL default '0', `order_num` tinyint(4) unsigned NOT NULL default '0', `level` int(10) unsigned NOT NULL default '0', `left_id` int(10) unsigned NOT NULL default '0', `right_id` int(10) unsigned NOT NULL default '0', `name` varchar(60) NOT NULL default '', PRIMARY KEY (`id`), KEY `right` (`right_id`), KEY `left` (`left_id`), KEY `order` (`order_num`), KEY `level` (`level`), KEY `parent_id` (`parent_id`), KEY `right_left` (`id`,`parent_id`,`left_id`,`right_id`) ) TYPE=MyISAM; CREATE TABLE `nested_set_locks` ( `lockID` char(32) NOT NULL default '', `lockTable` char(32) NOT NULL default '', `lockStamp` int(11) NOT NULL default '0', PRIMARY KEY (`lockID`,`lockTable`) ) TYPE=MyISAM COMMENT='Table locks for comments';その後サンプルを実行すると親ノード「root 1」に子ノード「node 1.1」「node 1.2」を持つ簡単なツリーができあがりました。
root 1 |
node 1.1 |
node 1.2 |
子ノードに孫ノードを付け足したい場合も、
$child2 = $nestedSet->createSubNode($parent, array('name' =>'node 1.2')); $nestedSet->createSubNode($child2, array('name' =>'node 2.1'));とcreateSubNode()に引数を渡すだけで簡単に追加することができます。
む。
普通は
$child2->createSubNode(array('name' =>'node 2.1'));とかじゃねーの?
実は$child2は単なる数値(nested_set.id)で、SimpleXMLみたいな再帰構造にはなっていません。
http://jp.php.net/manual/ja/book.simplexml.php
おかげでメソッド再帰呼び出しで値を投入しようか、なんてときに微妙。
ちなみにこのサンプル、$DatabasePointerは全く意味がないので削除してかまいません。
あと実際に本気で使用するのであれば、
$nestedSet =& DB_NestedSet::factory('MDB2', $dsn, $params);とMDB2を使用するようにした方がよいでしょう。
MongoDBってなんだ?
http://ja.wikipedia.org/wiki/MongoDB
> MongoDBは従来の関係データベース管理システムでは簡単に解決できない多量のトランザクション処理が要求されない問題に対し設計されている。
意味逆じゃないか?
いつものことながらWikipediaの表現は全く要領を得ませんが、MongoDBはNoSQLです。
NoSQLとは、RDBMSが持っているACID特性を一部犠牲にして、性能やスケーラビリティを確保したデータベースらしいです。
サーバを追加してちょっと設定をいじるだけでふたつのサーバにデータが分散して登録されるとか、DBやテーブルを作らないでいきなりインサートできるとか、同じテーブルに型の違うデータを突っ込めるとか、そんなRDBMSではありえない使い方ができるみたい。
CREATE DATABASEするまえに容量までも指定しとかないといけないOracleなどとは対局の存在と言えましょう。
ということでローカルPCのXAMPPにNoSQLを導入してもスケーラビリティの恩恵とかこれっぽっちもないような気がするんですが、まあとりあえず試してみます。
公式サイトからダウンロード、解凍、C:\xampp\mongodbに移動。
データベースファイルを設置するフォルダ「C:\xampp\mongodb\data\db」を作成。
コマンドプロンプトから以下を実行
>cd C:\xampp\mongodb\bin
C:\xampp\mongodb\bin>mongod --install --logpath D:\xampp\mongodb\log.txt --dbpath D:\xampp\mongodb\data\db
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
お?
ググったらC++ランタイムエラーだとか。
でもインストールし直しても変わらず。
よく見てみたらパスが存在しないDドライブだった。気をつけよう。
C:\xampp\mongodb\bin>mongod --install --logpath C:\xampp\mongodb\log.txt --dbpath C:\xampp\mongodb\data\db
all output going to: C:\xampp\mongodb\log.txt
Creating service MongoDB.
Service creation successful.
Service can be started from the command line via 'net start "MongoDB"'.
インストールできました。
サービスを起動
net start "MongoDB"
起動したら、せっかくなのでコマンドをバッチファイルにしておきましょう。
といっても単にコマンドをmongostart.batとかの名前で保存するだけです。
あとはスタートメニューにでも入れておけば好きなときに起動できます。
サービスが起動したら接続してみます。
C:\xampp\mongodb\bin>mongo
MongoDB shell version: 1.8.2
connecting to: test
>
何もしてないのにあっさり繋がりおった。
ユーザ登録とか権限とかはどうなってんの?
http://ja.wikipedia.org/wiki/MongoDB
> MongoDBは従来の関係データベース管理システムでは簡単に解決できない多量のトランザクション処理が要求されない問題に対し設計されている。
意味逆じゃないか?
いつものことながらWikipediaの表現は全く要領を得ませんが、MongoDBはNoSQLです。
NoSQLとは、RDBMSが持っているACID特性を一部犠牲にして、性能やスケーラビリティを確保したデータベースらしいです。
サーバを追加してちょっと設定をいじるだけでふたつのサーバにデータが分散して登録されるとか、DBやテーブルを作らないでいきなりインサートできるとか、同じテーブルに型の違うデータを突っ込めるとか、そんなRDBMSではありえない使い方ができるみたい。
CREATE DATABASEするまえに容量までも指定しとかないといけないOracleなどとは対局の存在と言えましょう。
ということでローカルPCのXAMPPにNoSQLを導入してもスケーラビリティの恩恵とかこれっぽっちもないような気がするんですが、まあとりあえず試してみます。
公式サイトからダウンロード、解凍、C:\xampp\mongodbに移動。
データベースファイルを設置するフォルダ「C:\xampp\mongodb\data\db」を作成。
コマンドプロンプトから以下を実行
>cd C:\xampp\mongodb\bin
C:\xampp\mongodb\bin>mongod --install --logpath D:\xampp\mongodb\log.txt --dbpath D:\xampp\mongodb\data\db
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
お?
ググったらC++ランタイムエラーだとか。
でもインストールし直しても変わらず。
よく見てみたらパスが存在しないDドライブだった。気をつけよう。
C:\xampp\mongodb\bin>mongod --install --logpath C:\xampp\mongodb\log.txt --dbpath C:\xampp\mongodb\data\db
all output going to: C:\xampp\mongodb\log.txt
Creating service MongoDB.
Service creation successful.
Service can be started from the command line via 'net start "MongoDB"'.
インストールできました。
サービスを起動
net start "MongoDB"
起動したら、せっかくなのでコマンドをバッチファイルにしておきましょう。
といっても単にコマンドをmongostart.batとかの名前で保存するだけです。
あとはスタートメニューにでも入れておけば好きなときに起動できます。
サービスが起動したら接続してみます。
C:\xampp\mongodb\bin>mongo
MongoDB shell version: 1.8.2
connecting to: test
>
何もしてないのにあっさり繋がりおった。
ユーザ登録とか権限とかはどうなってんの?
Mantisではことあるごとにメールを送信する仕様になっています。
http://www.alles.or.jp/~sogabe/mantis/manual/1.4.5.html
まあバグトラッキングシステムであるから当然といえば当然ですが。
ですがXAMPPでメールを送信するには外部SMTPサーバを使用したりMercuryを起動したりする必要があって面倒なので、メール無しで使えるようにしてみます。
ここに同じ改造をしている人がいましたが、残念ながらソースは公開していません。
config_inc.php
Mantisは新規ユーザ登録時、ユーザ名を登録するとパスワードを自動発行してメールで送付する仕組みになっているので、パスワードを自動発行するけどメールは送付しないようにします。
core/print_api.php
手っ取り早く初期パスワードをとりあえずメールアドレスと同じにしてしまいます。
公開サーバでこんなことやったらアウトですが、そんな状況であればメール送信できないってことがありえないのでまあいいでしょう。
何故IDではなくメアドかというと、パスワードを作成してる関数の引数にメアドがあってIDがないからというだけです。
core/authentication_api.php
さっそく登録。
SELECT * FROM `mantis_user_table` ;
無事登録されました。
ではログイン!
あなたのアカウントが無効化もしくはブロックされているか、入力した利用者名かパスワードが間違っています。
あれ?
ちなみにログインチェックしてるのはcore/authentication_api.phpのauth_attempt_login()なのですが、無駄に超ややこしくて一体何をやってるのかよくわかりません。
誰かこの関数の動作を説明してくれ。
http://www.alles.or.jp/~sogabe/mantis/manual/1.4.5.html
まあバグトラッキングシステムであるから当然といえば当然ですが。
ですがXAMPPでメールを送信するには外部SMTPサーバを使用したりMercuryを起動したりする必要があって面倒なので、メール無しで使えるようにしてみます。
ここに同じ改造をしている人がいましたが、残念ながらソースは公開していません。
config_inc.php
$g_validate_email = 'OFF'; $g_check_mx_record = 'OFF'; $g_send_reset_password = 'OFF'; $g_enable_email_notification = 'OFF';$g_enable_email_notification = 'OFF'を指定すると新規ユーザ登録ができなくなります。
Mantisは新規ユーザ登録時、ユーザ名を登録するとパスワードを自動発行してメールで送付する仕組みになっているので、パスワードを自動発行するけどメールは送付しないようにします。
core/print_api.php
# prints the signup link function print_signup_link() { if ( ( ON == config_get_global( 'allow_signup' ) ) && ( LDAP != config_get_global( 'login_method' ) ) && ( ON == config_get( 'enable_email_notification' ) ) ) { print_bracket_link( 'signup_page.php', lang_get( 'signup_link' ) ); } } ↓ function print_signup_link() { if ( ( ON == config_get_global( 'allow_signup' ) ) && ( LDAP != config_get_global( 'login_method' ) ) ) { print_bracket_link( 'signup_page.php', lang_get( 'signup_link' ) ); } }パスワードはランダムに作成されているので、メールが送信されないと確認不能になります。
手っ取り早く初期パスワードをとりあえずメールアドレスと同じにしてしまいます。
公開サーバでこんなことやったらアウトですが、そんな状況であればメール送信できないってことがありえないのでまあいいでしょう。
何故IDではなくメアドかというと、パスワードを作成してる関数の引数にメアドがあってIDがないからというだけです。
core/authentication_api.php
function auth_generate_random_password( $p_email ) { $t_val = mt_rand( 0, mt_getrandmax() ) + mt_rand( 0, mt_getrandmax() ); $t_val = md5( $t_val ); return utf8_substr( $t_val, 0, 12 ); } ↓ function auth_generate_random_password( $p_email ) { return $p_email; } function auth_generate_confirm_hash( $p_user_id ) { $t_confirm_hash_generator = config_get( 'password_confirm_hash_magic_string' ); $t_password = user_get_field( $p_user_id, 'password' ); $t_last_visit = user_get_field( $p_user_id, 'last_visit' ); $t_confirm_hash = md5( $t_confirm_hash_generator . $t_password . $t_last_visit ); return $t_confirm_hash; } ↓ function auth_generate_confirm_hash( $p_user_id ) { return $p_user_id; }これで完成!
さっそく登録。
SELECT * FROM `mantis_user_table` ;
id | 6 |
---|---|
username | hoge |
realname | |
dummy@example.com | |
password | 32066a2f550b9a8b3f3c8b25c918de37 |
enabled | 1 |
protected | 0 |
access_level | 25 |
login_count | 0 |
lost_password_request_count | 0 |
failed_login_count | 0 |
cookie_string | 32ff7a31ef3159f0d6be9e1e4fb6699b7b6a7aade80f86269e0768eb1e419579 |
last_visit | 1304299714 |
date_created | 1304299714 |
無事登録されました。
ではログイン!
あなたのアカウントが無効化もしくはブロックされているか、入力した利用者名かパスワードが間違っています。
あれ?
ちなみにログインチェックしてるのはcore/authentication_api.phpのauth_attempt_login()なのですが、無駄に超ややこしくて一体何をやってるのかよくわかりません。
誰かこの関数の動作を説明してくれ。