まず、結論を。
「型なしでも、型を強く意識して開発する事」です。
次に、型なしと型ありの定義ですが、
ここでは、数値(int型等)と文字列(String型等)の比較が「出来るかどうか」とします。
型なしは「出来て」、型ありは「出来ない」です。
また、結構恐い内容も出てきますが、
別に脅すつもりは更々ありません。
単に事実を知って頂きたいと思っているだけです。
1.条件分岐において
以下は、各種バージョン情報です。
OS
↓
Fedora12
PHP
↓
5.3.0
Java
↓
1.6.0.10
・まず、PHPで書いた場合。
-------------------------------------------------------
<?php
$b = 0;
$c = 'a1';
if($b == $c) {
echo "bとcは同じです\n";
} else {
echo "bとcは違います\n";
}
?>
結果:
[root@www ~]# php a.php
bとcは
同じです
[root@www ~]#
-------------------------------------------------------
なぜ、こうなるのかは以下辺りのマニュアルをご参照下さい。
PHP: 比較演算子 - Manual
PHP: 文字列 - Manual
・一方、Javaで書いた場合。
-------------------------------------------------------
class a {
public static void main(String args[]) {
int b = 0;
String c = "a1";
if(b == c) {
System.out.println("bとcは同じです");
} else {
System.out.println("bとcは違います");
}
}
}
結果:
[root@www ~]# javac a.java
a.java:8: 型 int と java.lang.String は比較できません。
if(b == c) {
^
エラー 1 個
[root@www ~]#
-------------------------------------------------------
「javac」というのは、あくまで
コンパイルするだけであって、
プログラムを実行するわけではありません。
実行するには、「java」と入力しますが、
javaを実行するには、javacでエラーが1件も出ない事が条件になります。
つまり、上記の「やっている事は同じコード」でも、
PHP→実行可能
Java→実行不可能
となるわけです。
ちなみに、ちょっと恐い話ですが、PHPにおいて以下の様なケースも。
-------------------------------------------------------
<?php
// WEB画面上で入力したパスワード
$input_password = '0010000';
// DBに保存されているパスワード
$db_password = '10e3';
if($input_password == $db_password) {
echo "パスワードが同じです\n";
} else {
echo "パスワードが違います\n";
}
?>
結果:
[root@www ~]# php b.php
パスワードが
同じです
[root@www ~]#
-------------------------------------------------------
上記は、個人的に浮動小数点数が苦手なので、はっきり分かりませんが、
$input_password = (float) '0010000';
$db_password = (float) '10e3';
var_dump($input_password);
var_dump($db_password);
上記の結果が、
float(10000)
float(10000)
となるので、おそらくfloatに変換してから比較していると思われます。
更にちなみに、文字列型以外でも安心は出来なくて、
if(0 == false)
上記も「true」となります。
・解決方法
マニュアルにもヒントが書いてありますが、以外と簡単で、
「==(イコール2つ)」を、「===(イコール3つ)」にする
です。
個人的には、パッと見で型を判断出来るメリットがあると思い、
文字列型だけはstrcmp関数を使って比較しています。
2.WHERE句の比較において
以下は、各種バージョン情報です。
OS
↓
Fedora12
MySQL
↓
5.1.41
Oracle
↓
11.1.0.6.0
・まず、MySQLの場合
-------------------------------------------------------
テーブル作成~データ確認
mysql> CREATE TABLE a (
-> user_id VARCHAR(100) NOT NULL primary key,
-> password VARCHAR(100) NOT NULL
-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.03 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into a values('a', 'b');
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from a;
+---------+----------+
| user_id | password |
+---------+----------+
| a | b |
+---------+----------+
1 row in set (0.00 sec)
mysql>
ここから、以下のSQL文を実行します。
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> delete from a where user_id = 0;
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from a;
Empty set (0.00 sec)
mysql>
-------------------------------------------------------
上記の通り、「0」と「'a'」が同じと見なされて、
データが削除されています。
なぜ、こうなるのかは現時点では判明していませんが、
PHPと同じく、0と'a'の比較時に、
'a'が0に変換されてから比較されていると予想します。
実際、DELETE文のWHERE句を0→1に変更したら、
データは削除されなかったので。
・一方、Oracleの場合
-------------------------------------------------------
テーブル作成~データ確認
SQL> CREATE TABLE a (
user_id VARCHAR2(100) NOT NULL primary key,
password VARCHAR2(100) NOT NULL
); 2 3 4
Table created.
SQL> insert into a values('a', 'b');
1 row created.
SQL> commit;
Commit complete.
SQL> select * from a;
USER_ID
--------------------------------------------
PASSWORD
--------------------------------------------
a
b
SQL>
ここから、以下のSQL文を実行します。
SQL> delete from a where user_id = 0;
delete from a where user_id = 0
*
ERROR at line 1:
ORA-01722: invalid number
SQL>
-------------------------------------------------------
上記の様に、「0」と「'a'」が別と見なされてエラーが発生しました。
ちなみに、DELETE文のWHERE句を0→'0'に変更した場合。
エラーは発生しなくなりますが、やはり'a'とは一致せずに正常に終了します。
そして、上記のMySQLの挙動が、以下の処理の様な時に問題となります。
-------------------------------------------------------
<?php
$user_id = 'test';
$result = null;
$conn = new mysqli('ホスト名', 'ユーザー名', 'パスワード', 'データベース名', ポート番号);
$conn->set_charset('utf8');
$conn->autocommit(false);
$stmt = $conn->prepare('DELETE FROM a WHERE user_id = ?');
$stmt->bind_param('i', $user_id);
$stmt->execute();
if(0 < $stmt->affected_rows) {
$result = "削除成功\n";
$conn->commit();
} else {
$result = "削除失敗\n";
$conn->rollback();
}
$stmt->close();
$conn->close();
echo $result;
?>
結果:
[root@www ~]# php bind_param_test.php
削除成功
[root@www ~]#
上記の実行時に発行されるSQLは以下の通りです。
MySQLのクエリログから抜粋しています。
Prepare DELETE FROM a WHERE user_id = ?
Execute DELETE FROM a WHERE user_id = 0
-------------------------------------------------------
やはり、データが削除されます。
ポイントは、mysqli_stmt::bind_param(
マニュアル)の第1引数です。
'i'は数値型を期待しているのに、文字列型を渡している為、
文字列→数値への変換により予期せぬ値になってしまっています。
・解決方法
変数の型(上記では$user_id)が文字列型で、
DB内のカラムの型(上記ではuser_id)がVARCHAR(文字列型)なのですから、
mysqli_stmt::bind_paramの第1引数も's'とすればよいだけです。
第2引数以降を文字列型と判断し、シングルクォーテーションで囲んでくれます。
・PostgreSQLは?
PostgreSQL8.4.4において。
VARCHARで定義されているカラムの値(例:'a'など)と、
数値型の値(例:0など)を比較しようとすると、「エラーになります」。
なので、この部分についてはOracleと同じの様です。
3.まとめ
PHP試験が秋に開始、オライリー本が教科書 - @ITや、
LAMP経験者のニーズ高し。ポテンシャル採用も - @IT自分戦略研究所を見ての通り、
PHPとMySQLに対するニーズが非常に高まってきています。
よって、これからプログラミングを始める人達も、
PHPとMySQLからというケースも多いと思います。
とりあえず、そういった方を見かけたら、
可能な限り、「型の存在と重要性」を伝えてあげれば良いのではないでしょうか。
テーマ:日記 - ジャンル:日記
- 2010/08/01(日) 10:10:42|
- プログラミング
-
| トラックバック:0
-
| コメント:0