cakephperの日記(CakePHP, Laravel, PHP)


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

2010-09-10

本番運用時は不要なDebug_kitなどを読み込まないようにするテクニック

本番時(debug=0)、Debug_kitは読み込まれてしまっても画面に表示しないようになっていますが、それでもコンポーネントの起動処理が発生するため無駄な処理が走ってしまいます。とはいえ、毎回本番デプロイする時に、Debug_kitの読み込み箇所を消すなんてこともできません。

CakeFestのワークショップソースコードを見てたら、タイトルのようなことをやってる箇所を発見。(ちなみにワークショップは参加してません)

http://github.com/cakephp/CakeFest-2010-Workshop/blob/master/app_controller.php

<?php
class AppController extends Controller {
  public $components = array('Session', 'Auth', 'RequestHandler');

  public function __construct() {
    if (Configure::read('debug')) {
        $this->components[] = 'DebugKit.Toolbar';
    }
  parent::__construct();
}

AppControllerのコンストラクタでdebug値を判定して、componentsプロパティに追加しています。

シンプルで良いですね。

2010-02-18

リダイレクトにアクション名だけを指定してたら、うまくURLを作ってくれなかった

ショーンホワイトの金メダルライディングをみて癒されました。次元が違う、一番好きだったのは540のグラブで足を突き上げてるやつ。高く飛んで、ゆっくり余裕で回転してるほうが、くるくる回ってるより好きです。素晴らしいルーティーン、そして悲しいルーティング。。。今日はroutesとの格闘が続いております。

Cake1.2.6

リダイレクトで、ルーティングを色々と設定してる時に、そのルーティング通りのURLにならなかったので、その対応。

routes.phpはこうなってて、

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

/mobile/users/hogeの画面から、リダイレクトで/mobile/users/hoge2に遷移させようとしたとき、user_mobilesコントローラ

$this->redirect('hoge2');

としたら、/user_mobiles/hoge2にリダイレクトされてしまった。。。

結局、下記のようにリダイレクトを修正して対応。

$this->redirect(array('action' => 'hoge2'));

リダイレクト内で、Router::url()を呼び出してるので、それで問題が起こらない形に持っていくのが良いみたい。他のコントローラへのリダイレクトなら、

$this->redirect(array('controller' => 'others', 'action' => 'fuga'));

こんな感じで指定。


参考

http://book.cakephp.org/view/425/redirect

http://api.cakephp.org/view_source/controller/#line-536



AuthComponentでUsers Table以外を使う時

小ネタ。 Cake1.2.6

HogeConpornentがHogeモデルを使ってる場合、認証画面をそのままHoge::login()って作るとうまく行かない。デフォルトで、Userモデルを見てしまうから。アクセスしても/users/loginに飛ばされるし。

beforeFileterなどで、下記の設定が必要

$this->Auth->userModel = $this->modelClass;

モデル名を直接指定したければ、modelClassを固定の文字列にすればOK


ということで、よく設定する内容はこんな感じ。

//認証エラーメッセージ
$this->Auth->loginError = 'ログインエラー。IDもしくはパスワードが違います。';

//アクセス権がない場合のエラーメッセージ 非表示
$this->Auth->authError = ' ';

//認証するフィールド名
$this->Auth->fields = array(
      'username' => 'loginid',
      'password' => 'password'
);

//アクセス制限しないアクション
$this->Auth->allow('');

//ログイン後の遷移先を固定
$this->Auth->loginRedirect = array('controller' => 'adminpanels', 'action' => 'top');

//Authで利用するモデルをUser以外に指定
$this->Auth->userModel = $this->modelClass;


さらに、ログイン後の遷移を完全固定するため、リファラー情報をloginアクション内で消してます。

function login() {
    //ログイン後に強制的に$this->Auth->loginRedirectの画面にリダイレクトさせるため、リファラ情報を消す
    $this->Session->delete('Auth.redirect');
}

2010-02-15

利用可能なコントローラを設定ファイルで制限する方法

cakephp 1.2.6で開発してます。

利用可能なコントローラを設定ファイルで制限する方法。ファイルベースで扱うために、bootstrapに利用可能なサービスのコントローラ名を記載。

//app/config/bootstrap.php
<?php
Configure::write('USE_SERVICE', array(
    'Services', 
    'Members', 
    'Users', 
    )
);
?>
//app/app_controller.php
<?php
function beforeFilter() {        
  $services = Configure::read('USE_SERVICE');
  if(!in_array($this->name, $services)) {
     die('このサービスは利用停止中です。<br/><br/> <a href="/">TOPへ戻る</a>');
  }
  parent::beforeFilter();
}
?>

コントローラ単位でSSL必須なページはSSLへリダイレクト

cakephp 1.2.6で開発してます。

コントローラ単位に、SSLが必須であれば強制リダイレクトさせる機能。

//app/config/bootstrap.php
<?php
// HTTP, HTTPSのサーバ名を定義
define( 'SERVER_HTTP', 'http://hoge.example.com' );
define( 'SERVER_HTTPS', 'https://hogessl.example.com' );

// SSLを利用するサイトはTRUE, 利用しないサイトはFALSEを指定
define( 'USE_SSL', TRUE );
?>
//app/app_controller.php
<?php
    //プロパティ
    var $useSSL = true; //SSLを必須化しないコントローラでは、falseにする

    function beforeFilter() {

        //SSLの利用が必須の場合をチェック
        if(USE_SSL && $this->useSSL) {
            if(env('HTTPS') === 'on' || env('HTTPS') === true) {
        //nothing to do

            } else {
                $HTTPS = SERVER_HTTPS . $this->here;
                $this->redirect($HTTPS);
            }
        }

        parent::beforeFilter();
    }

追記

まともにSSLサイトにリダイレクトさせようとすると、どこかでcommon nameを持っておくか、localhostの証書を読み込んでcommon nameを算出する必要がありますね。ただ、ほとんどのケースでは、SSLはwww付きで始まることが多いでしょうから、そんなあなたにはこのコンポーネントがオススメ。

http://github.com/plank/secured

2009-03-10

開発環境で自動でSQLにExplainをかけるコンポーネント バージョンアップ(1.1) PostgreSQL対応

English page is here.


何度もバージョンアップしてすみません。MASA-Pさんのコメントや、あつさんのトラックバックからPostgreSQLでは一部のExplain結果しか出力されない問題がわかりました。ありがとうございます。

PostgreSQLで検証してませんでした、手を抜いてすみません。。。


というわけで、PostgreSQL対応しました。あつさんのコードを参考にしました。

下記のような画面になります。

f:id:cakephper:20090310201855j:image


最新版はここからダウンロードしてください。


PostgreSQL対応の差分だけ解説します

CakePHP のおいしい食べ方で英語で紹介してもらえて、http://planetcakephp.org/にも反映されたので、ソースコードは全部載せておきます

<?php

/**
 * ExplainSqlComponent - Auto execute SQL Explain and set results in the debug mode.
 *
 * Copyright (c) 2009 Yasushi Ichikawa
 *
 * Use this compnent in afterFilter or afterRender.
 *  var $components = array('ExplainSql');
 *  $this->ExplainSql->showExplainSQL( $slowQueryThreshold = 0 );
 *
 * @author Yasushi Ichikawa
 * @version 1.11
 *
 * @lastmodified  Date: 2009-03-10 22:00:00 (JST)
 */
class ExplainSqlComponent extends Object{

	/**
	 *
	 * @var controller object
	 */
	var $_controller;


	/**
	 * set conroller object
	 */
	function startup(& $controller) {
		$this->_controller = $controller;

	}


	/**
	 * Get all SQL query and execute SQL Explain of them without DESCRIBE query.
	 *
	 * if set the $slowQueryThreshold,
	 * execute SQL Explain only slow query which are spent over $slowQueryThreshold seconds.
	 *
	 * @param integer $slowQueryThreshold
	 * @access public
	 */
	function showExplainSQL( $slowQueryThreshold = 0 ){
		$explain_results = array();
		$count = 1;

		if(Configure::read() < 2 ){
			return ;
		}

		if (!class_exists('ConnectionManager')) {
			return ;
		}

		$dbConfigs = ConnectionManager::sourceList();

		foreach ( $dbConfigs as $configName ) {
			$db =& ConnectionManager::getDataSource( $configName );

			if( empty($db->_queriesLog[0]) ){
				continue;
			}

			$driver = $db->config['driver'];

			foreach( $db->_queriesLog as $key => $value ){

				if( preg_match( '/^SELECT /i', $value['query'] ) && 
                                    $value['took'] >= $slowQueryThreshold ){

					$reesults = null;
					$results = $db->query( "Explain ". $value['query'] );


					if( $driver === 'postgres' ){
						//merge QIERY PLAN value
						$query_plan = "<ul>";
						foreach( $results as $postgre_value ){
							$query_plan .= "<li>";
							$query_plan .= $postgre_value[0]['QUERY PLAN'] ;
							$query_plan .= "</li>";
						}
						$query_plan .= "</ul>";
						$results[0][0]['QUERY PLAN'] = $query_plan;

						//change column order
						$results[0][0] = array_merge( array("id" => $count), $results[0][0] );
					}

					$results[0][0]['query'] =  $value['query'];
					$results[0][0]['id'] = $count;
					$explain_results[] = $results[0][0];

					$count++;
				}
			}

		}

		if( !empty( $explain_results[0] ) ){
				$this->_outputHtml( $explain_results );
		}
		return;

	}


	/**
	 * set SQL Explain results on the controller->output.
	 *
	 * @param array $explain_results
	 */
	function _outputHtml( $explain_results ){
		$html_out = '<table>';
		$html_out .= '<tr>';

		//set table column name
		foreach( $explain_results[0] as $titlekey => $titleval ){
			$html_out .= '<th>';
			$html_out .= $titlekey;
			$html_out .= '</th>';
		}
		$html_out .= '</tr>';


		//set results
		foreach($explain_results as $recordnum => $val_arr){
			$html_out .= '<tr>';
			foreach( $val_arr as $key => $value ){
				$html_out .= '<td  style = "text-align: left">';
				$html_out .= $value."&nbsp";
				$html_out .= '</td>';
			}
			$html_out .= '</tr>';
		}

		$html_out .= '</table>';


		$this->_controller->output .= $html_out;
	}

}

?>

$db->configの中に、DB接続情報が入っているので、そこから接続しているDBサーバの値を取得して、PostgreSQLの場合の処理を追加しました。

PostgreSQLのExplain結果の配列構造とMySQLの結果とが異なるので、無理やり合わせました。あつさんの記事を参考に、QUERY PLANのカラムの値をひとつにマージしてリスト表示させてます。

QUERY PLAN1, QUERY PLAN2, QUERY PLAN3のようにカラムを分けようと思いましたが、クエリによってQUERY PLANの数が異なるので、1カラムにまとめました。

それと、idという番号を振ってるだけのカラムの位置が、PostgreSQLの場合は連想配列の後半に来てたので、array_mergeで先頭に来るようにしました。


不具合や、要望があればどしどしお寄せください。よろしくお願いします。


追記:bakeryに投稿しました。そのうち承認されたら公開されると思います(たぶん。。。)


開発環境で自動でSQLにExplainをかけるコンポーネント バージョンアップ(1.0)

CakePHP 1.2.1を使ってます。

昨日書いたAuto Explain Componentの記事ですが、反応はほとんどないと思って、ソースコードとか適当に書いた状態で公開してしまいました。反応があってうれしかったのですが、いくつか不具合を発見したので、いきなりバージョンアップです。


追記

すみません、この記事はMySQLのみ対応となります。PostgreSQLをご利用の方は、下記の記事を参照ください。

開発環境で自動でSQLにExplainをかけるコンポーネント バージョンアップ(1.1) PostgreSQL対応




バグとしては、モデルをnewを使って個別に呼び出してた場合や、insert, update , deleteなどが発行された場合はうまく動きませんでした。今回はそのバグの修正と、追加機能として、ある秒数以上かかったSelect文のみExplainするようにしました。デフォルトは0秒以上のクエリなので全てのSelect文がExplain対象です。

表示内容は下記の画像のように何も変化はありません。

f:id:cakephper:20090310151153j:image


基本的にはcomponentファイルを入れ替えるだけでOKです。

コンポーネントやReadmeなどのファイルはこのZIPをダウンロードするか、下記のコードをコピー&ペーストしてください。

app/controllers/components/explain_sql.php

<?php

/**
 * ExplainSqlComponent - Auto execute SQL Explain and set results in the debug mode.
 *
 * Copyright (c) 2009 Yasushi Ichikawa
 *
 * Use this compnent in afterFilter or afterRender.
 *  var $components = array('ExplainSql');
 *  $this->ExplainSql->showExplainSQL( $slowQueryThreshold = 0 );
 *
 * @author Yasushi Ichikawa
 * @version 1.0
 *
 */
class ExplainSqlComponent extends Object{

	/**
	 *
	 * @var controller object
	 */
	var $_controller;


	/**
	 * set conroller object
	 */
	function startup(& $controller) {
		$this->_controller = $controller;

	}


	/**
	 * Get all SQL query and execute SQL Explain of them without DESCRIBE query.
	 *
	 * if set the $slowQueryThreshold,
	 * execute SQL Explain only slow query which are spent over $slowQueryThreshold seconds.
	 *
	 * @param integer $slowQueryThreshold
	 * @access public
	 */
	function showExplainSQL( $slowQueryThreshold = 0 ){
		$explain_results = array();
		$count = 1;

		if(Configure::read() < 2 ){
			return ;
		}

		if (!class_exists('ConnectionManager')) {
			return ;
		}

		$dbConfigs = ConnectionManager::sourceList();

		foreach ( $dbConfigs as $configName ) {
			$db =& ConnectionManager::getDataSource( $configName );

			if( empty($db->_queriesLog[0]) ){
				continue;
			}


			foreach( $db->_queriesLog as $key => $value ){

				if( preg_match( '/^SELECT /i', $value['query'] ) 
                  && $value['took'] >= $slowQueryThreshold ){

					$reesults = null;
					$results = $db->query( "Explain ". $value['query'] );

					$results[0][0]['query'] =  $value['query'];
					$results[0][0]['id'] = $count;

					$explain_results[] = $results[0][0];
					$count++;
				}
			}

		}

		if( !empty( $explain_results[0] ) ){
				$this->_outputHtml( $explain_results );
		}
		return;

	}


	/**
	 * set SQL Explain results on the controller->output.
	 *
	 * @param array $explain_results
	 */
	function _outputHtml( $explain_results ){
		$html_out = '<table>';
		$html_out .= '<tr>';

		//set table column name
		foreach( $explain_results[0] as $titlekey => $titleval ){
			$html_out .= '<th>';
			$html_out .= $titlekey;
			$html_out .= '</th>';
		}
		$html_out .= '</tr>';


		//set results
		foreach($explain_results as $recordnum => $val_arr){
			$html_out .= '<tr>';
			foreach( $val_arr as $key => $value ){
				$html_out .= '<td  style = "text-align: left">';
				$html_out .= $value."&nbsp";
				$html_out .= '</td>';
			}
			$html_out .= '</tr>';
		}

		$html_out .= '</table>';


		$this->_controller->output .= $html_out;
	}

}

?>

使い方は前と同じように、afterRenderとかafterFileterに入れてください。今回の追加機能で、ある指定秒数以上かかったクエリのみExplainするために、第一引数に数値をセットするようにしましたが、これがなくても動きます(全てのSelectがExplain対象)。

今回の例は、全ての画面に表示させるために、app/app_controller.phpに記載する例です。

<?php
class AppController extends Controller {

	var $components = array('ExplainSql');

	function afterFilter(){

		parent::afterFilter();

		$this->ExplainSql->showExplainSQL(  $slowQueryThreshold = 0  );

	}

}

?>

前のバージョンだと、発行されたクエリの取得を、むりやり適当なモデルを使って取得してたんですが、今回のバージョンから発行されたクエリの取得方法を変えました。さらにconfig/database.phpで$dafault以外のDBも使ってる場合でも動くようになってると思います(SelectだけスレーブDBを見ている場合とか)。

下記のmcurryさんソースコードを参考にしました。

http://github.com/mcurry/cakephp/tree/master/plugins/sql_log

2009-03-09

開発環境で自動でSQLにExplainをかけるコンポーネント

CakePHP 1.2.1を使ってます。

CakePHPはconfig/core.phpのdebug値を2にすると、画面にアクセスした際に発行されたSQL文が表示されます。これに加えて、発行したSQL文にExplainをかけてその結果を追加表示するコンポーネントを作りました。


動作チェックはMySQLでしましたが、explainを使えるPostgresなんかでも動くと思います。Oracleとかexplain planとかやらなきゃいけないっぽいので、たぶん動きません。


Explainの説明などは下記を参照(from opparaさんブログ)

http://dev.mysql.com/doc/refman/5.1/ja/explain.html

http://blog.livedoor.jp/nipotan/archives/3743275.html

http://dev.seesaa.net/article/238633.html


追記(2009/3/10)

不具合がいくつかあったのでバージョンアップしました。ソースコードなどは下記の記事を参照ください。

開発環境で自動でSQLにExplainをかけるコンポーネント バージョンアップ(1.0)


ソースコードここからダウンロードするか、下記のソースコードをコピー&ペーストしてください。(ブログに載せているソースコードは古いので、ZIPファイルをダウンロードしてください)


app/controllers/components/explain_sql.phpという名前で下記を保存してください。

<?php

class ExplainSqlComponent extends Object{

	var $_controller;

	function startup(& $controller) {
    	$this->_controller = $controller;
  	}



	function showExplainSQL(){
		if(Configure::read() < 2){
			return ;
		}

		if( empty($this->_controller->modelNames) ){
			return ;
		}

		$html_out = null;
		$modelname = $this->_controller->modelNames[0];

		if( !empty($this->_controller->$modelname->getDataSource()->_queriesLog[0]) ){

			$count = 0;
			$html_out .= '<table>';
			foreach($this->_controller->$modelname->getDataSource()->_queriesLog as $key => $value){

				//デバッグ時のDESCRIBEのSQL文は抜かす
				if( !preg_match( '/^DESCRIBE/', $value['query'] ) ){

					$result = $this->_controller->$modelname->query("Explain ". $value['query']);
					$result[0][0]['query'] =  $value['query'];

					//連想配列キーをテーブルのタイトルとして表示
					if($count === 0){
						$html_out .= '<tr>';
						foreach( $result[0][0] as $titlekey => $titleval ){
							$html_out .= '<th>';
							$html_out .= $titlekey;
							$html_out .= '</th>';
						}
						$html_out .= '</tr>';

						$count++;
					}



					//内容表示
					$html_out .= '<tr>';
					foreach( $result[0][0] as $titlekey => $titleval ){
							$html_out .= '<td  style = "text-align: left">';
							$html_out .= $titleval."&nbsp";
							$html_out .= '</td>';
					}
					$html_out .= '</tr>';
				}

			}
			$html_out .= '</table>';

			$this->_controller->output .= $html_out;
		}
	}

}

?>

それで、Explainをしたい画面のコントローラコンポーネント指定して、afterFilterで呼び出してください。今回はすべての画面に強制的にExplainをかますためにapp_controller.phpに記載します

app/app_controller.php

<?php
class AppController extends Controller {

	var $components = array('ExplainSql');

	function afterFilter(){

		parent::afterFilter();

		$this->ExplainSql->showExplainSQL();

	}

}

?>

これをすると、下記の画面のように、Explainの結果が追加されます。

f:id:cakephper:20090309171631j:image

これで開発画面ですぐにインデックスの様子がチェックできる!!


_queriesLogにはSQL実行時間などが記録されているので、それを使ってn秒以上かかったSQL文のみExplainとかも簡単にできるよ!


追記(2009/5/16)

下記ブログで、CakePHP1.1でSQL Explain コンポーネントを動かす記事が書いてあります。ありがとうございます > coelacanthさん

眠るシーラカンスと新米プログラマー「CakePHPのexplainコンポーネントを1.1で動かしてみた」