mysql_*
関数を使用すべきではない技術的な理由は何ですか? (例: mysql_query()
、mysql_connect()
またはmysql_real_escape_string()
)?
自分のサイトで機能する場合でも、他のものを使用する必要があるのはなぜですか?
自分のサイトで機能しない場合、次のようなエラーが表示されるのはなぜですか
警告: mysql_connect(): そのようなファイルまたはディレクトリはありません
MySQL 拡張機能:
非推奨であるため、これを使用すると、コードの将来性が低下します。
準備済みステートメントのサポートの欠如は、別の関数呼び出しで手動でエスケープするよりも、外部データをエスケープおよび引用するためのより明確でエラーが発生しにくい方法を提供するため、特に重要です。
SQL 拡張機能の比較を参照してください。
PHP は、MySQL に接続するための 3 つの異なる API を提供します。これらはmysql
(PHP 7 で削除された)、、、mysqli
およびPDO
拡張子です。
これらのmysql_*
関数は以前は非常に人気がありましたが、使用は推奨されていません。ドキュメンテーション チームはデータベースのセキュリティ状況について議論しており、一般的に使用されている ext/mysql 拡張機能を使用しないようにユーザーを教育することもその一環です ( php.internals: deprecating ext/mysqlを確認してください)。
そして、後の PHP 開発者チームは、ユーザーが MySQL に接続するときにエラーを生成するという決定を下しE_DEPRECATED
ました。mysql_connect()
mysql_pconnect()
ext/mysql
ext/mysql
はPHP 5.5 で公式に非推奨となり、PHP 7 で削除されました。
赤い箱が見えますか?
mysql_*
関数のマニュアルページに移動すると、赤いボックスが表示され、もう使用しないことを説明しています。
から離れるext/mysql
ということは、セキュリティだけでなく、MySQL データベースのすべての機能にアクセスできるということでもあります。
ext/mysql
はMySQL 3.23用に構築され、それ以来ほとんど追加されていませんが、ほとんどの場合、この古いバージョンとの互換性を維持しているため、コードの保守が少し難しくなっています。ext/mysql
includeでサポートされていない欠落している機能: ( PHP マニュアルから)。
mysql_*
関数を使用しない理由:
準備済みステートメントのサポートの欠如は、別の関数呼び出しで手動でエスケープするよりも、外部データをエスケープおよび引用するためのより明確でエラーが発生しにくい方法を提供するため、特に重要です。
SQL 拡張機能の比較を参照してください。
非推奨警告の抑制
コードがMySQLi
/に変換されている間、php.iniで除外するように設定することでPDO
、E_DEPRECATED
エラーを抑制できます。error_reporting
E_DEPRECATED:
error_reporting = E_ALL ^ E_DEPRECATED
これにより、他の廃止予定の警告も非表示になることに注意してください。ただし、これは MySQL 以外のものである可能性があります。( PHPマニュアルより)
記事PDO vs. MySQLi: どちらを使うべきか? Dejan Marjanovicによって選択するのに役立ちます。
そして、より良い方法は です。PDO
私は今、簡単なPDO
チュートリアルを書いています。
A.「<strong>PDO – PHP Data Objects – は、複数のデータベースへの統一されたアクセス方法を提供するデータベース アクセス レイヤーです。」</p>
関数を使用mysql_*
するか、古い方法で言うことができます (PHP 5.5 以降では非推奨)
$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);
With : 新しいオブジェクトPDO
を作成するだけです。PDO
コンストラクターは、データベース ソースを指定するためのパラメーターを受け入れます。コンストラクターは、PDO
主にDSN
(データ ソース名) とオプションusername
でpassword
.
ここでは、 を除くすべてに精通していると思いますDSN
。これは の新機能ですPDO
。Aは基本的に、使用するドライバーと接続の詳細を示すDSN
オプションの文字列です。PDO
詳細については、PDO MySQL DSNを確認してください。
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');
注:も使用できますがcharset=UTF-8
、エラーが発生する場合があるため、 を使用することをお勧めしますutf8
。
PDOException
接続エラーが発生した場合は、さらに処理するためにキャッチできるオブジェクトがスローされますException
。
よく読んでください:接続と接続管理 ¶
また、複数のドライバー オプションを配列として 4 番目のパラメーターに渡すこともできます。PDO
例外モードにするパラメータを渡すことをお勧めします。一部のPDO
ドライバーはネイティブの準備済みステートメントをサポートしていないためPDO
、準備のエミュレーションを実行します。また、このエミュレーションを手動で有効にすることもできます。ネイティブのサーバー側のプリペアド ステートメントを使用するには、明示的に設定する必要がありますfalse
。
もう 1 つは、MySQL
デフォルトでドライバーで有効になっている準備エミュレーションをオフにすることですが、PDO
安全に使用するには準備エミュレーションをオフにする必要があります。
なぜ準備エミュレーションをオフにする必要があるのかについては、後で説明します。理由を見つけるには、この投稿を確認してください。
MySQL
私が推奨していない古いバージョンを使用している場合にのみ使用できます。
以下は、その方法の例です。
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8',
'username',
'password',
array(PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
PDO の構築後に属性を設定できますか?
はい、setAttribute
メソッドを使用して PDO 構築後にいくつかの属性を設定することもできます。
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8',
'username',
'password');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
エラー処理はPDO
よりもはるかに簡単ですmysql_*
。
使用時の一般的な方法mysql_*
は次のとおりです。
//Connected to MySQL
$result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));
OR die()
ではエラーを処理できないため、エラーを処理する良い方法ではありませんdie
。スクリプトを突然終了し、通常はエンド ユーザーに表示したくないエラーを画面に表示し、血まみれのハッカーがスキーマを発見できるようにします。あるいは、mysql_*
多くの場合、関数の戻り値をmysql_error()と組み合わせて使用して、エラーを処理できます。
PDO
より良い解決策を提供します: 例外です。私たちが行うものはすべて-ブロックPDO
で囲む必要があります。エラー モード属性を設定することで、3 つのエラー モードのいずれかに強制的に移行できます。以下に 3 つのエラー処理モードを示します。try
catch
PDO
PDO::ERRMODE_SILENT
. エラーコードを設定するだけで、各結果を確認してからエラーの詳細をmysql_*
確認する必要がある場合とほとんど同じように機能します。$db->errorInfo();
PDO::ERRMODE_WARNING
上げE_WARNING
ます。(実行時の警告 (致命的ではないエラー)。スクリプトの実行は停止されません。)PDO::ERRMODE_EXCEPTION
: 例外をスローします。これは、PDO によって発生したエラーを表します。PDOException
独自のコードからa をスローしないでください。PHP の例外の詳細については、例外を参照してください。or die(mysql_error());
キャッチされていない場合は、 と非常によく似た動作をします。ただし、 とは異なりor die()
、PDOException
選択した場合は、 を適切にキャッチして処理できます。よく読んでください:
お気に入り:
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
try
そして、以下のように-でラップすることができcatch
ます:
try {
//Connect as appropriate as above
$db->query('hi'); //Invalid query!
}
catch (PDOException $ex) {
echo "An Error occured!"; //User friendly message/message you want to show to user
some_logging_function($ex->getMessage());
}
今すぐtry
-を扱う必要はありません。catch
適切なタイミングでキャッチできますが、try
-を使用することを強くお勧めしますcatch
。PDO
また、ものを呼び出す関数の外側でキャッチする方が理にかなっている場合があります。
function data_fun($db) {
$stmt = $db->query("SELECT * FROM table");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
//Then later
try {
data_fun($db);
}
catch(PDOException $ex) {
//Here you can handle error and show message/perform action you want.
}
また、 で処理することも、or die()
のように言うこともできますがmysql_*
、本当にさまざまです。エラー ログを参照するだけで、本番環境で危険なエラー メッセージを隠すことができdisplay_errors off
ます。
SELECT
さて、上記のすべてのことを読んだ後、おそらく次のように考えているでしょう:単純な、INSERT
、UPDATE
、またはDELETE
ステートメントの学習を開始したいのに、それはいったい何だろう? 心配する必要はありません。
だからあなたがしていることは次のmysql_*
とおりです:
<?php
$result = mysql_query('SELECT * from table') or die(mysql_error());
$num_rows = mysql_num_rows($result);
while($row = mysql_fetch_assoc($result)) {
echo $row['field1'];
}
ではPDO
、次のように実行できます。
<?php
$stmt = $db->query('SELECT * FROM table');
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo $row['field1'];
}
または
<?php
$stmt = $db->query('SELECT * FROM table');
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
//Use $results
注: 以下 ( query()
) のようなメソッドを使用している場合、このメソッドはPDOStatement
オブジェクトを返します。結果を取得したい場合は、上記のように使用します。
<?php
foreach($db->query('SELECT * FROM table') as $row) {
echo $row['field1'];
}
PDO データでは->fetch()
、ステートメント ハンドルのメソッドである を介して取得されます。fetch を呼び出す前に、データを取得する方法を PDO に伝えるのが最善の方法です。以下のセクションでは、これについて説明します。
PDO::FETCH_ASSOC
上記のfetch()
およびfetchAll()
コードでの の使用に注意してください。これはPDO
、フィールド名をキーとして行を連想配列として返すように指示します。他にも多くのフェッチモードがあり、1 つずつ説明します。
まず、フェッチ モードの選択方法を説明します。
$stmt->fetch(PDO::FETCH_ASSOC)
上記では、 を使用していfetch()
ます。以下も使用できます。
PDOStatement::fetchAll()
- 結果セットのすべての行を含む配列を返しますPDOStatement::fetchColumn()
- 結果セットの次の行から単一の列を返しますPDOStatement::fetchObject()
- 次の行を取得し、オブジェクトとして返します。PDOStatement::setFetchMode()
- このステートメントのデフォルトのフェッチ モードを設定します今、私はフェッチモードに来ます:
PDO::FETCH_ASSOC
: 結果セットで返される列名でインデックス付けされた配列を返しますPDO::FETCH_BOTH
(デフォルト): 結果セットで返される列名と 0 から始まる列番号の両方でインデックス付けされた配列を返します。さらに多くの選択肢があります!それらについては、PDOStatement
Fetch のドキュメントを参照してください。.
行数の取得:
mysql_num_rows
を使用して返された行数を取得する代わりに、次のように取得しPDOStatement
て実行できますrowCount()
。
<?php
$stmt = $db->query('SELECT * FROM table');
$row_count = $stmt->rowCount();
echo $row_count.' rows selected';
最後に挿入された ID を取得する
<?php
$result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')");
$insertId = $db->lastInsertId();
mysql_*
関数で行っていることは次のとおりです。
<?php
$results = mysql_query("UPDATE table SET field='value'") or die(mysql_error());
echo mysql_affected_rows($result);
また、pdo では、これと同じことが次の方法で実行できます。
<?php
$affected_rows = $db->exec("UPDATE table SET field='value'");
echo $affected_rows;
上記のクエリPDO::exec
では、SQL ステートメントを実行し、影響を受ける行の数を返します。
挿入と削除については後で説明します。
上記の方法は、クエリで変数を使用していない場合にのみ役立ちます。ただし、クエリで変数を使用する必要がある場合は、上記のように試行しないでください 。準備されたステートメントまたはパラメーター化されたステートメントがあります。
Q. プリペアードステートメントとは何ですか? なぜ必要なのですか?
A.準備済みステートメントは、データのみをサーバーに送信することによって複数回実行できるコンパイル済みの SQL ステートメントです。
プリペアド ステートメントを使用する一般的なワークフローは次のとおりです ( Wikipedia three 3 point から引用)。
準備: アプリケーションによってステートメント テンプレートが作成され、データベース管理システム (DBMS) に送信されます。パラメーター、プレースホルダー、またはバインド変数と呼ばれる特定の値は未指定のままです (?
以下にラベルを付けます)。
INSERT INTO PRODUCT (name, price) VALUES (?, ?)
DBMS は、ステートメント テンプレートに対してクエリの最適化を解析、コンパイル、および実行し、結果を実行せずに格納します。
1.00
指定できます。SQL にプレースホルダーを含めることで、準備済みステートメントを使用できます。基本的に、プレースホルダーのないものは 3 つ (上記の変数でこれを試さないでください)、名前のないプレースホルダーのあるもの、名前付きのプレースホルダーのあるものがあります。
Q.名前付きプレースホルダーとは何ですか?どのように使用すればよいですか?
A.名前付きプレースホルダー。疑問符の代わりに、コロンを前に付けたわかりやすい名前を使用してください。名前プレースホルダーの値の位置/順序は気にしません。
$stmt->bindParam(':bla', $bla);
bindParam(parameter,variable,data_type,length,driver_options)
実行配列を使用してバインドすることもできます。
<?php
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->execute(array(':name' => $name, ':id' => $id));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
友達にとってもう 1 つの便利な機能OOP
は、プロパティが名前付きフィールドに一致すると仮定して、名前付きプレースホルダーを使用してオブジェクトをデータベースに直接挿入できることです。例えば:
class person {
public $name;
public $add;
function __construct($a,$b) {
$this->name = $a;
$this->add = $b;
}
}
$demo = new person('john','29 bla district');
$stmt = $db->prepare("INSERT INTO table (name, add) value (:name, :add)");
$stmt->execute((array)$demo);
Q.名前のないプレースホルダーとは何ですか? また、どのように使用すればよいですか?
A.例を見てみましょう:
<?php
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->bindValue(1, $name, PDO::PARAM_STR);
$stmt->bindValue(2, $add, PDO::PARAM_STR);
$stmt->execute();
と
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->execute(array('john', '29 bla district'));
?
上記では、名前プレースホルダーのように名前の代わりにそれらを見ることができます。最初の例では、変数をさまざまなプレースホルダー ( $stmt->bindValue(1, $name, PDO::PARAM_STR);
) に割り当てます。次に、これらのプレースホルダーに値を割り当てて、ステートメントを実行します。2 番目の例では、最初の配列要素が最初の要素に、?
2 番目の要素が 2 番目の要素に移動し?
ます。
注:名前のないプレースホルダーPDOStatement::execute()
では、メソッドに渡す配列内の要素の適切な順序に注意する必要があります。
SELECT
, INSERT
, UPDATE
,DELETE
準備されたクエリSELECT
:
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->execute(array(':name' => $name, ':id' => $id));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
INSERT
:
$stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)");
$stmt->execute(array(':field1' => $field1, ':field2' => $field2));
$affected_rows = $stmt->rowCount();
DELETE
:
$stmt = $db->prepare("DELETE FROM table WHERE id=:id");
$stmt->bindValue(':id', $id, PDO::PARAM_STR);
$stmt->execute();
$affected_rows = $stmt->rowCount();
UPDATE
:
$stmt = $db->prepare("UPDATE table SET name=? WHERE id=?");
$stmt->execute(array($name, $id));
$affected_rows = $stmt->rowCount();
ただしPDO
、and/orMySQLi
は完全に安全というわけではありません。回答を確認してくださいSQL インジェクションを防ぐのに十分な PDO プリペアド ステートメントはありますか? によってircmaxell。また、彼の答えの一部を引用しています。
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES GBK');
$stmt = $pdo->prepare("SELECT * FROM test WHERE name = ? LIMIT 1");
$stmt->execute(array(chr(0xbf) . chr(0x27) . " OR 1=1 /*"));
分析的および総合的な理由はすでに述べました。新規参入者には、時代遅れの mysql_ 関数の使用をやめる、より重要なインセンティブがあります。
現代のデータベース API は使いやすいです。
コードを簡素化できるのは、主にバインドされたパラメーターです。また、優れたチュートリアル (上記を参照)により、 PDOへの移行はそれほど困難ではありません。
ただし、より大きなコード ベースを一度に書き直すには時間がかかります。この中間的な選択肢の存在理由:
< pdo_mysql.php >を使用すると、最小限の労力で古い mysql_ 関数から切り替えることができます。pdo_
対応するものを置き換える関数ラッパーを追加しますmysql_
。
データベースと対話する必要がある各呼び出しスクリプトで
単純に。include_once(
"pdo_mysql.php"
);
すべての関数プレフィックスを削除し、に置き換えます。mysql_
pdo_
mysql_
connect()
になるpdo_
connect()
mysql_
query()
になるpdo_
query()
mysql_
num_rows()
になるpdo_
num_rows()
mysql_
insert_id()
になるpdo_
insert_id()
mysql_
fetch_array()
になるpdo_
fetch_array()
mysql_
fetch_assoc()
になるpdo_
fetch_assoc()
mysql_
real_escape_string()
になるpdo_
real_escape_string()
コードは同様に機能し、ほとんど同じように見えます。
include_once("pdo_mysql.php");
pdo_connect("localhost", "usrABC", "pw1234567");
pdo_select_db("test");
$result = pdo_query("SELECT title, html FROM pages");
while ($row = pdo_fetch_assoc($result)) {
print "$row[title] - $row[html]";
}
ほら。
あなたのコードはPDOを使用しています。
いよいよ実際に活用する時が来ました。
扱いにくい API が必要なだけです。
pdo_query()
バインドされたパラメーターの非常に簡単なサポートを追加します。古いコードの変換は簡単です:
変数を SQL 文字列から移動します。
pdo_query()
。?
変数が以前にあったプレースホルダーとして疑問符を配置します。'
以前に文字列値/変数を囲んでいた単一引用符を取り除きます。この利点は、コードが長くなるほど顕著になります。
多くの場合、文字列変数は SQL に補間されるだけでなく、その間にエスケープ呼び出しで連結されます。
pdo_query("SELECT id, links, html, title, user, date FROM articles
WHERE title='" . pdo_real_escape_string($title) . "' OR id='".
pdo_real_escape_string($title) . "' AND user <> '" .
pdo_real_escape_string($root) . "' ORDER BY date")
プレースホルダーを適用すると?
、それを気にする必要はありません。
pdo_query("SELECT id, links, html, title, user, date FROM articles
WHERE title=? OR id=? AND user<>? ORDER BY date", $title, $id, $root)
pdo_* は引き続きまたは のいずれかを許可することに注意してください。変数を
エスケープして同じクエリにバインドしないでください。
:named
したがって、後でプレースホルダー リストも許可されます。さらに重要なことは、クエリの背後で $_REQUEST[] 変数を安全に渡すことができることです。送信された<form>
フィールドがデータベース構造と正確に一致する場合、それはさらに短くなります:
pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);
とてもシンプルです。しかし、なぜ削除してエスケープしたいのかについて、いくつかの書き直しのアドバイスと技術的な理由に戻りましょう。mysql_
sanitize()
古い学校の機能を修正または削除するすべての呼び出しをwith bound params に変換したらmysql_
pdo_query
、冗長なpdo_real_escape_string
呼び出しをすべて削除します。
sanitize
特に、古いチュートリアルで宣伝されているorclean
またはfilterThis
orclean_data
関数をいずれかの形式で修正する必要があります。
function sanitize($str) {
return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}
ここで最も明白なバグは、ドキュメントの欠如です。さらに重要なのは、フィルタリングの順序がまったく間違っていたことです。
正しい順序は次のとおりですstripslashes
。最も内側の呼び出しとして非推奨にtrim
、その後strip_tags
、htmlentities
出力コンテキストの として、最後に_escape_string
、そのアプリケーションが SQL インタースパースの直接前に実行される必要があります。
しかし、最初のステップとして、電話を取り除くだけです。_real_escape_string
sanitize()
データベースとアプリケーション フローが HTML コンテキスト セーフな文字列を想定している場合は、残りの関数を今のところ保持する必要があるかもしれません。今後は HTML エスケープのみを適用するというコメントを追加します。
文字列/値の処理は、PDO とそのパラメーター化されたステートメントに委譲されます。
サニタイズ機能に言及があっstripslashes()
た場合は、より高いレベルの見落としを示している可能性があります。
これは一般的に、非推奨の からのダメージ (二重エスケープ) を元に戻すためにありましたmagic_quotes
。ただし、文字列ごとではなく、中央で固定するのが最適です。
ユーザーランド反転アプローチのいずれかを使用します。stripslashes()
次に、関数内のを削除しsanitize
ます。
magic_quotes に関する歴史的なメモ。その機能は当然非推奨です。ただし、セキュリティ機能の失敗として誤って描写されることがよくあります。しかし、magic_quotes は、テニス ボールが栄養源として失敗したのと同じくらい失敗したセキュリティ機能です。それは単に彼らの目的ではありませんでした。
PHP2/FI の元の実装では、"引用符は自動的にエスケープされ、フォーム データを直接 msql クエリに渡すことが容易になります" だけで明示的に導入されました。特に、 mSQLは ASCII のみをサポートしていたため、誤って安全に使用できました。
その後、PHP3/Zend は MySQL 用の magic_quotes を再導入し、誤って文書化しました。しかし、もともとは便利な機能であり、セキュリティを目的としたものではありませんでした.
文字列変数を SQL クエリにスクランブルすると、従わなければならないほど複雑になるだけではありません。また、MySQL がコードとデータを再び分離するのは余計な作業です。
SQL インジェクションとは、データがコードコンテキストに流れ込むことです。データベース サーバーは、PHP が最初にクエリ句の間に変数を貼り付けた場所を後で見つけることができません。
バインドされたパラメーターを使用して、PHP コードで SQL コードと SQL コンテキスト値を分離します。しかし、舞台裏で再びシャッフルされることはありません (PDO::EMULATE_PREPARES を除く)。データベースは、不変の SQL コマンドと 1:1 の変数値を受け取ります。
この回答は、ドロップすることの読みやすさの利点に注意する必要があることを強調しています。この視覚的で技術的なデータ/コードの分離により、パフォーマンス上の利点 (値が異なるだけで INSERT を繰り返す) が生じることもあります。mysql_
パラメータ バインディングは、すべてのSQL インジェクションに対する魔法のワンストップ ソリューションではないことに注意してください。データ/値の最も一般的な用途を処理します。ただし、列名/テーブル識別子をホワイトリストに登録したり、動的句の構築を支援したり、単純な配列値リストを作成したりすることはできません。
これらのpdo_*
ラッパー関数は、コーディングしやすい一時停止 API を作成します。(MYSQLI
特異な関数シグネチャのシフトがなければ、これはほとんど可能性があったことです)。ほとんどの場合、実際の PDO も公開します。
書き換えは、新しい pdo_ 関数名を使用することにとどまる必要はありません。各 pdo_query() をプレーンな $pdo->prepare()->execute() 呼び出しに 1 つずつ移行できます。
ただし、もう一度単純化することから始めるのが最善です。たとえば、一般的な結果の取得:
$result = pdo_query("SELECT * FROM tbl");
while ($row = pdo_fetch_assoc($result)) {
foreach 反復だけで置き換えることができます。
foreach ($result as $row) {
または、直接かつ完全な配列の取得を行うこともできます。
$result->fetchAll();
ほとんどの場合、クエリが失敗した後に PDO や mysql_ が通常提供するよりも役立つ警告が表示されます。
したがって、うまくいけば、いくつかの実際的な理由と、ドロップする価値のある経路が視覚化されます。mysql_
pdoに切り替えるだけでは、まったく効果がありません。pdo_query()
また、それに対する単なるフロントエンドでもあります。
パラメータバインディングも導入するか、より優れたAPIから何か他のものを利用できない限り、それは無意味な切り替えです. 新規参入者に落胆を助長しないように、それが十分に単純に描かれていることを願っています. (教育は通常、禁止よりも効果的です。)
これは、おそらく機能する可能性のある最も単純なもののカテゴリに該当しますが、まだ非常に実験的なコードでもあります。ちょうど週末に書きました。ただし、多くの代替手段があります。PHPデータベースの抽象化をグーグルで検索して、少しブラウズしてください。このようなタスクのための優れたライブラリは、これまでもこれからもたくさんあります。
データベースとのやり取りをさらに簡素化したい場合は、Paris/Idiormなどのマッパーを試してみる価値があります。JavaScript で当たり障りのない DOM を使用する人がもういないのと同じように、最近では生のデータベース インターフェイスを子守する必要はありません。
mysql_
機能:
技術的な理由について言えば、非常に具体的でめったに使用されないものはごくわずかです。ほとんどの場合、人生でそれらを使用することはありません。
無知すぎるのかもしれませんが、
それらが必要な場合 - これらは、mysql 拡張機能から離れて、よりスタイリッシュでモダンな外観に移行する技術的な理由であることは間違いありません。
それにもかかわらず、いくつかの非技術的な問題もあり、経験を少し難しくする可能性があります
この後者の問題が問題です。
しかし、私の意見では、提案されたソリューションも優れているわけではありません。すべての PHP ユーザーが一度に SQL クエリを適切に処理する方法を学ぶというのは、
私にはあまりにも理想主義的な夢のように思えます。ほとんどの場合、mysql_* を mysqli_* に機械的に変更するだけで、アプローチは同じままです。特に、mysqli は準備済みステートメントの使用を信じられないほど苦痛で面倒なものにするためです。
言うまでもなく、ネイティブのプリペアド ステートメントは SQL インジェクションから保護するには不十分であり、mysqli も PDO も解決策を提供しません。
ですから、この正直な拡張と戦うのではなく、間違った慣行と戦い、正しい方法で人々を教育したいと思います.
また、いくつかの誤った、または重要でない理由もあります。
mysql_query("CALL my_proc");
いました)最後は興味深い点です。mysql ext はネイティブのプリペアド ステートメントをサポートしていませんが、安全のために必須ではありません。手動で処理されたプレースホルダーを使用して、準備されたステートメントを簡単に偽造できます (PDO と同じように)。
function paraQuery()
{
$args = func_get_args();
$query = array_shift($args);
$query = str_replace("%s","'%s'",$query);
foreach ($args as $key => $val)
{
$args[$key] = mysql_real_escape_string($val);
}
$query = vsprintf($query, $args);
$result = mysql_query($query);
if (!$result)
{
throw new Exception(mysql_error()." [$query]");
}
return $result;
}
$query = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d";
$result = paraQuery($query, $a, "%$b%", $limit);
出来上がり、すべてがパラメータ化され、安全です。
しかし、マニュアルの赤いボックスが気に入らない場合は、mysqli と PDO のどちらを選択するかという問題が発生します。
さて、答えは次のようになります。
大多数の PHP 関係者のように、生の API 呼び出しをアプリケーション コードで直接使用している場合 (これは本質的に間違った方法です) - PDO が唯一の選択肢です。まだ不完全ですが、多くの重要な機能を提供します。そのうちの 2 つは、PDO を mysqli と決定的に区別します。
そのため、平均的な PHP ユーザーで、ネイティブのプリペアド ステートメントを使用する際の頭痛の種を減らしたい場合は、PDO が唯一の選択肢です。
ただし、PDO も特効薬ではなく、苦労もあります。そこで、よくある落とし穴と複雑なケースの解決策をPDO タグ wiki
に書きました。
それにもかかわらず、拡張機能について話している人は、Mysqli と PDO に関する次の2 つの重要な事実を常に見逃しています。
準備済みステートメントは特効薬ではありません。準備済みステートメントを使用してバインドできない動的識別子があります。パラメータの数が不明な動的クエリがあり、クエリの構築が困難なタスクになっています。
mysqli_* 関数も PDO 関数も、アプリケーション コードに表示されるべきではありません。それらとアプリケーションコードの間には、バインディング、ループ、エラー処理などのすべての汚い仕事を内部で行い、アプリケーションコードをDRYでクリーン
にする抽象化レイヤーが必要です。特に、動的クエリ構築のような複雑なケースの場合。
そのため、PDO や mysqli に切り替えるだけでは不十分です。コード内で未加工の API 関数を呼び出す代わりに、ORM、クエリ ビルダー、または任意のデータベース抽象化クラスを使用する必要があります。
逆に、アプリケーション コードと mysql API の間に抽象化レイヤーがある場合、実際にはどのエンジンを使用してもかまいません。非推奨になるまで mysql ext を使用し、抽象化クラスを別のエンジンに簡単に書き直して、すべてのアプリケーション コードをそのままにすることができます。
このような抽象化クラスがどのようにあるべきかを示すために、私のsafemysql クラスに基づいたいくつかの例を次に示します。
$city_ids = array(1,2,3);
$cities = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);
この 1 行を PDO で必要なコードの量と比較してください。
次に、未加工の Mysqli 準備済みステートメントで必要となる非常に多くのコードと比較してください。エラー処理、プロファイリング、クエリ ロギングは既に組み込まれており、実行されていることに注意してください。
$insert = array('name' => 'John', 'surname' => "O'Hara");
$db->query("INSERT INTO users SET ?u", $insert);
通常の PDO 挿入と比較してください。すべての単一フィールド名が 6 回から 10 回繰り返されます。これらの多数の名前付きプレースホルダー、バインディング、およびクエリ定義のすべてにおいてです。
もう一つの例:
$data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);
PDO がこのような実際のケースを処理する例はほとんど見つかりません。
そして、それはあまりにも冗長で、おそらく安全ではありません.
繰り返しになりますが、懸念すべきは生のドライバーだけではなく、抽象化クラスであり、初心者向けマニュアルのばかげた例だけでなく、実際の問題を解決するのにも役立ちます。
多くの理由がありますが、おそらく最も重要な理由は、それらの関数がプリペアド ステートメントをサポートしていないため、安全でないプログラミング プラクティスを助長していることです。プリペアド ステートメントは、SQL インジェクション攻撃の防止に役立ちます。
関数を使用するときmysql_*
は、ユーザー指定のパラメーターを を通じて実行することを忘れないでくださいmysql_real_escape_string()
。1 か所だけ忘れたり、入力の一部だけを逃したりすると、データベースが攻撃を受ける可能性があります。
PDO
orで準備済みステートメントを使用mysqli
すると、この種のプログラミング エラーが発生しにくくなります。
(他の理由の中でも) 入力データがサニタイズされていることを確認するのがはるかに難しいためです。PDO や mysqli で行うように、パラメータ化されたクエリを使用すると、リスクを完全に回避できます。
例として、誰かが"enhzflep); drop table users"
ユーザー名として使用できます。古い関数では、クエリごとに複数のステートメントを実行できるため、その厄介なバグのようなものはテーブル全体を削除できます。
mysqli の PDO を使用すると、ユーザー名は"enhzflep); drop table users"
.
bobby-tables.comを参照してください。