SQLの窓

2018年02月06日


Windows 環境の MySQL + PHP で、ODBC ドライバを使用してデータを取得する場合の3パターン

MySQL ドキュメント : 5.2 Connector/ODBC Connection Parameterscharset パラメータがあります。

'character_set' の環境が全て utf8 という環境(character_set_results は空)が前提で、PHP のコードを SHIFT_JIS にすると何も問題無く処理できます。しかし、今では一般的な UTF-8(UTF8N) で PHP のコードを使用して、charset を utf8 にしても、戻されるデータはのみが正しく UTF-8 で取得できるだけで、渡す SQL が SHIFT_JIS である必要があるのと、戻る列名も SHIFT_JIS なので2箇所を変換する必要があります。

詳細は良く解りませんが、古い32ビットのソフトウェアで charset を cp932 にして正常に動作している環境でデータをエクスポートすると 列データと insert されたデータのキャラクタセットが違うという結果になります。この事から、日本語用の ODBC ドライバは SHIFT_JIS に特化した処理を行っているのかもしれません。

そもそも、MySQL を Windows で使用するにしても、mysqli を使用すれば PHP サイドからすると事は単純になりますが、テーブルの作成等を PHP ベースで行う事はめったにないので詳しい検証はできていません。

最後のパターン

charset を cp932 にして PHP を UTF-8 で作成する方法です。UTF-8 の環境の中に無理やり shift_jis の環境をねじ込むようなやり方になっていますが(列もデータも変換する)、Microsoft Access を対象とした ODBC 経由の処理と同じになり、これはこれでいろいろ有効な方法です。

PHP:shift_jis ODBC:cp932
<?php
header( "Content-Type: text/plain; charset=shift_jis" );

$host = "localhost";$db = "lightbox";$user = "root";$pass="pass";
$connect_string = "Driver={MySQL ODBC 5.3 Unicode Driver};server={$host};database={$db};charset=cp932";
$odbc = odbc_connect($connect_string, $user, $pass);

$query = "select * from 社員マスタ";

if ($result = odbc_exec( $odbc, $query) ) {

	while( $row = odbc_fetch_array( $result ) ) {
		$rows[] = $row;
	}
	odbc_free_result($result);
}

odbc_close( $odbc );

print_r($rows);

?>


PHP:utf-8 ODBC:utf8
<?php
header( "Content-Type: text/plain; charset=utf-8" );

$host = "localhost";$db = "lightbox";$user = "root";$pass="pass";
$connect_string = "Driver={MySQL ODBC 5.3 Unicode Driver};server={$host};database={$db};charset=utf8";
$odbc = odbc_connect($connect_string, $user, $pass);

$query = "select * from 社員マスタ";
$query = mb_convert_encoding($query,"MS932","utf-8");

if ($result = odbc_exec( $odbc, $query) ) {

	while( $row = odbc_fetch_array( $result ) ) {

		$new_row = array();
		foreach( $row as $k => $v ) {
			$new_row[mb_convert_encoding($k,"utf-8","MS932")] = $v;
		}
		$row = $new_row;

		$rows[] = $row;

	}
	odbc_free_result($result);
}

odbc_close( $odbc );

print_r($rows);

?>


PHP:utf-8 ODBC:cp932
<?php
header( "Content-Type: text/plain; charset=utf-8" );

$host = "localhost";$db = "lightbox";$user = "root";$pass="pass";
$connect_string = "Driver={MySQL ODBC 5.3 Unicode Driver};server={$host};database={$db};charset=cp932";
$odbc = odbc_connect($connect_string, $user, $pass);

$query = "select * from 社員マスタ";
$query = mb_convert_encoding($query,"MS932","utf-8");

if ($result = odbc_exec( $odbc, $query) ) {

	while( $row = odbc_fetch_array( $result ) ) {

		$new_row = array();
		foreach( $row as $k => $v ) {
			$new_row[mb_convert_encoding($k,"utf-8","MS932")] = mb_convert_encoding($v,"utf-8","MS932");
		}
		$row = $new_row;

		$rows[] = $row;

	}
	odbc_free_result($result);
}

odbc_close( $odbc );

print_r($rows);

?>



posted by lightbox at 2018-02-06 23:26 | PHP + データベース | このブログの読者になる | 更新情報をチェックする

2017年06月09日


PHP : SQLインジェクション対策付きの、MySQL のデータを JSON で返す自家製 API テスト用のテンプレート3パターン

特記事項

1) ブラウザの JavaScript からドメイン違いでも呼び出せるように、Access-Control-Allow-Origin を使用
2) pretty=no で、整形しません。escape=no で、日本語はそのまま返します(デバッグ優先)

real_escape_string で SQLインジェクション対策

SQL の実行に query メソッドを使用します。その為、SQL は単純に文字列で作成するので、real_escape_string で条件内の値を $_GET より $get に変換して使用します。
<?php
error_reporting( E_ALL & ~E_NOTICE );

session_cache_limiter('nocache');
session_start();

// 取得対象列
$list = "社員コード,氏名,フリガナ,所属,性別,作成日,更新日,給与,手当,管理者";
// json encode オプション
$flg = 0;

// JavaScript ajax 用
header( "Access-Control-Allow-Origin: *" );
header( "Content-Type: application/json; charset=utf-8" );

// DB 接続
$mysqli = new mysqli('サーバ', 'ユーザ', 'パスワード', 'データベース');
if ( $mysqli->connect_error ) {
	// 接続失敗
	$json = array( "error" => "Connect Error ({$mysqli->connect_errno}) {$mysqli->connect_error}" );
}
else {
	// SQL インジェクション用
	foreach( $_GET as $key => $value ) {
		// $_GET を $get で代替
		$get[$key] = $mysqli->real_escape_string( $value );
	}
	// json encode オプション
	if ( $get["pretty"] != "no" ) {
		$flg = $flg | JSON_PRETTY_PRINT;
	}
	if ( $get["escape"] != "no" ) {
		$flg = $flg | JSON_UNESCAPED_UNICODE;
	}

	$mysqli->set_charset("utf8");

	// ~E_NOTICE なので、単純化
	if ( $_GET["name"] == "" ) {
		$query = "select {$list} from 社員マスタ";
	}
	else {
		$query = "select {$list} from 社員マスタ";
		// 条件オプション
		$query .= " where 氏名 like '%{$get["name"]}%'";
	}

	$result = $mysqli->query($query);
	if ( $result === false ) {
		// SQL 失敗
		$json = array( "error" => "$mysqli->error : {$query}" );
	}
	else {
		// DB 読み出し成功
		$json = $result->fetch_all( MYSQLI_ASSOC );
	}
}

print json_encode( $json, $flg );

?>


bind_param で SQLインジェクション対策

prepare メソッドを使用すると、bind_param メソッドを使用して入力値から SQL を作成する事ができます。そして、結果として get_result メソッド を使用する事によって mysqli_result を取得できるので、その後は パターン(1) と同じになります
<?php
error_reporting( E_ALL & ~E_NOTICE );

session_cache_limiter('nocache');
session_start();

// 取得対象列
$list = "社員コード,氏名,フリガナ,所属,性別,作成日,更新日,給与,手当,管理者";
// json encode オプション
$flg = 0;

// JavaScript ajax 用
header( "Access-Control-Allow-Origin: *" );
header( "Content-Type: application/json; charset=utf-8" );

// DB 接続
$mysqli = new mysqli('サーバ', 'ユーザ', 'パスワード', 'データベース');
if ( $mysqli->connect_error ) {
	// 接続失敗
	$json = array( "error" => "Connect Error ({$mysqli->connect_errno}) {$mysqli->connect_error}" );
}
else {
	// json encode オプション
	if ( $_GET["pretty"] != "no" ) {
		$flg = $flg | JSON_PRETTY_PRINT;
	}
	if ( $_GET["escape"] != "no" ) {
		$flg = $flg | JSON_UNESCAPED_UNICODE;
	}

	$mysqli->set_charset("utf8");

	// ~E_NOTICE なので、単純化
	if ( $_GET["name"] == "" ) {
		$query = "select {$list} from 社員マスタ";
	}
	else {
		$query = "select {$list} from 社員マスタ";
		// 条件オプション
		$query .= " where 氏名 like ?";
	}

	// SQL 準備
	$stmt = $mysqli->prepare($query);
	if ( $stmt === false ) {
		// SQL 失敗
		$json = array( "error1" => "$mysqli->error : {$query}" );
	}
	else {
		// バインドは変数で
		$param = "%{$_GET["name"]}%";

		// パラメータの数が一致しない場合の warning を出さない為の @ 抑制
		@$stmt->bind_param('s', $param );
		if ( false === $stmt->execute() ) {
			$json = array( "error2" => "$mysqli->error" );
		}
		else {
			// MySQL ネイティブドライバ限定(PHP 5.3.0 以降)
			$result = $stmt->get_result();
			if ( $result === false ) {
				$json = array( "error3" => "$mysqli->error" );
			}
			else {
				// DB 読み出し成功
				$json = $result->fetch_all( MYSQLI_ASSOC );
			}
		}
	}
}

print json_encode( $json, $flg );

?>


get_result メソッド を使え無い場合の方法

get_result メソッドは、MySQL ネイティブドライバ限定(PHP 5.3.0 以降)なので、代替方法です。$stmt->store_result() => $stmt->bind_result => $stmt->fetch という流れになります。
<?php
error_reporting( E_ALL & ~E_NOTICE );

session_cache_limiter('nocache');
session_start();

// 取得対象列
$list = "社員コード,氏名,フリガナ,所属,性別,作成日,更新日,給与,手当,管理者";
// json encode オプション
$flg = 0;

// JavaScript ajax 用
header( "Access-Control-Allow-Origin: *" );
header( "Content-Type: application/json; charset=utf-8" );

// DB 接続
$mysqli = new mysqli('サーバ', 'ユーザ', 'パスワード', 'データベース');
if ( $mysqli->connect_error ) {
	// 接続失敗
	$json = array( "error" => "Connect Error ({$mysqli->connect_errno}) {$mysqli->connect_error}" );
}
else {
	// json encode オプション
	if ( $_GET["pretty"] != "no" ) {
		$flg = $flg | JSON_PRETTY_PRINT;
	}
	if ( $_GET["escape"] != "no" ) {
		$flg = $flg | JSON_UNESCAPED_UNICODE;
	}

	$mysqli->set_charset("utf8");

	// ~E_NOTICE なので、単純化
	if ( $_GET["name"] == "" ) {
		$query = "select {$list} from 社員マスタ";
	}
	else {
		$query = "select {$list} from 社員マスタ";
		// 条件オプション
		$query .= " where 氏名 like ?";
	}

	// SQL 準備
	$stmt = $mysqli->prepare($query);
	if ( $stmt === false ) {
		// SQL 失敗
		$json = array( "error1" => "$mysqli->error : {$query}" );
	}
	else {
		// バインドは変数で
		$param = "%{$_GET["name"]}%";

		// パラメータの数が一致しない場合の warning を出さない為の @ 抑制
		@$stmt->bind_param('s', $param );
		if ( false === $stmt->execute() ) {
			$json = array( "error2" => "$mysqli->error" );
		}
		else {
			// get_result() を使わない方法
			$stmt->store_result();
			$row_data = new StdClass;
			$stmt->bind_result(
				$row_data->{"社員コード"},
				$row_data->{"氏名"},
				$row_data->{"フリガナ"},
				$row_data->{"所属"},
				$row_data->{"性別"},
				$row_data->{"作成日"},
				$row_data->{"更新日"},
				$row_data->{"給与"},
				$row_data->{"手当"},
				$row_data->{"管理者"}
			);
			$json = array();
			while ($stmt->fetch()) {
				$json[] = $row_data;
			}
		}
	}
}

print json_encode( $json, $flg );

?>




posted by lightbox at 2017-06-09 20:43 | PHP + データベース | このブログの読者になる | 更新情報をチェックする

2017年04月15日


PHP で MySQLをテストする為のソースコード


※ $_GET['text'] で入力された SQL が引き渡されます。
※ GET コマンドなので、内容はアドレスバーで確認する事ができます
※ Form を使わずにアドレスバーから直接 SQL を実行できます。

MySQL 改良版拡張モジュール
<?php
session_cache_limiter('nocache');
session_start();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SQL実行結果</title> 
</head>
<body style='white-space:pre;'>
<?php
print $_GET['text'] . "\n";

$server = 'localhost';
$dbname = 'lightbox';
$user = 'root';
$password = 'パスワード';

// ***************************
// 接続
// ***************************
$mysqli = @ new mysqli($server, $user, $password, $dbname);
if ($mysqli->connect_error) {
	print "接続エラーです : ({$mysqli->connect_errno}) ({$mysqli->connect_error})";
	exit();
}

// ***************************
// クライアントの文字セット
// ***************************
$mysqli->set_charset("utf8"); 

// ***************************
// クエリ
// ***************************
$result = $mysqli->query($_GET['text']);
if ( !$result ) {
	print "\n";
	print "<span style='color:#f00'>" . $mysqli->error . "</span>";
}

// ***************************
// 列数
// ***************************
$nfield = $result->field_count;
if ( $nfield ) {
	$ncount = 0;
	print "<table style='border:solid 1px #000;border-collapse:collapse;'>\n";

	print "\t<th style='border:solid 1px #000;padding:5px;'></th>\n";

	$field = $result->fetch_fields( );
	for( $i = 0; $i < $nfield; $i++ ) {
		print "\t<th style='border:solid 1px #000;padding:5px;'>{$field[$i]->name}</th>\n";
	}

	while ($row = $result->fetch_row()) {
		print "<tr>\n";
		print "\t<td style='border:solid 1px #000;padding:5px;'>" . ($ncount + 1) . "</td>\n";
		for( $i = 0; $i < $nfield; $i++ ) {

			print "\t<td style='border:solid 1px #000;padding:5px;'>{$row[$i]}</td>\n";
		}
		print "</tr>\n";
		$ncount++;
	}
	print "</table>";
}

// ***************************
// 接続解除
// ***************************
$mysqli->close();
?>

</body>
</html>


PHP 5.5.0 で非推奨になり、PHP 7.0.0 で削除
<?php
session_cache_limiter('nocache');
session_start();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SQL実行結果</title> 
</head>
<body style='white-space:pre;'>
<?php
print $_GET['text'] . "\n";

$server = 'localhost';
$dbname = 'lightbox';
$user = 'root';
$password = 'パスワード';

// ***************************
// 接続
// ***************************
$connect = @mysql_connect( $server, $user, $password );
if ( !$connect ) {
	print "接続エラーです";
	exit();
}

// ***************************
// DB選択
// ***************************
mysql_select_db( $dbname, $connect );
mysql_set_charset("utf8", $connect); 

// ***************************
// クエリ
// ***************************
$result = mysql_query($_GET['text'], $connect);
if ( !$result ) {
	print "\n";
	print "<span style='color:#f00'>" . mysql_error() . "</span>";
}

// ***************************
// 列数
// ***************************
$nfield = @mysql_num_fields( $result );
if ( $nfield ) {
	$ncount = 0;
	print "<table style='border:solid 1px #000;border-collapse:collapse;'>\n";

	print "\t<th style='border:solid 1px #000;padding:5px;'></th>\n";
	for( $i = 0; $i < $nfield; $i++ ) {
		$field = mysql_fetch_field( $result, $i );

		print "\t<th style='border:solid 1px #000;padding:5px;'>{$field->name}</th>\n";
	}

	while ($row = mysql_fetch_row($result)) {
		print "<tr>\n";
		print "\t<td style='border:solid 1px #000;padding:5px;'>" . ($ncount + 1) . "</td>\n";
		for( $i = 0; $i < $nfield; $i++ ) {
			print "\t<td style='border:solid 1px #000;padding:5px;'>{$row[$i]}</td>\n";
		}
		print "</tr>\n";
		$ncount++;
	}
	print "</table>";
}

// ***************************
// 接続解除
// ***************************
mysql_close($connect);
?>

</body>
</html>

※ このコードではセキュリティ上の考慮はされていません。



posted by lightbox at 2017-04-15 19:47 | PHP + データベース | このブログの読者になる | 更新情報をチェックする
Seesaa の各ページの表示について
Seesaa の 記事がたまに全く表示されない場合があります。その場合は、設定> 詳細設定> ブログ設定 で 最新の情報に更新の『実行ボタン』で記事やアーカイブが最新にビルドされます。

Seesaa のページで、アーカイブとタグページは要注意です。タグページはコンテンツが全く無い状態になりますし、アーカイブページも歯抜けページはコンテンツが存在しないのにページが表示されてしまいます。

また、カテゴリページもそういう意味では完全ではありません。『カテゴリID-番号』というフォーマットで表示されるページですが、実際存在するより大きな番号でも表示されてしまいます。

※ インデックスページのみ、実際の記事数を超えたページを指定しても最後のページが表示されるようです

対処としては、このようなヘルプ的な情報を固定でページの最後に表示するようにするといいでしょう。具体的には、メインの記事コンテンツの下に『自由形式』を追加し、アーカイブとカテゴリページでのみ表示するように設定し、コンテンツを用意するといいと思います。


※ エキスパートモードで表示しています

アーカイブとカテゴリページはこのように簡単に設定できますが、タグページは HTML 設定を直接変更して、以下の『タグページでのみ表示される内容』の記述方法で設定する必要があります

<% if:page_name eq 'archive' -%>
アーカイブページでのみ表示される内容
<% /if %>

<% if:page_name eq 'category' -%>
カテゴリページでのみ表示される内容
<% /if %>

<% if:page_name eq 'tag' -%>
タグページでのみ表示される内容
<% /if %>
この記述は、以下の場所で使用します
container 終わり

フリーフォントで簡単ロゴ作成
フリーフォントでボタン素材作成
フリーフォントで吹き出し画像作成
フリーフォントではんこ画像作成
ほぼ自由に利用できるフリーフォント
フリーフォントの書体見本とサンプル
画像を大きく見る為のウインドウを開くボタンの作成

CSS ドロップシャドウの参考デモ
BUTTONS (CSS でボタン)
イラストAC
ぱくたそ
写真素材 足成
フリーフォント一覧
utf8 文字ツール
右サイド 終わり
base 終わり