2645

mysql_*関数を使用すべきではない技術的な理由は何ですか? (例: mysql_query()mysql_connect()またはmysql_real_escape_string())?

自分のサイトで機能する場合でも、他のものを使用する必要があるのはなぜですか?

自分のサイトで機能しない場合、次のようなエラーが表示されるのはなぜですか

警告: mysql_connect(): そのようなファイルまたはディレクトリはありません

4

13 に答える 13

2187

MySQL 拡張機能:

  • アクティブな開発中ではない
  • PHP 5.5 (2013 年 6 月リリース) で正式に廃止されました。
  • PHP 7.0 (2015 年 12 月リリース) で 完全削除されました。
    • これは、2018 年 12 月 31 日の時点で、サポートされているどのバージョンの PHP にも存在しないことを意味します。それをサポートする PHP のバージョンを使用している場合は、セキュリティの問題が修正されていないバージョンを使用しています。
  • オブジェクト指向インターフェースがない
  • サポートしていません:
    • ノンブロッキングの非同期クエリ
    • 準備済みステートメントまたはパラメーター化されたクエリ
    • ストアド プロシージャ
    • 複数のステートメント
    • 取引
    • 「新しい」パスワード認証方法 (MySQL 5.6 ではデフォルトで有効、5.7 では必須)
    • MySQL 5.1 以降の新機能のいずれか

非推奨であるため、これを使用すると、コードの将来性が低下します。

準備済みステートメントのサポートの欠如は、別の関数呼び出しで手動でエスケープするよりも、外部データをエスケープおよび引用するためのより明確でエラーが発生しにくい方法を提供するため、特に重要です。

SQL 拡張機能の比較を参照してください。

于 2012-10-12T13:23:43.763 に答える
1340

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/mysqlPHP 5.5 で公式に非推奨となり、PHP 7 で削除されました

赤い箱が見えますか?

mysql_*関数のマニュアルページに移動すると、赤いボックスが表示され、もう使用しないことを説明しています。

どうして


から離れるext/mysqlということは、セキュリティだけでなく、MySQL データベースのすべての機能にアクセスできるということでもあります。

ext/mysqlはMySQL 3.23用に構築され、それ以来ほとんど追加されていませんが、ほとんどの場合、この古いバージョンとの互換性を維持しているため、コードの保守が少し難しくなっています。ext/mysqlincludeでサポートされていない欠落している機能: ( PHP マニュアルから)。

mysql_*関数を使用しない理由:

  • 活発な開発中ではない
  • PHP 7 で削除されました
  • オブジェクト指向インターフェースがない
  • ノンブロッキングの非同期クエリをサポートしていません
  • 準備されたステートメントまたはパラメーター化されたクエリをサポートしていません
  • ストアド プロシージャをサポートしていません
  • 複数のステートメントをサポートしていません
  • トランザクションをサポートしていません
  • MySQL 5.1 のすべての機能をサポートしているわけではありません

クエンティンの回答から引用された上記のポイント

準備済みステートメントのサポートの欠如は、別の関数呼び出しで手動でエスケープするよりも、外部データをエスケープおよび引用するためのより明確でエラーが発生しにくい方法を提供するため、特に重要です。

SQL 拡張機能の比較を参照してください。


非推奨警告の抑制

コードがMySQLi/に変換されている間、php.iniで除外するように設定することでPDOE_DEPRECATEDエラーを抑制できます。error_reportingE_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

これにより、他の廃止予定の警告も非表示になることに注意してください。ただし、これは MySQL 以外のものである可能性があります。( PHPマニュアルより)

記事PDO vs. MySQLi: どちらを使うべきか? Dejan Marjanovicによって選択するのに役立ちます。

そして、より良い方法は です。PDO私は今、簡単なPDOチュートリアルを書いています。


シンプルで短い PDO チュートリアル


Q. 私の頭に浮かんだ最初の質問は、「PDO」とは何ですか?

A.「<strong>PDO – PHP Data Objects – は、複数のデータベースへの統一されたアクセス方法を提供するデータベース アクセス レイヤーです。」</p>

代替テキスト


MySQL への接続

関数を使用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(データ ソース名) とオプションusernamepassword.

ここでは、 を除くすべてに精通していると思います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 つのエラー処理モードを示します。trycatchPDO

  • 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-を使用することを強くお勧めしますcatchPDOまた、ものを呼び出す関数の外側でキャッチする方が理にかなっている場合があります。

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さて、上記のすべてのことを読んだ後、おそらく次のように考えているでしょう:単純な、INSERTUPDATE、またはDELETEステートメントの学習を開始したいのに、それはいったい何だろう? 心配する必要はありません。


データの選択

PDO 選択イメージ

だからあなたがしていることは次の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()ます。以下も使用できます。

今、私はフェッチモードに来ます:

  • PDO::FETCH_ASSOC: 結果セットで返される列名でインデックス付けされた配列を返します
  • PDO::FETCH_BOTH(デフォルト): 結果セットで返される列名と 0 から始まる列番号の両方でインデックス付けされた配列を返します。

さらに多くの選択肢があります!それらについては、PDOStatementFetch のドキュメントを参照してください。.

行数の取得:

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();

ステートメントの挿入と更新または削除

PDO イメージの挿入と更新

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 から引用)。

  1. 準備: アプリケーションによってステートメント テンプレートが作成され、データベース管理システム (DBMS) に送信されます。パラメーター、プレースホルダー、またはバインド変数と呼ばれる特定の値は未指定のままです (?以下にラベルを付けます)。

    INSERT INTO PRODUCT (name, price) VALUES (?, ?)

  2. DBMS は、ステートメント テンプレートに対してクエリの最適化を解析、コンパイル、および実行し、結果を実行せずに格納します。

  3. Execute : 後で、アプリケーションがパラメーターの値を提供 (またはバインド) し、DBMS がステートメントを実行します (場合によっては結果を返します)。アプリケーションは、異なる値を使用して何度でもステートメントを実行できます。この例では、最初のパラメーターと 2 番目のパラメーターに「Bread」を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準備されたクエリ

  1. 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);
    
  2. INSERT:

    $stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)");
    $stmt->execute(array(':field1' => $field1, ':field2' => $field2));
    $affected_rows = $stmt->rowCount();
    
  3. DELETE:

    $stmt = $db->prepare("DELETE FROM table WHERE id=:id");
    $stmt->bindValue(':id', $id, PDO::PARAM_STR);
    $stmt->execute();
    $affected_rows = $stmt->rowCount();
    
  4. 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 /*"));
于 2013-01-01T11:52:50.563 に答える
227

使いやすさ

分析的および総合的な理由はすでに述べました。新規参入者には、時代遅れの mysql_ 関数の使用をやめる、より重要なインセンティブがあります。

現代のデータベース API は使いやすいです。

コードを簡素化できるのは、主にバインドされたパラメーターです。また、優れたチュートリアル (上記を参照)により、 PDOへの移行はそれほど困難ではありません。

ただし、より大きなコード ベースを一度に書き直すには時間がかかります。この中間的な選択肢の存在理由:

mysql_* の代わりに相当する pdo_ *関数

< pdo_mysql.php >を使用すると、最小限の労力で古い mysql_ 関数から切り替えることができます。pdo_対応するものを置き換える関数ラッパーを追加しますmysql_

  1. データベースと対話する必要がある各呼び出しスクリプトで 単純に。include_once("pdo_mysql.php");

  2. すべて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()
    • 等々...

  3. コードは同様に機能し、ほとんど同じように見えます。

    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_* は引き続きまたは のいずれかを許可することに注意してください。変数を
エスケープして同じクエリにバインドしないでください。

  • プレースホルダー機能は、その背後にある実際の PDO によって提供されます。
  • :namedしたがって、後でプレースホルダー リストも許可されます。

さらに重要なことは、クエリの背後で $_REQUEST[] 変数を安全に渡すことができることです。送信された<form>フィールドがデータベース構造と正確に一致する場合、それはさらに短くなります:

pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);

とてもシンプルです。mysql_しかし、なぜ削除してエスケープしたいのかについて、いくつかの書き直しのアドバイスと技術的な理由に戻りましょう。

sanitize()古い学校の機能を修正または削除する

mysql_すべての呼び出しをwith bound params に変換したらpdo_query、冗長なpdo_real_escape_string呼び出しをすべて削除します。

sanitize特に、古いチュートリアルで宣伝されているorcleanまたはfilterThisorclean_data関数をいずれかの形式で修正する必要があります。

function sanitize($str) {
   return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}

ここで最も明白なバグは、ドキュメントの欠如です。さらに重要なのは、フィルタリングの順序がまったく間違っていたことです。

  • 正しい順序は次のとおりですstripslashes。最も内側の呼び出しとして非推奨にtrim、その後strip_tagshtmlentities出力コンテキストの として、最後に_escape_string、そのアプリケーションが SQL インタースパースの直接前に実行される必要があります。

  • しかし、最初のステップとして、電話を取り除くだけです。_real_escape_string

  • sanitize()データベースとアプリケーション フローが HTML コンテキスト セーフな文字列を想定している場合は、残りの関数を今のところ保持する必要があるかもしれません。今後は HTML エスケープのみを適用するというコメントを追加します。

  • 文字列/値の処理は、PDO とそのパラメーター化されたステートメントに委譲されます。

  • サニタイズ機能に言及があっstripslashes()た場合は、より高いレベルの見落としを示している可能性があります。

    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 の変数値を受け取ります。

この回答は、ドロップすることの読みやすさの利点に注意する必要があることを強調していますmysql_。この視覚的で技術的なデータ/コードの分離により、パフォーマンス上の利点 (値が異なるだけで INSERT を繰り返す) が生じることもあります。

パラメータ バインディングは、すべてのSQL インジェクションに対する魔法のワンストップ ソリューションではないことに注意してください。データ/値の最も一般的な用途を処理します。ただし、列名/テーブル識別子をホワイトリストに登録したり、動的句の構築を支援したり、単純な配列値リストを作成したりすることはできません。

ハイブリッド PDO の使用

これらの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_query()また、それに対する単なるフロントエンドでもあります。

パラメータバインディングも導入するか、より優れたAPIから何か他のものを利用できない限り、それは無意味な切り替えです. 新規参入者に落胆を助長しないように、それが十分に単純に描かれていることを願っています. (教育は通常、禁止よりも効果的です。)

これは、おそらく機能する可能性のある最も単純なもののカテゴリに該当しますが、まだ非常に実験的なコードでもあります。ちょうど週末に書きました。ただし、多くの代替手段があります。PHPデータベースの抽象化をグーグルで検索して、少しブラウズしてください。このようなタスクのための優れたライブラリは、これまでもこれからもたくさんあります。

データベースとのやり取りをさらに簡素化したい場合は、Paris/Idiormなどのマッパーを試してみる価値があります。JavaScript で当たり障りのない DOM を使用する人がもういないのと同じように、最近では生のデータベース インターフェイスを子守する必要はありません。

于 2013-12-24T23:30:38.677 に答える
158

mysql_機能:

  1. 古くなっています - それらはもはや維持されていません
  2. 別のデータベース バックエンドに簡単に移動できないようにする
  3. 準備済みステートメントをサポートしていないため、
  4. プログラマーが連結を使用してクエリを作成することを奨励し、SQL インジェクションの脆弱性につながる
于 2012-10-12T13:22:42.787 に答える
114

技術的な理由について言えば、非常に具体的でめったに使用されないものはごくわずかです。ほとんどの場合、人生でそれらを使用することはありません。
無知すぎるのかもしれませんが、

  • ノンブロッキング、非同期クエリ
  • 複数の結果セットを返すストアド プロシージャ
  • 暗号化 (SSL)
  • 圧縮

それらが必要な場合 - これらは、mysql 拡張機能から離れて、よりスタイリッシュでモダンな外観に移行する技術的な理由であることは間違いありません。

それにもかかわらず、いくつかの非技術的な問題もあり、経験を少し難しくする可能性があります

  • これらの関数を最新の PHP バージョンでさらに使用すると、非推奨レベルの通知が発生します。それらは単にオフにすることができます。
  • 遠い将来、デフォルトの PHP ビルドから削除される可能性があります。mydsql ext は PECL に移行され、サイトが何十年も機能していたクライアントを失いたくないため、すべてのホスティング事業者はそれを使用して PHP をコンパイルすることを喜んで行うため、大したことではありません。
  • Stackoverflow コミュニティからの強い抵抗。これらの正直な機能について言及するたびに、それらは厳密なタブーになっていると言われます。
  • 平均的な PHP ユーザーの場合、これらの関数を使用するというあなたの考えは、エラーが発生しやすく、間違っている可能性があります。間違った方法を教えてくれる数多くのチュートリアルやマニュアルがあるからです。関数自体ではなく、強調しなければなりませんが、関数の使用方法です。

この後者の問題が問題です。
しかし、私の意見では、提案されたソリューションも優れているわけではありません。すべての 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 のどちらを選択するかという問題が発生します。

さて、答えは次のようになります。

  • データベース抽象化レイヤーを使用する必要性を理解し、それを作成するための API を探している場合、mysqliは非常に良い選択です。これは実際に多くの mysql 固有の機能をサポートしているためです。
  • 大多数の PHP 関係者のように、生の API 呼び出しをアプリケーション コードで直接使用している場合 (これは本質的に間違った方法です) - PDO が唯一の選択肢です。まだ不完全ですが、多くの重要な機能を提供します。そのうちの 2 つは、PDO を mysqli と決定的に区別します。

    • mysqli とは異なり、PDO は値によってプレースホルダーをバインドできます。これにより、非常に厄介なコードの画面を何画面も使わずに、動的に構築されたクエリが実行可能になります。
    • mysqli とは異なり、PDO は常に単純な通常の配列でクエリ結果を返すことができますが、mysqli は mysqlnd インストールでのみそれを行うことができます。

そのため、平均的な PHP ユーザーで、ネイティブのプリペアド ステートメントを使用する際の頭痛の種を減らしたい場合は、PDO が唯一の選択肢です。
ただし、PDO も特効薬ではなく、苦労もあります。そこで、よくある落とし穴と複雑なケースの解決策をPDO タグ wiki
に書きました。

それにもかかわらず、拡張機能について話している人は、Mysqli と PDO に関する次の2 つの重要な事実を常に見逃しています。

  1. 準備済みステートメントは特効薬ではありません。準備済みステートメントを使用してバインドできない動的識別子があります。パラメータの数が不明な動的クエリがあり、クエリの構築が困難なタスクになっています。

  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 がこのような実際のケースを処理する例はほとんど見つかりません。
そして、それはあまりにも冗長で、おそらく安全ではありません.

繰り返しになりますが、懸念すべきは生のドライバーだけではなく、抽象化クラスであり、初心者向けマニュアルのばかげた例だけでなく、実際の問題を解決するのにも役立ちます。

于 2013-01-01T17:42:28.837 に答える
103

多くの理由がありますが、おそらく最も重要な理由は、それらの関数がプリペアド ステートメントをサポートしていないため、安全でないプログラミング プラクティスを助長していることです。プリペアド ステートメントは、SQL インジェクション攻撃の防止に役立ちます。

関数を使用するときmysql_*は、ユーザー指定のパラメーターを を通じて実行することを忘れないでくださいmysql_real_escape_string()。1 か所だけ忘れたり、入力の一部だけを逃したりすると、データベースが攻撃を受ける可能性があります。

PDOorで準備済みステートメントを使用mysqliすると、この種のプログラミング エラーが発生しにくくなります。

于 2012-10-12T13:23:00.523 に答える
80

(他の理由の中でも) 入力データがサニタイズされていることを確認するのがはるかに難しいためです。PDO や mysqli で行うように、パラメータ化されたクエリを使用すると、リスクを完全に回避できます。

例として、誰かが"enhzflep); drop table users"ユーザー名として使用できます。古い関数では、クエリごとに複数のステートメントを実行できるため、その厄介なバグのようなものはテーブル全体を削除できます。

mysqli の PDO を使用すると、ユーザー名は"enhzflep); drop table users".

bobby-tables.comを参照してください。

于 2012-10-12T13:24:04.980 に答える