PHPでmysql_ *関数を使用すべきではないのはなぜですか?

Oct 12 2012

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

自分のサイトで機能しているのに、なぜ他のものを使用する必要があるのですか?

それらが私のサイトで機能しない場合、なぜ私は次のようなエラーが発生するのですか?

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

回答

2118 Quentin Oct 12 2012 at 20:23

MySQL拡張機能:

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

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

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

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

1303 NullPoiиteя Jan 01 2013 at 18:52

PHPは、MySQLに接続するための3つの異なるAPIを提供します。これらはmysql(PHP 7で削除された)、、、mysqliおよびPDO拡張機能です。

mysql_*以前は非常に人気のあった機能でしたが、現在は使用を推奨していません。ドキュメントチームはデータベースのセキュリティ状況について話し合っており、一般的に使用されているext / mysql拡張機能から離れるようにユーザーを教育することもその一部です(php.internals:非推奨のext / mysqlを確認してください)。

そして、後のPHP開発者チームはE_DEPRECATED、ユーザーがMySQLに接続するときに、を介してmysql_connect()mysql_pconnect()またはに組み込まれている暗黙の接続機能を介して、エラーを生成することを決定しましたext/mysql

ext/mysql公式にPHP 5.5の時点で非推奨とされているPHP 7のように除去

レッドボックスが見えますか?

あなたはどのに行くときmysql_*の機能のマニュアルページ、あなたはそれはもう使用すべきではない説明、赤いボックスを参照してください。

なぜ


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

ext/mysqlMySQL3.23用に構築されており、それ以降、追加がほとんど行われていませんが、この古いバージョンとの互換性はほとんど維持されているため、コードの保守が少し難しくなっています。でサポートされていない不足している機能はext/mysql次のとおりです:(PHPマニュアルから)。

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

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

クエンティンの回答から引用した上記の点

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

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


非推奨の警告の抑制

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

error_reporting = E_ALL ^ E_DEPRECATED

これにより、他の非推奨の警告も非表示になることに注意してください。ただし、MySQL以外の場合もあります。(PHPマニュアルから

記事PDO対MySQLi:どちらを使用する必要がありますか?DejanMarjanovicによるあなたが選ぶのを手伝います。

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


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


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

A.「PDO– PHPデータオブジェクト–は、複数のデータベースにアクセスするための統一された方法を提供するデータベースアクセスレイヤーです。」


MySQLへの接続

mysql_*関数を使用するか、古い方法で言うことができます(PHP 5.5以降では非推奨)

$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);

ありPDO:あなたがする必要があるのは新しいPDOオブジェクトを作成することだけです。コンストラクターは、データベースソースを指定するためのパラメーターを受け入れます。コンストラクターは、PDOほとんどのDSN場合username、(データソース名)とオプションで、の4つのパラメーターを取りますpassword

ここで、あなたはDSN;を除くすべてに精通していると思います。これはで新しいですPDO。ADSNは基本的に、PDO使用するドライバーと接続の詳細を示す一連のオプションです。詳細については、PDO MySQLDSNを確認してください。

$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はすべて、try-catchブロックで囲む必要があります。PDOエラーモード属性を設定することにより、3つのエラーモードのいずれかに強制することができます。3つのエラー処理モードを以下に示します。

  • PDO::ERRMODE_SILENT。エラーコードを設定するだけで、mysql_*各結果を確認して$db->errorInfo();からエラーの詳細を取得する必要がある場合とほとんど同じように機能します。
  • PDO::ERRMODE_WARNING上げるE_WARNING。(実行時の警告(致命的でないエラー)。スクリプトの実行は停止されません。)
  • PDO::ERRMODE_EXCEPTION:例外をスローします。これは、PDOによって発生したエラーを表します。PDOException自分のコードからをスローしないでください。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エラーログを回して読み取るだけで、本番環境で危険なエラーメッセージを非表示にできます。

さて、上記のすべてのものを読んだ後、あなたはおそらく考えている:一体何であるかという、私は単純な傾い開始したい時にSELECTINSERTUPDATE、または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_ASSOCfetch()および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();

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

私たちが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ステートメントです。

プリペアドステートメントを使用する一般的なワークフローは次のとおりです(ウィキペディアの3つの3ポイントから引用)。

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

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

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

  3. 実行:後で、アプリケーションがパラメーターの値を提供(またはバインド)し、DBMSがステートメントを実行します(場合によっては結果を返します)。アプリケーションは、さまざまな値を使用して、ステートメントを何度でも実行できます。この例では、最初のパラメーターと1.002番目のパラメーターに「Bread」を指定できます。

SQLにプレースホルダーを含めることにより、プリペアドステートメントを使用できます。基本的に、プレースホルダーのないもの(上記の変数でこれを試さないでください)、名前のないプレースホルダーのあるもの、名前のあるプレースホルダーのあるものの3つがあります。

Q.では、プレースホルダーとは何ですか。また、どのように使用しますか?
A.名前付きプレースホルダー。疑問符の代わりに、コロンを前に付けたわかりやすい名前を使用してください。名前プレースホルダーの値の位置/順序は関係ありません。

 $stmt->bindParam(':bla', $bla);

bindParam(parameter,variable,data_type,length,driver_options)

また、execute配列を使用してバインドすることもできます。

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

OOP友人にとってのもう1つの優れた機能は、プロパティが名前付きフィールドと一致することを前提として、名前付きプレースホルダーがオブジェクトをデータベースに直接挿入できることです。例えば:

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番目の例では、最初の配列要素が1?番目に、2番目が2番目に移動し?ます。

名前のないプレースホルダーでは、PDOStatement::execute()メソッドに渡す配列内の要素の適切な順序に注意する必要があります。


SELECTINSERTUPDATEDELETEクエリを準備

  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、および/またはMySQLi完全に安全ではありません。答えを確認してくださいPDOプリペアドステートメントはSQLインジェクションを防ぐのに十分ですか?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 /*"));
305 Madara'sGhost Oct 12 2012 at 20:28

まず、私たちが皆に与える標準的なコメントから始めましょう:

mysql_*新しいコードで関数を使用しないでください。それらはもはや維持されておらず、公式に非推奨になっています。参照してください赤いボックスを?代わりにプリペアドステートメントについて学び、 PDOまたはMySQLiを使用してください-この記事はどちらを決定するのに役立ちます。PDOを選択した場合ここに優れたチュートリアルがあります。

これを一文ずつ見ていき、説明しましょう。

  • それらはもはや維持されておらず、公式に非推奨になっています

    これは、PHPコミュニティがこれらの非常に古い関数のサポートを徐々に廃止していることを意味します。これらは、PHPの将来の(最近の)バージョンには存在しない可能性があります。これらの関数を継続して使用すると、(それほどではないが)遠い将来にコードが破損する可能性があります。

    新着!--ext / mysqlはPHP5.5で正式に非推奨になりました!

    新しい!ext / mysqlはPHP7で削除されました

  • 代わりに、プリペアドステートメントについて学ぶ必要があります

    mysql_*拡張機能は、プリペアドステートメントをサポートしていません。これは(とりわけ)SQLインジェクションに対する非常に効果的な対策です。これにより、MySQLに依存するアプリケーションの非常に深刻な脆弱性が修正され、攻撃者がスクリプトにアクセスして、データベースに対して可能なクエリを実行できるようになりました。

    詳細については、「PHPでSQLインジェクションを防ぐにどうすればよいですか?」を参照してください

  • レッドボックスが見えますか?

    あなたがいずれかに行くときmysqlの機能のマニュアルページ、あなたはそれはもう使用すべきではない説明、赤いボックスを参照してください。

  • PDOまたはMySQLiのいずれかを使用する

    より優れた、より堅牢で十分に構築された代替手段、PDO(データベースの相互作用に対する完全なOOPアプローチを提供するPHPデータベースオブジェクト)、およびMySQL固有の改善であるMySQLiがあります。

221 mario Dec 25 2013 at 06:30

使いやすさ

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

最新のデータベースAPIは使いやすいです。

コードを単純化できるのは、ほとんどがバインドされたパラメーターです。また、優れたチュートリアル(上記のとおり)を使用すれば、PDOへの移行はそれほど難しくありません。

ただし、より大きなコードベースを一度に書き換えるには時間がかかります。この中間代替案の存在理由:

等価PDO_ *の代わりに機能mysql_ *

< 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]";
    }
    

Etvoilà。
コードは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_呼び出しをpdo_queryバインドされたパラメーターを使用して変換したら、冗長なpdo_real_escape_string呼び出しをすべて削除します。

特に、日付の付いたチュートリアルで宣伝されているsanitizeorcleanまたは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。ただし、これは文字列ごとではなく、中央修正するの最適です。

    • ユーザーランド反転アプローチの1つを使用します。次にstripslashes()sanitize関数内のを削除します。

    magic_quotesに関する歴史的なメモ。その機能は当然非推奨です。ただし、セキュリティ機能の失敗として誤って描写されることがよくあります。しかし、magic_quotesは、テニスボールが栄養源として失敗したのと同じくらい失敗したセキュリティ機能です。それは単に彼らの目的ではありませんでした。

    PHP2 / FIの元の実装では、「引用符は自動的にエスケープされ、フォームデータをmsqlクエリに直接渡すのが簡単になります」と明示的に導入されました。特に、ASCIIのみをサポートしているため、誤ってmSQLで使用しても安全でした。
    次に、PHP3 / ZendはMySQLにmagic_quotesを再導入し、それを誤って文書化しました。しかし、もともとは単なる便利な機能であり、セキュリティを目的としたものではありませんでした。

プリペアドステートメントの違い

文字列変数をSQLクエリにスクランブルすると、従うのがより複雑になるだけではありません。また、MySQLがコードとデータを再び分離することは余分な努力です。

SQLインジェクションは、単にデータがコードコンテキストに流れ込むときです。データベースサーバーは、後でPHPがクエリ句の間に変数を最初に接着した場所を見つけることができません。

バインドされたパラメーターを使用して、PHPコード内のSQLコードとSQ​​Lコンテキスト値を分離します。ただし、舞台裏で再びシャッフルされることはありません(PDO :: EMULATE_PREPARESを除く)。データベースは、不変のSQLコマンドと1:1の変数値を受け取ります。

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

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

ハイブリッドPDOの使用

これらのpdo_*ラッパー関数は、コーディングに適したストップギャップAPIを作成します。(これMYSQLIは、特異な関数のシグネチャシフトがなかった場合に発生した可能性がほとんどあります)。また、ほとんどの場合、実際のP​​DOを公開します。
書き換えは、新しい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を使用しなくなったように、最近では生のデータベースインターフェイスをベビーシッターする必要はありません。

150 Alnitak Oct 12 2012 at 20:22

mysql_機能:

  1. 古くなっています-それらはもう維持されていません
  2. 別のデータベースバックエンドに簡単に移動できないようにする
  3. プリペアドステートメントをサポートしないため、
  4. プログラマーに連結を使用してクエリを作成するように促し、SQLインジェクションの脆弱性を引き起こします
109 YourCommonSense Jan 02 2013 at 00:42

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

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

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

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

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

この後者の問題は問題です。
しかし、私の意見では、提案された解決策も良くありません。これらすべてのPHPユーザーがSQLクエリを一度に適切に処理する方法を学ぶ
ことは、私にはあまりにも理想的な夢のように思えます。ほとんどの場合、mysql_ *をmysqli_ *に機械的に変更し、アプローチは同じままにします。特にmysqliは、プリペアドステートメントの使用法を信じられないほど苦痛で面倒なものにしているためです。
言うまでもなく、ネイティブのプリペアドステートメントSQLインジェクションから保護するのに十分ではなく、mysqliもPDOもソリューションを提供しません。

ですから、この正直な延長と戦うのではなく、間違った慣行と戦い、正しい方法で人々を教育したいと思います。

また、次のようないくつかの誤ったまたは重要でない理由があります

  • ストアドプロシージャをサポートしていません(長年使用していmysql_query("CALL my_proc");ました)
  • トランザクションをサポートしていません(上記と同じ)
  • 複数のステートメントをサポートしていません(誰がそれらを必要としますか?)
  • 活発な開発中ではありません(それで、それは実際的な方法であなたに影響与えますか?)
  • OOインターフェースがない(作成するのに数時間かかる)
  • プリペアドステートメントまたはパラメータ化されたクエリをサポートしていません

最後は興味深い点です。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呼び出しを使用している場合(これは本質的に間違った方法です)-この拡張機能はAPIだけでなく、セミDALのふりをするため、PDOが唯一の選択肢です。まだ不完全ですが、多くの重要な機能を提供します。そのうちの2つにより、PDOはmysqliと決定的に区別されます。

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

したがって、平均的なPHPユーザーであり、ネイティブのプリペアドステートメントを使用する際の頭痛の種を大幅に減らしたい場合は、PDO(これも)が唯一の選択肢です。
ただし、PDOも特効薬ではなく、困難を伴います。
そこで、PDOタグウィキで一般的な落とし穴と複雑なケースすべての解決策を書きました

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

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

  2. mysqli_ *関数もPDO関数もアプリケーションコードに表示されるべきではありませんでした。
    そこには、あるべき抽象化レイヤアプリケーションコードのDRYとクリーンを作る、結合、ループ、エラー処理など内部のすべての汚い仕事を行いますそれらとアプリケーション・コードの間、。特に動的クエリ構築のような複雑なケースの場合。

したがって、PDOまたはmysqliに切り替えるだけでは十分ではありません。コードで生のAPI関数を呼び出す代わりに、ORM、クエリビルダー、またはデータベース抽象化クラスを使用する必要があります。
逆に、アプリケーションコードとmysql APIの間に抽象化レイヤーがある場合は、実際にどのエンジンを使用するかは問題ではありません。非推奨になるまでmysqlextを使用してから、すべてのアプリケーションコードをそのままにして、抽象化クラスを別のエンジンに簡単に書き換えることができます

このような抽象化クラスがどのようにあるべきかを示すために、私の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);

すべてのフィールド名が6〜10回繰り返される場合、通常のPDO挿入と比較してください。これらすべての名前付きプレースホルダー、バインディング、およびクエリ定義で。

もう一つの例:

$data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);

このような実際的なケースを処理するためのPDOの例はほとんど見つかりません。
そして、それは言葉が多すぎて、おそらく安全ではないでしょう。

したがって、もう一度、生のドライバーだけでなく、抽象化クラスも考慮する必要があります。これは、初心者向けマニュアルのばかげた例だけでなく、実際の問題を解決するのにも役立ちます。

98 Trott Oct 12 2012 at 20:23

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

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

PDOまたはでプリペアドステートメントを使用mysqliすると、この種のプログラミングエラーが発生しにくくなります。

77 enhzflep Oct 12 2012 at 20:24

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

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

mysqliのPDOを使用する場合、ユーザー名は最終的に"enhzflep); drop table users"。になります。

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

66 Fluffeh Sep 18 2013 at 19:28

この回答は、不十分に記述されたPHPユーザー検証コードをバイパスすることがいかに簡単であるか、これらの攻撃がどのように機能するか(そして何を使用するか)、古いMySQL関数を安全なプリペアドステートメントに置き換える方法を示すために書かれています-そして基本的に、StackOverflowユーザーがなぜ(おそらく多くの担当者がいる)コードを改善するために質問をしている新しいユーザーに吠えています。

まず、このテストmysqlデータベースを自由に作成してください(私は私の準備と呼んでいます):

mysql> create table users(
    -> id int(2) primary key auto_increment,
    -> userid tinytext,
    -> pass tinytext);
Query OK, 0 rows affected (0.05 sec)

mysql> insert into users values(null, 'Fluffeh', 'mypass');
Query OK, 1 row affected (0.04 sec)

mysql> create user 'prepared'@'localhost' identified by 'example';
Query OK, 0 rows affected (0.01 sec)

mysql> grant all privileges on prep.* to 'prepared'@'localhost' with grant option;
Query OK, 0 rows affected (0.00 sec)

これで、PHPコードに移行できます。

次のスクリプトがWebサイトの管理者の検証プロセスであると仮定します(簡略化されていますが、コピーしてテストに使用すると機能します)。

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }

    $database='prep';
    $link=mysql_connect('localhost', 'prepared', 'example');
    mysql_select_db($database) or die( "Unable to select database");

    $sql="select id, userid, pass from users where userid='$user' and pass='$pass'";
    //echo $sql."<br><br>";
    $result=mysql_query($sql);
    $isAdmin=false;
    while ($row = mysql_fetch_assoc($result)) {
        echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
        $isAdmin=true;
        // We have correctly matched the Username and Password
        // Lets give this person full access
    }
    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }
    mysql_close($link);

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

一見、十分に正当なようです。

ユーザーはログインとパスワードを入力する必要がありますよね?

素晴らしい、次のように入力しないでください:

user: bob
pass: somePass

そしてそれを提出してください。

出力は次のとおりです。

You could not be verified. Please try again...

素晴らしい!期待どおりに機能しているので、実際のユーザー名とパスワードを試してみましょう。

user: Fluffeh
pass: mypass

すごい!すべてのラウンドでハイファイブ、コードは管理者を正しく検証しました。パーフェクトだ!

まあ、そうではありません。ユーザーが賢い小さな人だとしましょう。その人が私だとしましょう。

次のように入力します。

user: bob
pass: n' or 1=1 or 'm=m

そして、出力は次のとおりです。

The check passed. We have a verified admin!

おめでとうございます。あなたは私があなたの超保護された管理者専用セクションに入ることを許可しました。私は偽のユーザー名と偽のパスワードを入力しました。真剣に、あなたが私を信じていないなら、私が提供したコードでデータベースを作成し、このPHPコードを実行してください-一見すると、ユーザー名とパスワードをかなりうまく検証しているように見えます。

だから、答えとして、それがあなたが怒鳴られている理由です。

それでは、何がうまくいかなかったのか、そしてなぜ私があなたのsuper-admin-only-bat-caveに入ったのかを見てみましょう。私は推測をして、あなたが入力に注意を払っていないと仮定し、単にそれらをデータベースに直接渡しました。実際に実行しているクエリを変更する方法で入力を作成しました。それで、それは何であるはずでしたか、そしてそれは最終的に何でしたか?

select id, userid, pass from users where userid='$user' and pass='$pass'

これがクエリですが、変数を使用した実際の入力に置き換えると、次のようになります。

select id, userid, pass from users where userid='bob' and pass='n' or 1=1 or 'm=m'

最初にパスワードを一重引用符で囲み、次に完全に新しい比較を導入するように「パスワード」を作成した方法をご覧ください。次に、安全のために、別の「文字列」を追加して、元のコードで期待どおりに一重引用符が閉じられるようにしました。

しかし、これは今あなたに怒鳴っている人々についてではなく、あなたのコードをより安全にする方法をあなたに示すことについてです。

さて、何がうまくいかなかったので、どうすれば修正できますか?

これは古典的なSQLインジェクション攻撃です。そのことに関して最も単純なものの1つ。攻撃ベクトルの規模では、これは戦車を攻撃して勝利する幼児です。

では、どのようにしてあなたの神聖な管理セクションを保護し、それを素晴らしく安全にするのでしょうか?最初に行うことは、これらの本当に古くて非推奨のmysql_*関数の使用をやめることです。あなたはオンラインで見つけたチュートリアルに従っていて、それは機能しますが、それは古く、時代遅れで、数分のうちに、汗をかくことなくそれを乗り越えました。

これで、mysqli_またはPDOを使用するためのより良いオプションがあります。私は個人的にPDOの大ファンなので、この回答の残りの部分ではPDOを使用します。賛否両論がありますが、個人的には賛成派が反対派をはるかに上回っています。MySQLやOracleを使用している場合でも、血まみれの何かを使用している場合でも、接続文字列を変更するだけで、複数のデータベースエンジン間で移植可能であり、使用したいすべての優れた機能を備えており、すっきりしています。私はきれいが好きです。

それでは、今度はPDOオブジェクトを使用して記述されたそのコードをもう一度見てみましょう。

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }
    $isAdmin=false;

    $database='prep';
    $pdo=new PDO ('mysql:host=localhost;dbname=prep', 'prepared', 'example');
    $sql="select id, userid, pass from users where userid=:user and pass=:password";
    $myPDO = $pdo->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
    if($myPDO->execute(array(':user' => $user, ':password' => $pass)))
    {
        while($row=$myPDO->fetch(PDO::FETCH_ASSOC))
        {
            echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
            $isAdmin=true;
            // We have correctly matched the Username and Password
            // Lets give this person full access
        }
    }

    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

主な違いは、これ以上mysql_*機能がないことです。それはすべてPDOオブジェクトを介して行われ、次に、プリペアドステートメントを使用しています。さて、あなたが尋ねる準備された声明は何ですか?これは、クエリを実行する前に、実行するクエリをデータベースに通知する方法です。この場合、データベースに次のように伝えます。「こんにちは。useridが変数で、passも変数であるテーブルusersからid、userid、passを必要とするselectステートメントを実行します。」

次に、executeステートメントで、データベースに、現在期待されているすべての変数を含む配列を渡します。

結果は素晴らしいです。以前のユーザー名とパスワードの組み合わせをもう一度試してみましょう。

user: bob
pass: somePass

ユーザーは確認されませんでした。驚くばかり。

どうですか:

user: Fluffeh
pass: mypass

ああ、私は少し興奮しました、それはうまくいきました:チェックは合格しました。確認済みの管理者がいます!

それでは、巧妙なチャップが入力するデータを試して、小さな検証システムを乗り越えようとします。

user: bob
pass: n' or 1=1 or 'm=m

今回は、次のようになります。

You could not be verified. Please try again...

これが、質問を投稿するときに怒鳴られる理由です。これは、試行しなくてもコードがバイパスされる可能性があることを人々が理解できるためです。この質問と回答を使用して、コードを改善し、コードをより安全にし、最新の関数を使用してください。

最後に、これはこれが完璧なコードであると言っているのではありません。それを改善するためにできることは他にもたくさんあります。たとえば、ハッシュ化されたパスワードを使用して、データベースに意味のある情報を保存するときに、プレーンテキストで保存せず、複数のレベルの検証を行うようにします。古いインジェクションが発生しやすいコードをこれに変更するだけで、優れたコードを作成することができます。ここまで進んでまだ読んでいるという事実は、このタイプを実装するだけではないという希望を私に与えてくれます。あなたのウェブサイトやアプリケーションを書くときのコードの、しかしあなたが外に出て、私が今述べた他のものを研究するかもしれないということ-そしてもっと。かろうじて機能する最も基本的なコードではなく、できる限り最高のコードを書いてください。

34 Alexander Sep 02 2015 at 14:20

MySQL拡張機能は、3つの中で最も古いものであり、開発者がMySQLと通信するために使用した元の方法でした。この拡張機能は、PHPとMySQLの両方の新しいリリースで改善が行われたため、他の2つの選択肢を優先して非推奨になりました。

  • MySQLiは、MySQLデータベースを操作するための「改善された」拡張機能です。これは、MySQLサーバーの新しいバージョンで利用可能な機能を利用し、関数指向とオブジェクト指向の両方のインターフェースを開発者に公開し、他のいくつかの気の利いたことを行います。

  • PDOは、以前は主要なデータベースアクセス拡張機能(MySQL、PostgreSQL、SQLite、MSSQLなど)に分散されていた機能のほとんどを統合するAPIを提供します。インターフェイスは、プログラマーがデータベース接続、クエリ、および結果セット、および低レベルのドライバーは、データベースサーバーとの通信およびリソース処理を実行します。PDOには多くの議論と作業が行われており、最新のプロフェッショナルなコードでデータベースを操作する適切な方法と見なされています。

22 AniMenon Sep 07 2016 at 22:06

上記の答えは本当に長いと思うので、要約すると:

mysqli拡張機能には多くの利点があります。mysql拡張機能に対する主な機能強化は次のとおりです。

  • オブジェクト指向インターフェース
  • プリペアドステートメントのサポート
  • 複数のステートメントのサポート
  • トランザクションのサポート
  • 強化されたデバッグ機能
  • 組み込みサーバーのサポート

出典:MySQLiの概要


上記の回答で説明されているように、mysqlの代替手段はmysqliとPDO(PHPデータオブジェクト)です。

  • APIはサーバー側のプリペアドステートメントをサポートします:MYSQLiとPDOでサポートされます
  • APIはクライアント側のプリペアドステートメントをサポートします:PDOでのみサポートされます
  • APIはストアドプロシージャをサポートします:MySQLiとPDOの両方
  • APIは複数のステートメントとすべてのMySQL4.1以降の機能をサポートします-MySQLiおよびほとんどの場合PDOでもサポートされます

MySQLiとPDOはどちらもPHP5.0で導入されましたが、MySQLはPHP3.0より前に導入されました。注意すべき点は、MySQLはPHP5.xに含まれていますが、それ以降のバージョンでは非推奨になっていることです。

7 PavelTzonkov Jun 09 2017 at 13:24

mysql_*mysqliまたはPDOを使用してほぼすべての関数を定義することが可能です。古いPHPアプリケーションの上にそれらを含めるだけで、PHP7で動作します。ここで私の解決策。

<?php

define('MYSQL_LINK', 'dbl');
$GLOBALS[MYSQL_LINK] = null;

function mysql_link($link=null) {
    return ($link === null) ? $GLOBALS[MYSQL_LINK] : $link;
}

function mysql_connect($host, $user, $pass) {
    $GLOBALS[MYSQL_LINK] = mysqli_connect($host, $user, $pass);
    return $GLOBALS[MYSQL_LINK];
}

function mysql_pconnect($host, $user, $pass) {
    return mysql_connect($host, $user, $pass);
}

function mysql_select_db($db, $link=null) {
    $link = mysql_link($link);
    return mysqli_select_db($link, $db);
}

function mysql_close($link=null) {
    $link = mysql_link($link);
    return mysqli_close($link);
}

function mysql_error($link=null) {
    $link = mysql_link($link);
    return mysqli_error($link);
}

function mysql_errno($link=null) {
    $link = mysql_link($link);
    return mysqli_errno($link);
}

function mysql_ping($link=null) {
    $link = mysql_link($link);
    return mysqli_ping($link);
}

function mysql_stat($link=null) {
    $link = mysql_link($link);
    return mysqli_stat($link);
}

function mysql_affected_rows($link=null) {
    $link = mysql_link($link);
    return mysqli_affected_rows($link);
}

function mysql_client_encoding($link=null) {
    $link = mysql_link($link);
    return mysqli_character_set_name($link);
}

function mysql_thread_id($link=null) {
    $link = mysql_link($link);
    return mysqli_thread_id($link);
}

function mysql_escape_string($string) {
    return mysql_real_escape_string($string);
}

function mysql_real_escape_string($string, $link=null) {
    $link = mysql_link($link);
    return mysqli_real_escape_string($link, $string);
}

function mysql_query($sql, $link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, $sql);
}

function mysql_unbuffered_query($sql, $link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, $sql, MYSQLI_USE_RESULT);
}

function mysql_set_charset($charset, $link=null){
    $link = mysql_link($link);
    return mysqli_set_charset($link, $charset);
}

function mysql_get_host_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_host_info($link);
}

function mysql_get_proto_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_proto_info($link);
}
function mysql_get_server_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_server_info($link);
}

function mysql_info($link=null) {
    $link = mysql_link($link);
    return mysqli_info($link);
}

function mysql_get_client_info() {
    $link = mysql_link();
    return mysqli_get_client_info($link);
}

function mysql_create_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "CREATE DATABASE `$db`");
}

function mysql_drop_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "DROP DATABASE `$db`");
}

function mysql_list_dbs($link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, "SHOW DATABASES");
}

function mysql_list_fields($db, $table, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    $table = str_replace('`', '', mysqli_real_escape_string($link, $table));
    return mysqli_query($link, "SHOW COLUMNS FROM `$db`.`$table`");
}

function mysql_list_tables($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "SHOW TABLES FROM `$db`");
}

function mysql_db_query($db, $sql, $link=null) {
    $link = mysql_link($link);
    mysqli_select_db($link, $db);
    return mysqli_query($link, $sql);
}

function mysql_fetch_row($qlink) {
    return mysqli_fetch_row($qlink);
}

function mysql_fetch_assoc($qlink) {
    return mysqli_fetch_assoc($qlink);
}

function mysql_fetch_array($qlink, $result=MYSQLI_BOTH) {
    return mysqli_fetch_array($qlink, $result);
}

function mysql_fetch_lengths($qlink) {
    return mysqli_fetch_lengths($qlink);
}

function mysql_insert_id($qlink) {
    return mysqli_insert_id($qlink);
}

function mysql_num_rows($qlink) {
    return mysqli_num_rows($qlink);
}

function mysql_num_fields($qlink) {
    return mysqli_num_fields($qlink);
}

function mysql_data_seek($qlink, $row) {
    return mysqli_data_seek($qlink, $row);
}

function mysql_field_seek($qlink, $offset) {
    return mysqli_field_seek($qlink, $offset);
}

function mysql_fetch_object($qlink, $class="stdClass", array $params=null) {
    return ($params === null)
        ? mysqli_fetch_object($qlink, $class)
        : mysqli_fetch_object($qlink, $class, $params);
}

function mysql_db_name($qlink, $row, $field='Database') {
    mysqli_data_seek($qlink, $row);
    $db = mysqli_fetch_assoc($qlink);
    return $db[$field];
}

function mysql_fetch_field($qlink, $offset=null) {
    if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    return mysqli_fetch_field($qlink);
}

function mysql_result($qlink, $offset, $field=0) {
    if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    $row = mysqli_fetch_array($qlink);
    return (!is_array($row) || !isset($row[$field]))
        ? false
        : $row[$field];
}

function mysql_field_len($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->length : false;
}

function mysql_field_name($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field))
        return false;
    return empty($field->orgname) ? $field->name : $field->orgname;
}

function mysql_field_table($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field))
        return false;
    return empty($field->orgtable) ? $field->table : $field->orgtable;
}

function mysql_field_type($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->type : false;
}

function mysql_free_result($qlink) {
    try {
        mysqli_free_result($qlink);
    } catch (Exception $e) {
        return false;
    }
    return true;
}