※ パクレゼルヴではWeb開発エンジニアを大募集中!詳細はこちら

Archive

Author Archive

MySQLのdatetime型の問い合わせ時の注意  

2010/7/7 水曜日 19:46:21

こんばんは。MoriMoriMoriMoriです。

今回は、なんとなくうまく行きそうだからOKでしょ、
という感じでやると失敗するパターンについて書きます。

下記のような顧客情報テーブルがあったとします。

DESC users;
+----------+--------------+------+-----+---------+----------------+
| Field    | Type         | Null | Key | Default | Extra          |
+----------+--------------+------+-----+---------+----------------+
| id       | int(11)      | NO   | PRI | NULL    | auto_increment |
| name     | varchar(255) | NO   |     |         |                |
| birthday | date         | NO   |     |         |                |
| created  | datetime     | NO   |     |         |                |
+----------+--------------+------+-----+---------+----------------+

SELECT * FROM users;
+----+---------+------------+---------------------+
| id | name    | birthday   | created             |
+----+---------+------------+---------------------+
|  1 | takeshi | 1986-01-21 | 2010-02-10 11:40:30 |
|  2 | isamu   | 1980-01-05 | 2010-05-17 15:12:32 |
|  3 | mikiko  | 1983-12-12 | 2010-05-31 18:10:18 |
|  4 | fumio   | 1976-07-13 | 2010-06-05 09:37:49 |
+----+---------+------------+---------------------+

データが登録された日付(created)で
2010/1/1~2010/5/31までのデータを抽出したい、とします。

以下のSQLを投げてみましょう。

SELECT * FROM users WHERE created >= '2010-01-01' AND created <= '2010-05-31';

クエリの結果は以下。

+----+---------+------------+---------------------+
| id | name    | birthday   | created             |
+----+---------+------------+---------------------+
|  1 | takeshi | 1986-01-21 | 2010-02-10 11:40:30 |
|  2 | isamu   | 1980-01-05 | 2010-05-17 15:12:32 |
+----+---------+------------+---------------------+

そうです。
created <= '2010-05-31'
としてるにも関わらず、mikikoさんのデータが引っ張って来れてません。

datetime型にdate型で検索しようとすると、予想通りの結果になりません。
面倒くさがらず、以下のようなSQLにしましょう。

SELECT * FROM users WHERE created >= '2010-01-01 00:00:00' AND created <= '2010-05-31 23:59:59';

または

SELECT * FROM users WHERE created >= '2010-01-01 00:00:00' AND created < '2010-06-01 00:00:00';

MoriMoriMoriMori MySQL

久々にベタ書きで入力フォームを書いてみる  

2010/6/30 水曜日 15:47:47

モリモリモリモリです。

Webアプリは実際の業務ではCakePHP、symfony、Zend Framework等のPHPフレームワーク、
自社フレームワーク、またテンプレートエンジンとしてsmarty等を使うかと思います。

WEBプロミングを始めたばかりの人は何を参考にすればいいか割と苦労するかなと。
私自身がそうでした。

ベタ書きで簡単な入力フォームを書いたらどーなるかなと思って久々に書いてみることにしました。

以下は、セッションを利用したPC向けの書き方です。
手抜きな部分も有りますので参考程度にお願いします。

< ?php
session_start();

$param = array('name','tel');
foreach($param as $val){
	// SESSION値よりもPOST値を優先
	if(isset($_POST[$val])){
		${$val} = trim($_POST[$val]);
	}elseif(isset($_SESSION[$val])){
		${$val} = trim($_SESSION[$val]);
	}else{
		${$val} = '';
	}
}

// 確認ボタンが押された際の処理
if(isset($_POST['confirm_btn'])){
	// バリデーション
	$err = array();
	foreach($param as $val){
		switch($val){
			case 'name':
				if($name == ''){
					$err['name'] = '※名前は必須です。<br />';
				}
				break;
			case 'tel':
				if($tel == ''){
					$err['tel'] = '※電話番号は必須です。<br />';
				}elseif(!preg_match('/^\d+$/',$tel)){
					$err['tel'] = '※電話番号は数字で入力して下さい。<br />';
				}
				break;
			default:
		}
	}
	// エラーが無ければ
	if(count($err) == 0){
		// 値をセッションに格納
		foreach($param as $val){
			$_SESSION[$val] = ${$val};
		}
		// 確認画面へロケート
		header('Location:http://example.com/confirm.php');
		exit;
	}
}
?>
<html>
<body>
<form method="post" action="">
	< ?php
	if(isset($err['name'])){
		echo $err['name'];
	}
	?>
	名前: <input type="text" name="name" value="<?php echo htmlspecialchars($name); ?/>" /><br />
	< ?php
	if(isset($err['tel'])){
		echo $err['tel'];
	}
	?>
	電話番号: <input type="text" name="tel" value="<?php echo htmlspecialchars($tel); ?/>" /><br />
	<input type="submit" name="confirm_btn" value="確認" />
</form>
</body>
</html>

投稿の際に勝手に置換されたりするので、上記のコードをまんまコピペしてもうまく動きません。汗
適宜修正お願いします。
すいません。

5行目あたりからの処理の

${$val}

ですが、こんな感じで可変変数を使うことにより、$name、$telに入力値が入ってくるようになります。

これについては弊社の下記記事も参考にどうぞ。
http://labs.pakureserve.jp/archives/124

バリデーション処理もベタで書いてますがご了承下さい。

処理としては、エラーが無ければセッションに格納して、確認画面へロケーション。
確認画面ではセッションの値を表示
(表示の際は項目に応じてhtmlspecialchars、nl2br関数等を使って表示させましょう)し、
登録完了画面で登録が完了したら、

$_SESSION = array();

等でセッションを空にしてやる感じで。

あと、最近たまたま気付いたことですが、もしこのフォームのテキストフォームが一つだった場合、
IEでテキストを入力してENTERキーを押すと、多分動きません。笑

これはIEのバグ?

IEでテキストフォームが一つの場合、ENTERキーでSubmitした場合に、
なぜか type=”submit” のパラメータと値がpostされないのです。
つまり、この場合だと、$_POST['confirm_btn']のパラメータと値が送信されません。

これを回避するためには以下のようなテキストフォームをダミーで入れといてやれば大丈夫です。

<input type="text" style="display:none;" />

…美しくない!

MoriMoriMoriMori HTMLとか, PHP

簡単な入力フォームこそ気を付けよう  

2010/5/31 月曜日 22:57:17

phpで作る入力フォームでやってしまいがちな事を紹介します。

入力した情報を確認画面で表示させる、というよくあるパターン。
(ベタ書きで失礼します。)

▼form.php(入力画面)

<form method="post" action="./confirm.php">
	ニックネーム: <input type="text" name="title" value="" /><br />
	<input type="submit" value="送信" />
</form>

▼confirm.php(確認画面)

< ?php
if(isset($_POST['title']) && $_POST['title']){
	echo htmlspecialchars($_POST['title']);
}else{
	echo '未入力';
}
?>

これ、一見問題無さそうですが、ある文字を入力すると未入力と表示されてしまいます。

そうです。

0(ゼロ)です。

TRUEかFALSEかで判断しているため、post送信された0はFALSEとして見なされます。

なのでこうやりましょう。

▼confirm.php(確認画面)

< ?php
if(isset($_POST['title']) && $_POST['title'] != ''){
	echo htmlspecialchars($_POST['title']);
}else{
	echo '未入力';
}
?>

どうしても「0」という名前で登録したいんだ!というお客様がいないとも限りませんからね。。

 

さて、もう一つ紹介します。

クロスサイトスクリプティング(XSS)のお話です。

今度は同じphpファイル自身にpost送信するパターン。
▼http://example.com/test.php

< ?php
// なんらかの処理
?>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
…
</form>

action先に何の迷いも無く

$_SERVER['PHP_SELF']

と書くと、実はこれが結構危ないのです。

他のhtmlファイルで下記のようなソースを書き、上記のスクリプトへのリンクを貼りましょう。
そして、クリックしてみましょう。(攻撃!)

<a href="http://example.com/test.php/%22%3E%3Cscript%3Ealert('xss');%3C/script%3E/">攻撃</a>

JavaScriptが実行され、アラートが表示されるかと思います。

URLの後半部分はURLエンコードされたものです。

$_SERVER['PHP_SELF']は、渡されたURIのホスト部分の後から、
GETクエリ部分の前まで(?の前まで)をURLデコードした形で格納します。

つまり、$_SERVER ['PHP_SELF']は、

/test.php/"><script>alert('xss');</script>/

となるので、

<form method="post" action="/test.php/"><script>alert('xss');</script>/">

となり、JavaScriptが実行されてしまうわけです。

 
では結論。
このようにしましょう。

</form><form method="post" action="">
…
</form>
<form method="post" action="<?php echo $_SERVER['SCRIPT_NAME']; ?>">
…
</form>
<form method="post" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>">
…
</form>

空にしてあげるのが一番ラクかと思います。

MoriMoriMoriMori HTMLとか, PHP

カラーコード、カラーネームのバリデーション  

2010/4/30 金曜日 17:14:35

久しぶりの投稿です。
HNはチョコボールでしたがMoriMoriMoriMoriに変えます。
無意味にCamelCaseで。

管理画面でdivブロックのタイトル背景色なんかを設定する項目で、
入力項目に正しいカラーネーム、カラーコードが入力されているかどうかをチェックする
バリデーションの関数を紹介します。

// 有効なカラーネーム(147色)もしくはカラーコードかどうかチェック
function checkColor($str, $name)
{
	if($str == ''){
		return '';
	}
	// カラーネーム配列(147色)
	$arrColorName = array('aliceblue','antiquewhite','aqua','aquamarine','azure','beige','bisque','black','blanchedalmond','blue','blueviolet','brass','brown','burlywood','cadetblue','chartreuse','chocolate','coolcopper','copper','coral','cornflower','cornflowerblue','cornsilk','crimson','cyan','darkblue','darkbrown','darkcyan','darkgoldenrod','darkgray','darkgreen','darkkhaki','darkmagenta','darkolivegreen','darkorange','darkorchid','darkred','darksalmon','darkseagreen','darkslateblue','darkslategray','darkturquoise','darkviolet','deeppink','deepskyblue','dimgray','dodgerblue','feldsper','firebrick','floralwhite','forestgreen','fuchsia','gainsboro','ghostwhite','gold','goldenrod','gray','green','greenyellow','honeydew','hotpink','indianred','indigo','ivory','khaki','lavender','lavenderblush','lawngreen','lemonchiffon','lightblue','lightcoral','lightcyan','lightgoldenrodyellow','lightgreen','lightgrey','lightpink','lightsalmon','lightseagreen','lightskyblue','lightslategray','lightsteelblue','lightyellow','lime','limegreen','linen','magenta','maroon','mediumaquamarine','mediumblue','mediumorchid','mediumpurple','mediumseagreen','mediumslateblue','mediumspringgreen','mediumturquoise','mediumvioletred','midnightblue','mintcream','mistyrose','moccasin','navajowhite','navy','oldlace','olive','olivedrab','orange','orangered','orchid','palegoldenrod','palegreen','paleturquoise','palevioletred','papayawhip','peachpuff','peru','pink','plum','powderblue','purple','red','richblue','rosybrown','royalblue','saddlebrown','salmon','sandybrown','seagreen','seashell','sienna','silver','skyblue','slateblue','slategray','snow','springgreen','steelblue','tan','teal','thistle','tomato','turquoise','violet','wheat','white','whitesmoke','yellow','yellowgreen');

	// 精査用に小文字に統一
	$str = strtolower($str);
	if(!preg_match("/^[0-9a-f]{6}$/",$str) && !preg_match("/^[0-9a-f]{3}$/",$str) && !in_array($str, $arrColorName)){
		return $name.'のカラー指定が不正です。<br />';
	}
	return '';
}

データベースへの登録の際、カラムには、カラーネームの場合はそのまま入れ、
カラーコードの場合は、「#ffcc33」のように先頭に#を付けて登録してあげたほうが良いかと思います。
なぜなら、出力の度にカラーコードの場合は#を付ける、
といった判定をする処理を入れるのはパフォーマンスが悪いからです。
微々たる差異ですが。

出力の際のコーディング(ベタ書き)です。

if($title['bgcolor'] == ''){
	echo '<div style="text-align:center;">'
}else{
	echo '<div style="text-align:center;background-color:'.$title['bgcolor'].';">';
}
echo 'ブロックタイトル';

XHTMLでは16進数のカラーコードの英字は小文字で指定した方が良いので、
XHTML前提で利用する場合、「FFCCCC」のように入力された場合は強制的に
「#ffcccc」のように小文字にして(#もつけて)登録してあげてもいいかもしれません。
また、入力欄にカラーコードの場合、#つきで入力されても通るようにしてあげるとさらに親切かと。

fontタグのcolor属性の値やbodyタグのtext属性の値等は、
3桁の16進数での表記は当然ダメですよ!

■ダメな例

<body text="#000">
<font color="#fff">

ついでに書きますが、XHTMLでの属性checkedの誤った書き方。

<input type="radio" checked />

属性=”属性値”という書き方が基本なので、

<input type="radio" checked="checked" />

と書きましょう!

以上、MoriMoriMoriMoriMoriMoriでした。

MoriMoriMoriMori HTMLとか, MySQL, PHP, データベース

DBにNULLが入ってる時にIFNULLを!  

2009/3/2 月曜日 18:52:58

DBの設計上、NULLが入っている場合の集計の際にちょっとハマりました。

下記のような注文テーブル(orders)があったとします。

DESC orders;
+-----------+---------+------+-----+---------+----------------+
| Field     | Type    | Null | Key | Default | Extra          |
+-----------+---------+------+-----+---------+----------------+
| id        | int(11) | NO   | PRI | NULL    | auto_increment |
| total     | int(11) | YES  |     | NULL    |                |
| sub_total | int(11) | YES  |     | NULL    |                |
+-----------+---------+------+-----+---------+----------------+

SELECT * FROM orders;
+----+-------+-----------+
| id | total | sub_total |
+----+-------+-----------+
|  1 |  1050 |       500 |
|  2 |  1575 |      NULL |
+----+-------+-----------+

各注文のtotalからsub_totalを差し引いた金額(エイリアスをcostとする)を取得したい時、

SELECT (total - sub_total) AS cost FROM orders;

このようにすると、NULLの場合は0として計算してくれるだろうと思っていたら甘かったです。
id: 1のcostは550、id: 2のcostはNULLとなります。

SELECT (IFNULL(total,0) - IFNULL(sub_total,0))
AS cost FROM orders;

NULLが入っている可能性がある場合は、このようにIFNULLを使ってやればNULLならば0として計算してくれます。

IFNULL(expr1,expr2)

expr1がNULLでない場合はexpr1を返し、それ以外の場合はexpr2を返します。

MoriMoriMoriMori MySQL, データベース