SQL Explain panel for the DebugKit plugin.

2009/3/20追記
微妙にバージョンアップしました。
http://d.hatena.ne.jp/cakephper/20090320/1237520921


2009/5/16追記
使い方の注意点が下記にあります。あつさんありがとう。layout指定は必須とか、htmlのheadタグは小文字で書くとか。
CakePHPクッキング「Explain SQL Component for Debugkitの注意点」


English page is here

CakePHP 1.2.1を利用してます。

前に作ったSQL Explainコンポーネントを、DebugKitに組み込みました。前のコンポーネントコンポーネント内にViewの仕事であるhtml生成などが入っていたため、bakeryに投稿した記事に対して、コンセプトはいいけど、Bad Componentだと言われました(coreコードのdbo_source::showLogメソッドとかどうなんよ?と思ったけど)


こいうものは、プラグイン化して、ちゃんとMVCを分けた方が良いとのことで、どうせならとDebugKitに組み込みました。DebugKitに組み込むのはすごく簡単で、感動しました。

ソースコードここからダウンロードするか、thechawのプロジェクトから下記コマンドのようにgit cloneで落としてください。
git clone git@thechaw.com:forks/ichikaway/debug_kit.git
(追記:chawからgit cloneする場合は、事前にchawにアカウントを作ってpublic keyを登録しておく必要があります)


このDebugKitを入れると、下記のような画面になり、パネルがひとつ増えて、Explain結果を表示してくれます。(こっちの方が見たいときだけ見れるので便利)


設置は、app/plugins/debug_kitに一式置いてください。
そして、app_controller.phpで下記のようにコンポーネントを読み込んでください。

<?php
class AppController extends Controller {
  var $components = array('DebugKit.Toolbar');
}
?>

これだけです。便利!

下記、Debug Kitへの組み込み方法を含めての解説です

まず、plugin/debugkit/controllers/components/toolbar.phpの下記の行を編集します。

var $_defaultPanels = array('history', 'session', 'request', 
           'sqlLog', 'timer', 'log', 'variables','sqlExplain');

上記のように、最後にsqlExplainという値を配列に追加します。
これによって、sqlExplainPanelクラスが自動で読み込まれます。


各Panelクラスは、plugin/debugkit/controllers/components/toolbar.phpに記述していくスタイルのようなので、同じようにこのファイルの最後に、下記のクラスを記述します。

<?php
class sqlExplainPanel extends DebugPanel {
	var $plugin = 'debug_kit';

	var $dbConfigs = array();

	var $slowQueryThreshold = 0;

/**
 * get db configs.
 *
 * @param string $controller
 * @access public
 * @return void
 */
	function startUp(&$controller) {
		if (!class_exists('ConnectionManager')) {
			$this->dbConfigs = array();
			return false;
		}
		$this->dbConfigs = ConnectionManager::sourceList();
		return true;
	}
/**
 * Get Sql Explain results for each DB config
 *
 * @param string $controller
 * @access public
 * @return array
 */
	function beforeRender(&$controller) {
		$queryLogs = array();
		if (!class_exists('ConnectionManager')) {
			return array();
		}


		$count=1;

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

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

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


			if( $driver === 'mysql' || $driver === 'postgres' ){
				$explain_results['sqlexplain_driver'] =  $driver;


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

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

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


						if( $driver === 'postgres' ){
							//merge QIERY PLAN value
							$query_plan = array();
							foreach( $results as $postgre_value ){
								$query_plan[] = $postgre_value[0]['QUERY PLAN'] ;
							}
							$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++;
					}
				}

			}

		}



		return $explain_results;
	}
}
?>

各パネルのクラスは、beforeRenderに処理を記載して、debugkitがそれを自動で呼び出しています。
beforeRenderでreturnをすると、Viewでは$contentという変数にreturn値が入ります。今回は配列をreturnしました。


そして、Viewは下記のファイルを追加します。基本的に1パネル1Viewファイルです。
app/plugins/debug_kit/views/elements/sql_explain_panel.ctp

<h2><?php __('Sql Explain Results')?></h2>
<?php if (!empty($content)) : ?>

	<?php
		$driver = $content['sqlexplain_driver'];
		unset($content['sqlexplain_driver']);
	?>



<div class="cake-sql-log">

	<?php if( $driver === 'mysql' || $driver === 'postgres' ): ?>


		<?php
			$headers = array_keys($content[0]);

			foreach( $content as $rownum => $value ){

				foreach( $value as $title => $linevalue ){
					if( is_array($linevalue) ){
						$linevalue_li = "<ul>";
						foreach( $linevalue as $num => $arr_val ){
							$linevalue_li  .= "<li>";
							$linevalue_li  .= $arr_val;
							$linevalue_li  .= "</li>";
						}
						$linevalue_li .= "</ul>";
						$linevalue = $linevalue_li;
					}
					$row[$rownum][] = $linevalue;
				}

			}

			echo $toolbar->table($row, $headers, array('title' => 'SQL Explain Results'));
		?>




	<?php else: ?>
		<p><?php __('support only MySQL and PostgreSQL.'); ?></p>

	<?php endif; ?>




</div>

<?php else: ?>
	<p class="warning"><?php __('No active database connections'); ?></p>
<?php endif; ?>|

$contentをコンポーネントから受け取ってるので、あとはその配列をテーブルタグに落とし込んでいるだけです。
テーブル作成は、$toolbar->tableメソッドで簡単にできます。第1引数にテーブルの内容の配列、第2引数にテーブルのヘッダ、第3引数にテーブルのタイトルを取ります。
テーブルの内容とヘッダの配列は、キーを数値にしているようなので、連想配列の文字列のキー名を変換しています。PostgreSQLだけは配列構造は一つ深くなるので、is_arrayで認識して配列をリストタグに入れるようにしています。


基本的にはこれだけです。簡単、便利!