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

이전에이 문제가 있었던 것을 기억합니다. bind 함수에 전달하기 전에 값을 정수로 캐스트하십시오. 나는 이것이 그것을 해결한다고 생각합니다.

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

37
감사! 그러나 PHP 5.3에서 위의 코드는 "치명적인 오류 : 참조로 매개 변수 2를 전달할 수 없습니다"라는 오류를 던졌습니다. 거기에 int를 캐스팅하는 것을 좋아하지 않습니다. 대신 (int) trim($_GET['skip'])시도해보십시오 intval(trim($_GET['skip'])).
Will Martin

5
누군가 디자인 / 보안 (또는 기타) 관점에서 이것이 왜 그런지 설명을 제공한다면 멋질 것입니다.
Ross

6
이것은 에뮬레이트 된 준비된 문이 활성화 된 경우에만 작동합니다 . 비활성화되면 실패합니다 (그리고 비활성화되어야합니다!)
Madara 's Ghost

4
@Ross 구체적으로 대답 할 수는 없지만, LIMIT와 OFFSET은이 모든 PHP / MYSQL / PDO 광기가 개발 회로를 강타한 이후에 붙은 기능이라는 점을 지적 할 수 있습니다. 사실, 나는 Lerdorf 자신이 감독했다고 믿습니다. 몇 년 전 LIMIT 구현. 아니, ...이 질문에 대답하지 않지만, 그것은 애프터 마켓 애드온의 것을 나타냅니다 않으며, 당신은 그들이 때때로 해결하는 방법을 잘 알고있다
FredTheWebGuy

2
@Ross PDO는 값에 대한 바인딩을 허용하지 않습니다. bindParam ( ': something', 2) 시도하면 PDO가 숫자가 가질 수없는 변수에 대한 포인터를 사용하기 때문에 오류가 발생합니다 ($ i가 2이면 $ i에 대한 포인터를 가질 수 있지만 2 번).
Kristijan 2014-06-20

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... 왜 나에게 그렇게 간단하지 않습니까? 미래의 독자들에게 알려드립니다!
Matthew Johnson

@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를 사용하지만 시야를 넓히려 고 노력할 것이라고 생각했습니다.
Matthew Johnson

@MatthewJohnson mysql에 PDO를 사용하는 경우 드라이버가이 기능을 모두 지원합니다. 그래서, 당신은 약간의 실수로 인해이 메시지를 받고 있습니다
당신의 상식

1
드라이버 지원 문제 메시지가 나타나면 setAttributepdo 개체가 아닌 문 ($ stm, $ stmt) 을 호출했는지 다시 확인하십시오 .
Jehong 안

17

버그 보고서를 보면 다음이 작동 할 수 있습니다.

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

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

하지만 들어오는 데이터가 정확하다고 확신합니까? 오류 메시지 만있을 것 같습니다 때문에 하나 (인용 부호되는 정수로 반대) 수 후 견적. 수신 데이터에 오류가있을 수도 있습니다. 당신은 print_r($_GET);알아낼 수 있습니까?


1
``15 ', 15'. 첫 번째 숫자는 따옴표로 완전히 묶여 있습니다. 두 번째 숫자에는 따옴표가 전혀 없습니다. 네, 데이터가 좋습니다.
Nathan H

8

이것은 요약과 같습니다.
LIMIT / OFFSET 값을 매개 변수화하는 네 가지 옵션이 있습니다.

  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
3에 대해 % d와 함께 sprintf ()를 사용했을 것입니다. 변수를 사용하는 것보다 조금 더 안정적이라고 말하고 싶습니다.
hakre

예, varfunc cast + interpolation은 가장 실용적인 예가 아닙니다. {$_GET->int["limit"]}그런 경우에는 종종 게으름 을 사용 합니다.
mario

7

...에 대한 LIMIT :init, :end

그렇게 묶어야합니다. 배열의 모든 변수로 $req->execute(Array());캐스트되므로 작동하지 않는 경우 PDO::PARAM_STRLIMIT절대적으로 정수가 필요합니다. 원하는대로 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

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).';';

SQL 인젝션을 방지하기 위해 intval ()을 사용하는 것이 매우 중요합니다. 특히 $ _GET 등에서 제한을받는 경우에는 더욱 그렇습니다. 그렇게하면 이것이 LIMIT를 작동시키는 가장 쉬운 방법입니다.

'PDO에서 LIMIT의 문제'에 대한 많은 이야기가 있지만 여기에서 PDO 매개 변수는 항상 정수가 될 것이기 때문에 LIMIT에 사용되지 않았다고 생각합니다. 빠른 필터가 작동합니다. 그럼에도 불구하고 철학은 항상 SQL 주입 필터링을 직접 수행하지 않고 'PDO가 처리하도록'하는 것이기 때문에 약간 오해의 소지가 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.