Ethna_DBでfetchArrayのようなことをする

2010.03.03    Ethna, PHP, 馬場   タグ: —    baba   

PHPでmysql_queryの結果オブジェクトは、mysql_fetch_row()しても良いですが、mysql_fetch_array()した方が便利ですよね。
※mysql_fetch_row()では、結果を通常の配列で取得しますが、mysql_fetch_array()では、カラム名をキーにした連想配列で取得できます

EthnaでAppObjectを使わずにEthna_DBを直接たたく場合、結果オブジェクトはPEAR::DB_Resultオブジェクトです。
これはfetchRow()のメソッドを持ちますが、fetchArray()は存在しません。
http://pear.plus-server.net/package.database.db.db-result.fetchrow.html

カラム名をキーにした連想配列が欲しい場合、queryを投げる前にsetFetchMode(DB_FETCHMODE_ASSOC)を実行しておく必要があります。
http://pear.plus-server.net/package.database.db.db-common.setfetchmode.html

EthnaのデフォルトDBアダプタ、DB_PEARは、内部にPEAR::DBを持っているので、以下のようなコードを書けばOKです。

$db = $this->backend->getDB();
$db->db->setFetchMode(DB_FETCHMODE_ASSOC);
$res = $db->query($sql);
print_r($res->fetchRow());

http://ethna.jp/ethna-document-dev_guide-db.html
Ethnaの デフォルトの DB接続クラス(Ethna_DB_PEAR) はPEAR::DBを継承しているので
と書いてありますが、継承ではなくて委譲なので、$db->db->setFetchMode()となります。

fputcsvの問題点と改良

2010.03.01    PHP, 馬場      baba   

PHPのfputcsv関数は便利です。
しかし、以下のような点が不便です。

エンコーディングを変換できない
主にCSV出力の目的になるExcelでは、Shift-JIS以外のCSVを開けません。
Excel 2007ならBOMを付けるとか、データインポートで実行するといった回避策はありますが、日本語のみならShift-JISで出力したいことが多いと思います。

このためにはあらかじめShift-JISに変換しておく必要があり、

$sjis = array_map(create_function(’$str’, ‘return mb_convert_encoding($str, “Shift-JIS”, “UTF-8″);’), $line);

といった処理をやっていましたが、正直めんどうです。

ダブルコーテーションのエスケープにバグがある
カンマが含まれる場合、ダブルコーテーションで囲ってくれます。
ダブルコーテーションが含まれる場合、ダブルコーテーションを二重にしてエスケープしてくれます。
しかし、\” (バックスラッシュ+ダブルコーテーション)があった場合、なぜかこの部分のダブルコーテーションは二重にしてくれません。

ということで、結局自前でやった方が便利です。
mb_str_replaceは、http://fetus.k-hsu.net/document/programming/php/mb_str_replace.htmlを使わせて頂きました。

function _fputcsv($fp, $data, $toEncoding='Shift-JIS', $srcEncoding='UTF-8') {
	require_once 'mb_str_replace.php';

	$csv = '';
	foreach ($data as $col) {
		if (is_numeric($col)) {
			$csv .= $col;
		} else {
			$col = mb_convert_encoding($col, $toEncoding, $srcEncoding);
			$col = mb_str_replace('"', '""', $col, $toEncoding);
			$csv .= '"' . $col . '"';
		}
		$csv .= ',';
	}

	fwrite($fp, $csv);
	fwrite($fp, "\r\n");
}

ちゃんとテストしていないのでミスがあるかもしれませんが、このくらいシンプルで十分そうですね。

SET NAMESが危険な理由のおさらい

2010.02.17    PHP, セキュリティ, 馬場   タグ: —    baba   

1年くらい前に社内MLに投げた、「MySQLでSET NAMESを使ってはいけない理由」をコピペしてみます。手抜きです、はい。
赤字は注釈です。


今更ながら、「MySQLで SET NAMES を使ってはいけない」の根拠のお話です。

下記のPHPスクリプトでは、入力値を元にSQL文を生成し、検索クエリを投げています。
※sqltestというDBには、カラムnameを持つuserテーブルが存在します。

GETで渡された値はきちんとmysql_real_escape_stringをかけているので、SQLインジェクションは出来ないように見えます。
しかし、
http://localhost/sqltest/index.php?name=%95%5c’%20OR%201=1%20–%20
にアクセスすると、全部のデータが見えてしまいます。
下にあるPHPスクリプトを、localhost/sqltest/index.php として配置してください。

“SET NAMES SJIS” を実行すると、MySQLのエンコードがShift-JISになりますが、mysql_real_escape_stringはUTF-8のまま動作します。
16進数で 95 5c 27 20 は、
UTF-8: (謎の文字)(バックスラッシュ)(シングルコーテーション)(スペース)
Shift-JIS: (表)(シングルコーテーション)(スペース)
になります。
mysql_real_escape_stringは、バックスラッシュとシングルコーテーションそれぞれをエスケープします。
95 5c 5c 5c 27 20
MySQLは、Shift-JISとして動作するので、 (表)(\)(\)(シングルコーテーション)(スペース) と認識します。
つまり、\が一つ余分に入ることで、入力値のシングルコーテーションがエスケープされなくなります。

マルチバイト非対応のエスケープ関数を使うのと同じ理屈で、SET NAMES は危険です。
基本的に文字コード中に5Cが入るShift-JISが危険ですが、他の文字コードでも似たようなことが起こる可能性があります。
mysql_set_charset(’SJIS’);
なら、mysql_real_escape_stringもShift-JISとして動作するようになるので、安全です。


<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>SQLテストページ</title>
<head>
</head>
<body>

<form action="<?php echo $_SERVER['SCRIPT_NAME'];?>">
	<input type="text" name="name" />
	<input type="submit" value="search" />
</form>

<?php
	//DBに接続
	if (! $db = mysql_connect('localhost', 'sqltest', 'sqltest'))
	{
		echo 'CONNECT ERROR';
		exit;
	}
	mysql_select_db('sqltest', $db);

	mysql_query('SET NAMES SJIS');
//	mysql_set_charset('SJIS');

	//SQLを生成
	$name = mysql_real_escape_string($_REQUEST['name']);
	$sql = "SELECT * FROM user WHERE name='$name'";
	echo $sql . '<br />';

	//実行
	if (! $res = mysql_query($sql))
	{
		echo "QUERY ERROR <br />";
		echo mysql_error();
		exit;
	}

	echo "<pre>";
	while ($row = mysql_fetch_array($res)) {
		print_r($row);
	}
	echo "</pre>";
?>

</body>
</html>

CakePHPでDBのエンコーディングを指定

2010.02.16    CakePHP, PHP, 馬場   タグ: —    baba   

PHPでMySQL 5をUTF8にして使う場合、文字が全部????になってしまう問題を防ぐため、

default-character-set=utf8
character-set-server=utf8
collation-server=utf8_general_ci
skip-character-set-client-handshake

を指定する、というのが定石になっています(よね?)。

しかし、共用サーバなどでmy.iniを編集できない場合、mysql_set_charset() などを使うと思います。

CakePHPでは、bootstrapでDB設定を読んで手動で設定するのも面倒だな、と思っていたら、さすがはCakePHP。便利な設定が。
app/config/database.php

var $default = array(
	'driver' => 'mysql',
	'persistent' => false,
	'host' => 'localhost',
	'login' => 'baba',
	'password' => 'hoge',
	'database' => 'testdb',
	'prefix' => '',
    'encoding' => 'utf8',
);

このようにencodingを指定するだけで、勝手にSET NAMESやmysql_set_charset()を実行してくれます。便利便利。

XAMPP 1.7.3 でいろいろ困った

2010.02.11    PHP, 馬場   タグ: —    baba   

XAMPP はPHPやMySQLなどもいっぺんに入ってとても便利ですが、その分バージョン違いで混乱することも多いですね。

最新版1.7.3 では、PHPも5.3.1になっていて、以下の点で既存システムが動かなくなりました。php.iniのデフォルトが変わったようです。

エラー表示をE_ALLにしたら、deprecatedが大量に出る
php.ini で error_reporting = E_ALL & ~E_DEPRECATED にすればOK

cURLエクステンションが無効で、PEAR::XML_RPC2が動かない(エラーも出ない)
php.ini で extension=php_curl.dll のコメントアウトを外せばOK

特定の環境に慣れていると、開発したシステムの動作環境を正確に把握・記述することを怠ってしまうので、注意しないといけませんね。

CakePHP paginatorのsortだけを使う

2010.01.28    CakePHP, PHP, 馬場      baba   

CakePHPのpaginatorは、ページングの他にソートもやってくれて大変便利です。

しかし、たまに「ソートはするけどページングしたくない」ということもありますよね?
そんなとき、$this->paginate['limit'] = null のようにやると、Zero Divide でエラーになります。paginatorの中ではそんなことは想定されていません。

ゼロからソート部分を作るのも面倒なので、paginatorのソート部分だけを無理矢理使わせてもらいましょう。
コントローラで以下のように処理します。

//手動でsort
$order = null;
if (isset($this->passedArgs['sort'])) {
	$direction = null;
	if (isset($this->passedArgs['direction'])) {
		$direction = strtolower($this->passedArgs['direction']);
	}
	if ($direction != 'asc' && $direction != 'desc') {
		$direction = 'asc';
	}
	$order = array($this->passedArgs['sort'] => $direction);
}

//paginate処理はするが、そのデータは使わない
$this->paginate();

//全件を表示
$result = $this->Model->find('all', array('order' => $order)));

この方法なら、ビューでは、

$paginator->sort(’なまえ’, ‘User.name’);

のように、普通にpaginatorを使うときの同じようにかけます。

強引であまり褒められた方法ではありませんが、これで目的は達成できました。

PHPで呼ぶたびに値を変えてみよう

2010.01.21    PHP, 馬場      baba   

開発時のテスト用に、呼ぶたびに2つの値(緯度・経度)の値が0.1ずつ増えて表示されるようなPHPスクリプトが欲しくなりました。
セッションが使えれば良いのですが、今回はクライアント側が特殊で上手く使えないため、サーバ上のファイルに保存してしまいます。

要するに値が2つ保存されるアクセスカウンタと同じです。とにかく、例外処理などを省いて簡単にすることだけを考えてみます。

//初期値 (BPSの位置です)
//35.508642
//139.442771
$latitude = 35.508642;
$longitude = 139.442771;

//前回のデータを読み込む
$data = eval('return ' . file_get_contents('data.txt') . ';');
if (is_array($data)) {
	extract($data);
}
$latitude += 0.1;
$longitude += 0.1;

//新しいデータを保存
file_put_contents('data.txt', var_export(array('latitude' => $latitude, 'longitude' => $longitude), true));

//表示
echo $latitude . '\n' . $longitude;

突っ込みどころ満載ですが、スクリプト言語なんだから、たまにはevalが使いたいなーと思って書いただけですので・・・

WordPressのテーマ内でページの内容を表示する

2010.01.19    PHP, 馬場   タグ: —    baba   

WordPressでも、ページを上手く使えばTOPページなどの各種ページを作って、それぞれにテーマを割り当てることが出来ます。

これらのページをCMS管理する際、ページコンテンツをすべて1ページにしてしまうと、編集者がうっかりHTMLを書き換えて、レイアウトが崩れてしまう恐れがあります。
そこで、「新着情報」「ごあいさつ」のように1ページ内の各種コンテンツごとにページを用意すれば、安全に編集できます。

テーマファイル内で特定ページの内容を表示するには、以下のようにします。

<?php
 $page = query_posts('pagename=news');
 echo $page['page_content'];
?>

このようにquery_postsを使えばOKなのですが、ページID指定の p=2 といった書式では、ページを取得できません(投稿のみ対応しているようです)。
かならず、pagename=news のようにページスラッグを指定する必要があります。

テーマファイルとページが強く結合してしまい、美しい使い方とは言えませんが、WordPressで綺麗さを追求すると痛い目を見るので・・・

いちばん簡単なJSON

2010.01.15    PHP, Zend Framework, 馬場   タグ: —    baba   

Ajaxを使う際、PHP側からJSON形式の値を出力したいことは良くあると思います(入力はだいたいPOSTで事足りますね)。

PHPでJSON形式の値を出力したい場合、いちばん簡単なのはjson_encode()です。

//本来はheaderなども正しく処理した方が良いが省略
$list = array('apple', 'orange', 'banana');
echo json_encode($list);

ただし、json_encode()はPHP 5.2.0以上の関数なので、それ未満の環境だとundefined functionでエラーになります。
http://php.net/manual/en/function.json-encode.php

リリース後にPHPのバージョン違いに気がついた可哀想な人には、Zend_Jsonがおすすめです。
まずはZend FrameworkのページからZend Frameworkをダウンロードします(minimalでOK)。

そして、回答したフォルダのZend/Json.phpと、Zend/Jsonフォルダをinclude_pathが通ったフォルダにコピーします。

あとは、コード内で

require_once 'Zend/Json.php';
$list = array('apple', 'orange', 'banana');
echo Zend_Json::encode($list);

とやるだけ。
関数の名前が変わるだけで、同じ使い勝手が実現できていますね。これなら一括置換も簡単。

PEAR::XML_RPC2で簡単サーバ

2010.01.13    PHP, ネットワーク, 馬場   タグ: —    baba   

XML_RPCのクライアントを開発するとき、ダミーでサーバが必要になります。
手っ取り早いダミーとして、PHPのPEAR::XML_RPC2が便利です。以前クライアント側は紹介しましたが、今回はサーバの超基本的な使い方。

ダウンロード
http://pear.php.net/package/XML_RPC2からダウンロードできます。

使い方
適当な場所に設置したら、以下のようにincludeして、各アクションに対応する関数を定義します。
そして、XML_RPC_Serverでアクション名と実行するアクションの対応を配列で渡せばOKです。

require_once("XML/RPC.php");
require_once("XML/RPC/Server.php");
$GLOBALS['XML_RPC_defencoding'] = "UTF-8";

function  version($params) {
	return new XML_RPC_Response(new XML_RPC_Value('1.0', 'string'));
}

function average($params) {
	$v1 = $params->getParam(0)->scalarval();
	$v2 = $params->getParam(1)->scalarval();
	return new XML_RPC_Response(new XML_RPC_Value($v1 + $v2, 'int'));
}

new XML_RPC_Server(array(
	"Test.version" => array("function" => "version"),
	"Test.average" => array("function" => "average"),
));

とってもかんたん。

古い投稿 »

COPYRIGHT [C] 2009 BEYOND PERSPECTIVE SOLUTIONS LTD. ALL RIGHTS RESERVED.