LIMIT句でbindValueメソッドを適用する方法は?


117

これが私のコードのスナップショットです:

$fetchPictures = $PDO->prepare("SELECT * 
    FROM pictures 
    WHERE album = :albumId 
    ORDER BY id ASC 
    LIMIT :skip, :max");

$fetchPictures->bindValue(':albumId', $_GET['albumid'], PDO::PARAM_INT);

if(isset($_GET['skip'])) {
    $fetchPictures->bindValue(':skip', trim($_GET['skip']), PDO::PARAM_INT);    
} else {
    $fetchPictures->bindValue(':skip', 0, PDO::PARAM_INT);  
}

$fetchPictures->bindValue(':max', $max, PDO::PARAM_INT);
$fetchPictures->execute() or die(print_r($fetchPictures->errorInfo()));
$pictures = $fetchPictures->fetchAll(PDO::FETCH_ASSOC);

私は得る

SQL構文にエラーがあります。MySQLサーバーのバージョンに対応するマニュアルで、1行目の '' 15 '、15'付近で使用する正しい構文を確認してください

PDOがSQLコードのLIMIT部分の変数に単一引用符を追加しているようです。私はそれを調べたところ、関連していると思われるこのバグを見つけました:http : //bugs.php.net/bug.php?id=44639

それは私が見ているものですか?このバグは2008年4月からオープンしています。その間、私たちは何をすべきか?

SQL文を送信する前に、ページ分割を作成し、データがクリーンでSQLインジェクションセーフであることを確認する必要があります。



回答:


165

以前にこの問題があったことを覚えています。バインド関数に渡す前に、値を整数にキャストします。これで解決すると思います。

$fetchPictures->bindValue(':skip', (int) trim($_GET['skip']), PDO::PARAM_INT);

37
ありがとう!しかし、PHP 5.3では、上記のコードは「致命的なエラー:参照によってパラメーター2を渡すことができません」というエラーをスローしました。そこにintをキャストするのは好きではありません。の代わりに(int) trim($_GET['skip'])、試してください intval(trim($_GET['skip']))
マーティンは

5
これがそうである理由を誰かが説明を提供した場合...デザイン/セキュリティ(またはその他)の観点からはクールです。
ロス

6
これは、エミュレートされた準備済みステートメントが有効になっている場合にのみ機能します。無効にすると失敗します(無効にする必要があります!)
Madara's Ghost

4
@Rossこれには具体的に答えることはできませんが、LIMITとOFFSETは、このすべてのPHP / MYSQL / PDOの狂気が開発回路にヒットした後で接着された機能であることを指摘できます...実際、監督したのはラードルフ自身だった数年前のLIMIT実装。いいえ、それは質問の答えにはなりませんが、それはアフターマーケットアドオンであることを示しており、彼らがときどきうまく機能できることを知っています
。–

2
@Ross PDOは、値ではなく変数へのバインドを許可しません。bindParam( ':something'、2)を試してみると、PDOは数値が持つことのできない変数へのポインターを使用するため、エラーが発生します($ iが2の場合、$ iへのポインターを持つことはできますが、 2番)。
クリスティアン

44

最も簡単な解決策は、エミュレーションモードをオフにすることです。次の行を追加するだけでそれを行うことができます

$PDO->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );

また、このモードは、PDO接続の作成時にコンストラクターパラメーターとして設定できます。ドライバーがsetAttribute()関数をサポートしていないと報告している人もいるので、より良い解決策になるかもしれません。

バインディングに関する問題を解決するだけでなく、値をに直接送信execute()できるため、コードが大幅に短くなります。エミュレーションモードが既に設定されていると仮定すると、全体の作業には6行ものコードが必要になります。

$skip = isset($_GET['skip']) ? (int)trim($_GET['skip']) : 0;
$sql  = "SELECT * FROM pictures WHERE album = ? ORDER BY id LIMIT ?, ?";
$stmt  = $PDO->prepare($sql);
$stmt->execute([$_GET['albumid'], $skip, $max]);
$pictures = $stmt->fetchAll(PDO::FETCH_ASSOC);

SQLSTATE[IM001]: Driver does not support this function: This driver doesn't support setting attributes...なぜそれがために非常にシンプルになることはありません :)私は確かにこれはほとんどの人が、私の場合、私は受け入れ答えのようなものを使用するようになってしまったでしょうよながら。将来の読者の目を引くだけです!
マシュージョンソン

@MatthewJohnsonそれはどんなドライバーですか?
あなたの常識

よくわかりませんが、マニュアルではと書いてありPDO::ATTR_EMULATE_PREPARES Enables or disables emulation of prepared statements. Some drivers do not support native prepared statements or have limited support for themます。それは私にとって新しいものですが、それでも私はPDOを使い始めたばかりです。通常はmysqliを使用しますが、視野を広げようと考えました。
マシュージョンソン

@MatthewJohnson mysqlでPDOを使用している場合、ドライバーはこの機能を正しくサポートします。だから、あなたはいくつかの間違いが原因でこのメッセージを受け取っています
あなたの常識

1
ドライバーサポートの問題のメッセージが表示された場合setAttributeは、pdoオブジェクトではなく、statement($ stm、$ stmt)を呼び出すかどうかを再度確認してください。
Jehong Ahn

17

バグレポートを見ると、次のことが機能する可能性があります。

$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);

$fetchPictures->bindValue(':skip', (int)trim($_GET['skip']), PDO::PARAM_INT);  

しかし、あなたの入ってくるデータは正しいですか?エラーメッセージには、は、数字の後に引用符 1つ(数字全体が引用符で囲まれているのではなく)。これは、受信データのエラーである可能性もあります。あなたはprint_r($_GET);見つけるために行うことができますか?


1
'' 15 '、15'。最初の数値は引用符で完全に囲まれています。2番目の数値には引用符がありません。はい、データは良好です。
Nathan H

8

これは要約です。
LIMIT / OFFSET値をパラメーター化する4つのオプションがあります。

  1. 無効にPDO::ATTR_EMULATE_PREPARES述べたように、上記

    渡された値が->execute([...])常に文字列として表示されるのを防ぎます。

  2. 手動に切り替え ->bindValue(..., ..., PDO::PARAM_INT)パラメーター入力にます。

    ただし、->実行リスト[]ほど便利ではありません。

  3. ここで例外を作成し、SQLクエリを準備するときに単純な整数を補間するだけです。

     $limit = intval($limit);
     $s = $pdo->prepare("SELECT * FROM tbl LIMIT {$limit}");

    キャスティングは重要です。より一般的には->prepare(sprintf("SELECT ... LIMIT %d", $num))、そのような目的で使用されることがわかります。

  4. MySQLを使用していないが、たとえばSQLiteやPostgresを使用している場合。バインドされたパラメーターをSQLで直接キャストすることもできます。

     SELECT * FROM tbl LIMIT (1 * :limit)

    ここでも、MySQL / MariaDBはLIMIT句の式をサポートしていません。未だに。


1
私はsprintf()を%dで3使用したでしょうが、変数よりも少し安定していると思います。
hakre 2015年

ええ、varfunc cast + interpolationは最も実用的な例ではありません。私は{$_GET->int["limit"]}そのような場合にしばしば怠惰を使います。
マリオ2015年

7

ために LIMIT :init, :end

あなたはそのようにバインドする必要があります。あなたが$req->execute(Array());それがPDO::PARAM_STR配列のすべての変数にキャストするのでそれが機能しないのでLIMITあなたが絶対に整数を必要とするような何かを持っていた場合。必要に応じてbindValueまたはBindParam。

$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);

2

これがなぜ起こっているのか誰も説明していないので、私は答えを追加しています。これが動作する理由は、を使用しているためですtrim()。のPHPマニュアルを見るtrimと、戻り値の型はstringです。次に、これをとして渡そうとしていPDO::PARAM_INTます。これを回避するいくつかの方法は次のとおりです。

  1. 使用する filter_var($integer, FILTER_VALIDATE_NUMBER_INT)整数を渡すことを確認するためにします。
  2. 他の人が言ったように、 intval()
  3. キャスティング (int)
  4. それが整数かどうかをチェックする is_int()

他にもたくさんの方法がありますが、これが根本的な原因です。


3
これは、変数が常に整数であった場合でも発生します。
felwithe 2017

1

bindValueのオフセットとPDO :: PARAM_INTを使用した制限とそれは動作します


-1

// BEFORE(現在のエラー)$ query = ".... LIMIT:p1、30;"; ... $ stmt-> bindParam( ':p1'、$ limiteInferior);

// AFTER(エラー修正済み)$ query = ".... LIMIT:p1、30;"; ... $ limiteInferior =(int)$ limiteInferior; $ stmt-> bindParam( ':p1'、$ limiteInferior、PDO :: PARAM_INT);


-1

PDO::ATTR_EMULATE_PREPARES 私にくれた

ドライバーはこの機能をサポートしていません:このドライバーは属性の設定エラーをサポートしていません。

私の回避策は$limit、次の例のように、変数を文字列として設定し、それをprepareステートメントで結合することでした。

$limit = ' LIMIT ' . $from . ', ' . $max_results;
$stmt = $pdo->prepare( 'SELECT * FROM users WHERE company_id = :cid ORDER BY name ASC' . $limit . ';' );
try {
    $stmt->execute( array( ':cid' => $company_id ) );
    ...
}
catch ( Exception $e ) {
    ...
}

-1

PHPの異なるバージョンとPDOの奇妙さの間には多くのことが起こっています。ここで3つまたは4つの方法を試しましたが、LIMITを機能させることができませんでした。
私の提案は、intval()フィルターを使用した文字列フォーマット/連結を使用することです:

$sql = 'SELECT * FROM `table` LIMIT ' . intval($limitstart) . ' , ' . intval($num).';';

特に$ _GETなどから制限を取得している場合は、SQLインジェクションを防ぐためにintval()を使用することが非常に重要です。 その場合、これがLIMITを機能させる最も簡単な方法です。

「PDOでのLIMITの問題」についての話はたくさんありますが、ここでの私の考えは、PDOパラメータは常に整数であり、クイックフィルタが機能するため、LIMITに使用されることは決してないということです。それでも、SQLインジェクションフィルタリングを自分で行うのではなく、「PDOで処理する」という哲学が常にされているため、少し誤解を招く可能性があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.