문제

이 있을 얻을 수 있는 방법 원서 SQL 문을 실행할 때 호출 PDOStatement::execute()에서 준비된 문?디버깅 목적으로 이것은 매우 유용합니다.

도움이 되었습니까?

해결책

매개 변수 값이 보간 된 최종 SQL 쿼리를 원한다고 가정합니다. 나는 이것이 디버깅에 유용하다는 것을 이해하지만, 준비된 진술이 작동하는 방식은 아닙니다. 매개 변수는 클라이언트 측에서 준비된 명령문과 결합되지 않으므로 PDO는 매개 변수와 결합 된 쿼리 문자열에 액세스 할 수 없어야합니다.

sql 문은 repay ()를 할 때 데이터베이스 서버로 전송되며 execute ()를 할 때 매개 변수가 별도로 전송됩니다. MySQL의 일반 쿼리 로그는 execute () 후 보간 된 값이있는 최종 SQL을 표시합니다. 아래는 일반 쿼리 로그에서 발췌 한 것입니다. PDO가 아닌 MySQL CLI에서 쿼리를 실행했지만 원리는 동일합니다.

081016 16:51:28 2 Query       prepare s1 from 'select * from foo where i = ?'
                2 Prepare     [2] select * from foo where i = ?
081016 16:51:39 2 Query       set @a =1
081016 16:51:47 2 Query       execute s1 using @a
                2 Execute     [2] select * from foo where i = 1

pdo 속성 pdo :: att_emulate_prepares를 설정하면 원하는 것을 얻을 수도 있습니다. 이 모드에서 PDO는 매개 변수를 SQL 쿼리에 보간하고 ()를 실행할 때 전체 쿼리를 보냅니다. 이것은 진정한 준비된 쿼리가 아닙니다. execute () 전에 변수를 SQL 문자열에 보간하여 준비된 쿼리의 이점을 우회합니다.


@afilina의 RE Comment :

아니요, 텍스트 SQL 쿼리입니다 ~ 아니다 실행 중 매개 변수와 결합됩니다. 따라서 PDO가 당신에게 보여줄 것이 없습니다.

내부적으로 pdo :: attr_emulate_prepares를 사용하는 경우 PDO는 SQL 쿼리의 사본을 만들고 준비 및 실행을 수행하기 전에 매개 변수 값을 보간합니다. 그러나 PDO는이 수정 된 SQL 쿼리를 노출시키지 않습니다.

pdostatement 객체에는 속성 $ QueryString이 있지만 PDOSTATEMENT의 생성자에만 설정되며 쿼리가 매개 변수로 다시 작성 될 때 업데이트되지 않습니다.

PDO가 다시 작성한 쿼리를 노출하도록 요청하는 것은 합리적인 기능 요청입니다. 그러나 PDO :: Att_emulate_prepares를 사용하지 않는 한 "완전한"쿼리를 제공하지 않습니다.

그렇기 때문에 MySQL Server의 일반 쿼리 로그를 사용하는 위의 해결 방법을 표시하는 이유입니다.이 경우 매개 변수 자리 표시기가있는 준비된 쿼리조차 서버에 다시 작성되며 매개 변수 값이 쿼리 문자열로 백색되어 있기 때문입니다. 그러나 이것은 쿼리 실행 중에만 로깅 중에 만 수행됩니다.

다른 팁

/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public static function interpolateQuery($query, $params) {
    $keys = array();

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }
    }

    $query = preg_replace($keys, $params, $query, 1, $count);

    #trigger_error('replaced '.$count.' keys');

    return $query;
}

(?)와 같은 명령문에 대한 배열의 처리 출력을 포함하도록 메소드를 수정했습니다.

업데이트 : NULL 값 및 복제 된 $ params에 대한 점검을 추가하여 실제 $ PARAM 값이 수정되지 않았습니다.

훌륭한 일 Bigwebguy와 감사합니다!

/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }

        if (is_string($value))
            $values[$key] = "'" . $value . "'";

        if (is_array($value))
            $values[$key] = "'" . implode("','", $value) . "'";

        if (is_null($value))
            $values[$key] = 'NULL';
    }

    $query = preg_replace($keys, $values, $query);

    return $query;
}

Pdostatement에는 공공 재산 $ QueryString이 있습니다. 그것은 당신이 원하는 것이되어야합니다.

Pdostatement에는 문서화되지 않은 메소드 Debugdumpparams ()도보고 싶을 수도 있습니다.

조금 늦게 아마 하지만 지금 PDOStatement::debugDumpParams

Dumps 정보를 포함하여 준비에 직접 문의 출력입니다.그것이 제공하는 SQL 쿼리에는 사용,수의 사용되는 매개 변수(Params),매개 변수 목록은,자신의 이름을 형(paramtype)as integer,자신의 키 이름이나 위치,그리고 위치에서 쿼리(을 지원하는 경우에 PDO 드라이버 그렇지 않으면,그것은 것입 -1).

당신은 더 많이 찾을 수 있습에 php 공식 문서

예제:

<?php
/* Execute a prepared statement by binding PHP variables */
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour');
$sth->bindParam(':calories', $calories, PDO::PARAM_INT);
$sth->bindValue(':colour', $colour, PDO::PARAM_STR, 12);
$sth->execute();

$sth->debugDumpParams();

?>

Mike의 코드에 조금 더 추가 - 단일 따옴표를 추가하려면 값을 걸어

/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }

        if (is_array($value))
            $values[$key] = implode(',', $value);

        if (is_null($value))
            $values[$key] = 'NULL';
    }
    // Walk the array to see if we can add single-quotes to strings
    array_walk($values, create_function('&$v, $k', 'if (!is_numeric($v) && $v!="NULL") $v = "\'".$v."\'";'));

    $query = preg_replace($keys, $values, $query, 1, $count);

    return $query;
}

나는이 상황을 내 자신의 요구에 대해 연구하는 데 많은 시간을 보냈습니다. 이것과 다른 몇 가지 스레드는 나에게 큰 도움이되었으므로 내가 생각한 것을 공유하고 싶었습니다.

보간 된 쿼리 문자열에 액세스하는 것은 중요한 이점이지만 문제 해결은 중요한 이점이지만 특정 쿼리 만 로그를 유지할 수 있기를 원했습니다 (따라서이 목적으로 데이터베이스 로그를 사용하는 것이 이상적이지 않았습니다). 우리는 또한 로그를 사용하여 주어진 시간에 테이블의 상태를 재현 할 수 있기를 원했기 때문에 보간 문자열이 제대로 피해야했습니다. 마지막으로, 우리는이 기능을 가능한 한 적게 다시 쓰여야하는 전체 코드 기반으로 확장하고 싶었습니다 (마감일, 마케팅 등; 귀하는 그것이 어떻게 알고 있습니다).

내 솔루션은 기본 PDostatement 객체의 기능을 확장하여 매개 변수화 된 값 (또는 참조)을 캐시하고 명령문이 실행되면 PDO 객체의 기능을 사용하여 매개 변수가 쿼리에 다시 주입 될 때 매개 변수를 올바르게 탈출하는 것입니다. 끈. 그런 다음 명령문 객체의 메소드를 실행하고 당시 실행 된 실제 쿼리를 기록하기 위해 묶을 수 있습니다 (또는 적어도 가능한 한 번식에 대한 충실한).

내가 말했듯이, 우리는이 기능을 추가하기 위해 전체 코드베이스를 수정하고 싶지 않았으므로 기본값을 덮어 씁니다. bindParam() 그리고 bindValue() pdostatement 객체의 방법, 바운드 데이터 캐싱을 수행 한 다음 parent::bindParam() 또는 부모 ::bindValue(). 이를 통해 기존 코드베이스는 정상적으로 계속 작동 할 수있었습니다.

마지막으로 execute() 방법이 호출되고, 보간을 수행하고 결과 문자열을 새 속성으로 제공합니다. E_PDOStatement->fullQuery. 쿼리를 보거나 로그 파일에 기록되면 출력 될 수 있습니다.

확장자는 설치 및 구성 지침과 함께 GitHub에서 사용할 수 있습니다.

https://github.com/noahheck/e_pdostatem

부인 성명:
분명히, 내가 언급했듯이, 나는이 확장을 썼습니다. 여기에 많은 스레드의 도움으로 개발 되었기 때문에 다른 스레드가 다른 스레드를 방문한 경우에 저의 솔루션을 여기에 게시하고 싶었습니다.

PDOSTATEMENT 클래스를 확장하여 경계 변수를 캡처하고 나중에 사용하도록 저장할 수 있습니다. 그런 다음 변수 소독제 (디버그 바인드 바이트에 대한), 다른 하나는 변수 (디버그 쿼리)로 쿼리를 인쇄하기 위해 2 가지 방법을 추가 할 수 있습니다.

class DebugPDOStatement extends \PDOStatement{
  private $bound_variables=array();
  protected $pdo;

  protected function __construct($pdo) {
    $this->pdo = $pdo;
  }

  public function bindValue($parameter, $value, $data_type=\PDO::PARAM_STR){
    $this->bound_variables[$parameter] = (object) array('type'=>$data_type, 'value'=>$value);
    return parent::bindValue($parameter, $value, $data_type);
  }

  public function bindParam($parameter, &$variable, $data_type=\PDO::PARAM_STR, $length=NULL , $driver_options=NULL){
    $this->bound_variables[$parameter] = (object) array('type'=>$data_type, 'value'=>&$variable);
    return parent::bindParam($parameter, $variable, $data_type, $length, $driver_options);
  }

  public function debugBindedVariables(){
    $vars=array();

    foreach($this->bound_variables as $key=>$val){
      $vars[$key] = $val->value;

      if($vars[$key]===NULL)
        continue;

      switch($val->type){
        case \PDO::PARAM_STR: $type = 'string'; break;
        case \PDO::PARAM_BOOL: $type = 'boolean'; break;
        case \PDO::PARAM_INT: $type = 'integer'; break;
        case \PDO::PARAM_NULL: $type = 'null'; break;
        default: $type = FALSE;
      }

      if($type !== FALSE)
        settype($vars[$key], $type);
    }

    if(is_numeric(key($vars)))
      ksort($vars);

    return $vars;
  }

  public function debugQuery(){
    $queryString = $this->queryString;

    $vars=$this->debugBindedVariables();
    $params_are_numeric=is_numeric(key($vars));

    foreach($vars as $key=>&$var){
      switch(gettype($var)){
        case 'string': $var = "'{$var}'"; break;
        case 'integer': $var = "{$var}"; break;
        case 'boolean': $var = $var ? 'TRUE' : 'FALSE'; break;
        case 'NULL': $var = 'NULL';
        default:
      }
    }

    if($params_are_numeric){
      $queryString = preg_replace_callback( '/\?/', function($match) use( &$vars) { return array_shift($vars); }, $queryString);
    }else{
      $queryString = strtr($queryString, $vars);
    }

    echo $queryString.PHP_EOL;
  }
}


class DebugPDO extends \PDO{
  public function __construct($dsn, $username="", $password="", $driver_options=array()) {
    $driver_options[\PDO::ATTR_STATEMENT_CLASS] = array('DebugPDOStatement', array($this));
    $driver_options[\PDO::ATTR_PERSISTENT] = FALSE;
    parent::__construct($dsn,$username,$password, $driver_options);
  }
}

그런 다음이 상속 된 클래스를 사용하여 퓨어스를 디버깅 할 수 있습니다.

$dbh = new DebugPDO('mysql:host=localhost;dbname=test;','user','pass');

$var='user_test';
$sql=$dbh->prepare("SELECT user FROM users WHERE user = :test");
$sql->bindValue(':test', $var, PDO::PARAM_STR);
$sql->execute();

$sql->debugQuery();
print_r($sql->debugBindedVariables());

를 야기하는

사용자 = 'user_test'인 사용자에서 사용자를 선택하십시오.

배열 ([: test] => user_test)

해결책은 자발적으로 쿼리에 오류를 넣고 오류의 메시지를 인쇄하는 것입니다.

//Connection to the database
$co = new PDO('mysql:dbname=myDB;host=localhost','root','');
//We allow to print the errors whenever there is one
$co->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

//We create our prepared statement
$stmt = $co->prepare("ELECT * FROM Person WHERE age=:age"); //I removed the 'S' of 'SELECT'
$stmt->bindValue(':age','18',PDO::PARAM_STR);
try {
    $stmt->execute();
} catch (PDOException $e) {
    echo $e->getMessage();
}

표준 출력 :

sqlstate [42000] : 구문 오류 또는 액세스 위반 : [...] 가까운 '나이 = 18 인 사람의 선출 *' 1 행에서

쿼리의 첫 80 자만 인쇄한다는 점에 유의해야합니다.

언급 된 $ QueryString 속성은 아마도 매개 변수가 해당 값으로 대체되지 않고 전달 된 쿼리 만 반환 할 것입니다. .NET에서 쿼리 executer의 캐치 부분이 매개 변수를 값으로 제공 한 값으로 간단한 검색을 대체하여 오류 로그가 쿼리에 사용 된 실제 값을 표시 할 수 있도록합니다. PHP의 매개 변수를 열거하고 매개 변수를 지정된 값으로 바꿀 수 있어야합니다.

다소 관련이 있습니다 ... 특정 변수를 소독하려는 경우 사용할 수 있습니다. pdo :: 인용. 예를 들어, CakePHP와 같은 제한된 프레임 워크가있는 경우 여러 부분 유사 조건을 검색합니다.

$pdo = $this->getDataSource()->getConnection();
$results = $this->find('all', array(
    'conditions' => array(
        'Model.name LIKE ' . $pdo->quote("%{$keyword1}%"),
        'Model.name LIKE ' . $pdo->quote("%{$keyword2}%"),
    ),
);

bind param 이후에 전체 쿼리 문자열을 기록해야하므로 코드의 조각입니다. 희망, 모든 사람이 동일한 문제를 가진 모든 사람에게 유용합니다.

/**
 * 
 * @param string $str
 * @return string
 */
public function quote($str) {
    if (!is_array($str)) {
        return $this->pdo->quote($str);
    } else {
        $str = implode(',', array_map(function($v) {
                    return $this->quote($v);
                }, $str));

        if (empty($str)) {
            return 'NULL';
        }

        return $str;
    }
}

/**
 * 
 * @param string $query
 * @param array $params
 * @return string
 * @throws Exception
 */
public function interpolateQuery($query, $params) {
    $ps = preg_split("/'/is", $query);
    $pieces = [];
    $prev = null;
    foreach ($ps as $p) {
        $lastChar = substr($p, strlen($p) - 1);

        if ($lastChar != "\\") {
            if ($prev === null) {
                $pieces[] = $p;
            } else {
                $pieces[] = $prev . "'" . $p;
                $prev = null;
            }
        } else {
            $prev .= ($prev === null ? '' : "'") . $p;
        }
    }

    $arr = [];
    $indexQuestionMark = -1;
    $matches = [];

    for ($i = 0; $i < count($pieces); $i++) {
        if ($i % 2 !== 0) {
            $arr[] = "'" . $pieces[$i] . "'";
        } else {
            $st = '';
            $s = $pieces[$i];
            while (!empty($s)) {
                if (preg_match("/(\?|:[A-Z0-9_\-]+)/is", $s, $matches, PREG_OFFSET_CAPTURE)) {
                    $index = $matches[0][1];
                    $st .= substr($s, 0, $index);
                    $key = $matches[0][0];
                    $s = substr($s, $index + strlen($key));

                    if ($key == '?') {
                        $indexQuestionMark++;
                        if (array_key_exists($indexQuestionMark, $params)) {
                            $st .= $this->quote($params[$indexQuestionMark]);
                        } else {
                            throw new Exception('Wrong params in query at ' . $index);
                        }
                    } else {
                        if (array_key_exists($key, $params)) {
                            $st .= $this->quote($params[$key]);
                        } else {
                            throw new Exception('Wrong params in query with key ' . $key);
                        }
                    }
                } else {
                    $st .= $s;
                    $s = null;
                }
            }
            $arr[] = $st;
        }
    }

    return implode('', $arr);
}

이 질문은 조금 오래되었음을 알고 있지만, 많은 시간 전에이 코드를 사용하고 있습니다 ( @chris-go의 응답을 사용했습니다). 이제이 코드는 PHP 7.2와 더 이상 사용되지 않습니다.

이 코드의 업데이트 된 버전을 게시하겠습니다 (기본 코드의 크레딧은 @bigwebguy, @마이크 그리고 @Chris-Go, 모두이 질문에 대한 답변) :

/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }

        if (is_array($value))
            $values[$key] = implode(',', $value);

        if (is_null($value))
            $values[$key] = 'NULL';
    }
    // Walk the array to see if we can add single-quotes to strings
    array_walk($values, function(&$v, $k) { if (!is_numeric($v) && $v != "NULL") $v = "\'" . $v . "\'"; });

    $query = preg_replace($keys, $values, $query, 1, $count);

    return $query;
}

코드의 변경 사항은 Array_Walk () 함수에 있으며 익명 함수로 Create_Function을 대체합니다. 이것은 이러한 좋은 코드 조각을 기능적이며 PHP 7.2 (그리고 미래 버전도 희망)과 호환됩니다.

당신이 사용할 수있는 sprintf(str_replace('?', '"%s"', $sql), ...$params);

예는 다음과 같습니다.

function mysqli_prepared_query($link, $sql, $types='', $params=array()) {
    echo sprintf(str_replace('?', '"%s"', $sql), ...$params);
    //prepare, bind, execute
}

$link = new mysqli($server, $dbusername, $dbpassword, $database);
$sql = "SELECT firstname, lastname FROM users WHERE userage >= ? AND favecolor = ?";
$types = "is"; //integer and string
$params = array(20, "Brown");

if(!$qry = mysqli_prepared_query($link, $sql, $types, $params)){
    echo "Failed";
} else {
    echo "Success";
}

이것은 PHP> = 5.6에만 작동합니다

Mike's answer is working good until you are using the "re-use" bind value.
For example:

SELECT * FROM `an_modules` AS `m` LEFT JOIN `an_module_sites` AS `ms` ON m.module_id = ms.module_id WHERE 1 AND `module_enable` = :module_enable AND `site_id` = :site_id AND (`module_system_name` LIKE :search OR `module_version` LIKE :search)

The Mike's answer can only replace first :search but not the second.
So, I rewrite his answer to work with multiple parameters that can re-used properly.

public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;
    $values_limit = [];

    $words_repeated = array_count_values(str_word_count($query, 1, ':_'));

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
            $values_limit[$key] = (isset($words_repeated[':'.$key]) ? intval($words_repeated[':'.$key]) : 1);
        } else {
            $keys[] = '/[?]/';
            $values_limit = [];
        }

        if (is_string($value))
            $values[$key] = "'" . $value . "'";

        if (is_array($value))
            $values[$key] = "'" . implode("','", $value) . "'";

        if (is_null($value))
            $values[$key] = 'NULL';
    }

    if (is_array($values)) {
        foreach ($values as $key => $val) {
            if (isset($values_limit[$key])) {
                $query = preg_replace(['/:'.$key.'/'], [$val], $query, $values_limit[$key], $count);
            } else {
                $query = preg_replace(['/:'.$key.'/'], [$val], $query, 1, $count);
            }
        }
        unset($key, $val);
    } else {
        $query = preg_replace($keys, $values, $query, 1, $count);
    }
    unset($keys, $values, $values_limit, $words_repeated);

    return $query;
}

preg_replace didn't work for me and when binding_ was over 9, binding_1 and binding_10 was replaced with str_replace (leaving the 0 behind), so I made the replacements backwards:

public function interpolateQuery($query, $params) {
$keys = array();
    $length = count($params)-1;
    for ($i = $length; $i >=0; $i--) {
            $query  = str_replace(':binding_'.(string)$i, '\''.$params[$i]['val'].'\'', $query);
           }
        // $query  = str_replace('SQL_CALC_FOUND_ROWS', '', $query, $count);
        return $query;

}

Hope someone finds it useful.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top