รับสตริงแบบสอบถาม SQL ดิบจากคำสั่งที่เตรียมไว้ของ PDO


130

มีวิธีในการเรียกใช้สตริง SQL ดิบเมื่อเรียกใช้ PDOStatement :: execute () บนคำสั่งที่เตรียมไว้หรือไม่ สำหรับวัตถุประสงค์ในการดีบักสิ่งนี้จะเป็นประโยชน์อย่างยิ่ง



1
ตรวจสอบpdo-debugแบบบรรทัดเดียว
Sliq

วิธีที่สะอาดที่สุดที่ฉันพบคือไลบรารีE_PDOStatement $stmt = $pdo->prepare($query); /* ... */ echo $stmt->fullQuery;คุณเพียงแค่ ทำงานโดยการขยายคลาส PDOStatementดังนั้นจึงมีความสง่างามตามที่ PDO API อนุญาต
ComFreek

คำตอบ:


110

ฉันคิดว่าคุณหมายความว่าคุณต้องการเคียวรี SQL ขั้นสุดท้ายโดยมีค่าพารามิเตอร์สอดแทรกเข้าไป ฉันเข้าใจว่าสิ่งนี้จะเป็นประโยชน์สำหรับการดีบัก แต่ไม่ใช่วิธีที่งบเตรียมทำงาน พารามิเตอร์จะไม่รวมกับคำสั่งที่เตรียมไว้ทางฝั่งไคลเอ็นต์ดังนั้น PDO ไม่ควรเข้าถึงสตริงการสืบค้นรวมกับพารามิเตอร์

คำสั่ง SQL จะถูกส่งไปยังเซิร์ฟเวอร์ฐานข้อมูลเมื่อคุณเตรียม () และพารามิเตอร์จะถูกส่งแยกกันเมื่อคุณดำเนินการ () บันทึกการสืบค้นทั่วไปของ MySQL จะแสดง SQL ขั้นสุดท้ายที่มีการแก้ไขค่าหลังจากที่คุณดำเนินการ () ด้านล่างนี้เป็นข้อความที่ตัดตอนมาจากบันทึกการสืบค้นทั่วไปของฉัน ฉันรันแบบสอบถามจาก mysql CLI ไม่ใช่จาก PDO แต่หลักการก็เหมือนกัน

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 :: ATTR_EMULATE_PREPARES ในโหมดนี้ PDO จะแทรกพารามิเตอร์ลงในแบบสอบถาม SQL และส่งแบบสอบถามทั้งหมดเมื่อคุณดำเนินการ () นี่ไม่ใช่แบบสอบถามที่เตรียมไว้จริง คุณจะหลีกเลี่ยงประโยชน์ของแบบสอบถามที่เตรียมไว้โดยการแทรกตัวแปรลงในสตริง SQL ก่อนดำเนินการ ()


ความคิดเห็นจาก @afilina:

ไม่คิวรี SQL แบบข้อความจะไม่รวมกับพารามิเตอร์ระหว่างการดำเนินการ ดังนั้นจึงไม่มีอะไรให้ PDO แสดงให้คุณเห็น

ภายในถ้าคุณใช้ PDO :: ATTR_EMULATE_PREPARES PDO จะสร้างสำเนาของคิวรี SQL และสอดแทรกค่าพารามิเตอร์เข้าไปก่อนที่จะดำเนินการจัดเตรียมและดำเนินการ แต่ PDO ไม่เปิดเผยแบบสอบถาม SQL ที่แก้ไขนี้

ออบเจ็กต์ PDOStatement มีคุณสมบัติ $ queryString แต่จะถูกตั้งค่าในตัวสร้างสำหรับ PDOStatement เท่านั้นและจะไม่อัปเดตเมื่อมีการเขียนแบบสอบถามใหม่ด้วยพารามิเตอร์

เป็นคำขอคุณลักษณะที่สมเหตุสมผลสำหรับ PDO ที่จะขอให้พวกเขาเปิดเผยแบบสอบถามที่เขียนใหม่ แต่ถึงอย่างนั้นก็จะไม่ให้คำค้นหาที่ "สมบูรณ์" แก่คุณเว้นแต่คุณจะใช้ PDO :: ATTR_EMULATE_PREPARES

นี่คือเหตุผลที่ฉันแสดงวิธีแก้ปัญหาด้านบนของการใช้บันทึกการสืบค้นทั่วไปของเซิร์ฟเวอร์ MySQL เนื่องจากในกรณีนี้แม้แต่แบบสอบถามที่เตรียมไว้พร้อมตัวยึดพารามิเตอร์ก็ถูกเขียนใหม่บนเซิร์ฟเวอร์โดยมีการป้อนค่าพารามิเตอร์ลงในสตริงการสืบค้น แต่จะทำได้เฉพาะในระหว่างการบันทึกไม่ใช่ระหว่างการดำเนินการสืบค้น


10
และคุณจะได้รับแบบสอบถามรูอย่างไรเมื่อ PDO :: ATTR_EMULATE_PREPARES ถูกตั้งค่าเป็น TRUE
Yasen Zhelev

2
@Yasen Zhelev: หาก PDO กำลังจำลองการเตรียมการมันจะสอดแทรกค่าพารามิเตอร์ลงในแบบสอบถามก่อนที่จะเตรียมการสืบค้น ดังนั้น MySQL จะไม่เห็นเวอร์ชันของแบบสอบถามที่มีตัวยึดพารามิเตอร์ MySQL บันทึกเฉพาะแบบสอบถามทั้งหมด
Bill Karwin

2
@ Bill: 'พารามิเตอร์ไม่รวมกับคำสั่งที่เตรียมไว้ในฝั่งไคลเอ็นต์' - รอ - แต่มันรวมกันที่ฝั่งเซิร์ฟเวอร์หรือไม่? หรือ mysql แทรกค่าลงใน DB อย่างไร?
Stann

1
@afilina ไม่คุณทำไม่ได้ ดูคำอธิบายของฉันด้านบน
Bill Karwin

3
ว้าวโหวตลด? กรุณาอย่ายิงผู้ส่งสาร ฉันแค่อธิบายวิธีการทำงาน
Bill Karwin

107
/**
 * 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;
}

6
ทำไมไม่ลองใช้strtr(): เร็วกว่าง่ายกว่าและให้ผลลัพธ์แบบเดียวกัน strtr($query, $params);
Tony Chiboucas

การใช้งานสำหรับสิ่งนี้คืออะไร?

แค่อยากจะแวะมาและขอบคุณเช่นกันอยู่นอกคลาสพิเศษทั้งหมดสำหรับสิ่งนี้ซึ่งตอนนี้ฉันได้ลบออกไปเพราะมันเล็กและยอดเยี่ยม :) มีประโยชน์มากสำหรับการดีบักข้อความค้นหาทั้งหมดที่แอปพลิเคชันทำในแต่ละหน้าโดยการบันทึก: D
NaughtySquid

เห็นฟังก์ชั่นนี้แล้วทำให้ฉันมีความสุขมากแม้ว่าจะมีบางอย่างที่ฉันไม่เข้าใจทำไมคุณถึงตรวจสอบว่า$keyเป็น a stringและไม่$value? ฉันพลาดอะไรไปรึเปล่า? เหตุผลที่ฉันถามเป็นเพราะเอาต์พุตนี้พารามิเตอร์ที่สองไม่ถูกมองว่าเป็นสตริง:string(115) "INSERT INTO tokens (token_type, token_hash, user_id) VALUES ('resetpassword', hzFs5RLMpKwTeShTjP9AkTA2jtxXls86, 1);"
Kerwin Sneijders

1
นี่เป็นการเริ่มต้นที่ดี แต่จะล้มเหลวหากค่าของ $ param มีเครื่องหมายคำถาม ("?")
chickenchilli

32

ฉันแก้ไขวิธีการเพื่อรวมการจัดการเอาต์พุตของอาร์เรย์สำหรับคำสั่งเช่น WHERE IN (?)

อัปเดต: เพิ่งเพิ่มการตรวจสอบค่า 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;
}

2
ฉันคิดว่าคุณต้องทำแทน$values = $params; $values = array()
ทดสอบ

ชิ้นเล็ก ๆ อีกชิ้นที่พลาดคือเครื่องสาย ในการจับภาพเหล่านั้นให้วางไว้เหนือis_arrayเครื่องหมาย:if (is_string($value)) $values[$key] = "'" . $value . "'";
treeface

นี่เป็นเพียงค่าการผูกที่ จำกัด เพียงครั้งเดียวใน preg_replace เพิ่มบรรทัดนี้หลังจาก$values = $params; $values_limit = []; $words_repeated = array_count_values(str_word_count($sql, 1, ':_')); เพิ่มสิ่งนี้ภายในก่อนหากอยู่ใน foreach $values_limit[$key] = (isset($words_repeated[':'.$key]) ? intval($words_repeated[':'.$key]) : 1);และก่อนอื่นใน foreach ให้$values_limit = [];ใช้ foreach loop $ values ​​อีกครั้งเพื่อ preg_replace ด้วยisset($values_limit[$key])
vee

ตัวอย่างเช่น loop $ values if (is_array($values)) { foreach ($values as $key => $val) { if (isset($values_limit[$key])) { $sql = preg_replace(['/:'.$key.'/'], [$val], $sql, $values_limit[$key], $count); } } unset($key, $val); } else { $sql = preg_replace($keys, $values, $sql, 1, $count); }
vee

12

สายไปหน่อย แต่ตอนนี้มีแล้ว PDOStatement::debugDumpParams

ทิ้งข้อมูลที่มีอยู่ในคำสั่งที่เตรียมไว้โดยตรงบนเอาต์พุต จะจัดเตรียมแบบสอบถาม SQL ที่ใช้งานจำนวนพารามิเตอร์ที่ใช้ (Params) รายการพารามิเตอร์พร้อมชื่อประเภท (พารามิเตอร์) เป็นจำนวนเต็มชื่อคีย์หรือตำแหน่งและตำแหน่งในแบบสอบถาม (หากเป็นเช่นนี้ ได้รับการสนับสนุนโดยไดรเวอร์ 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();

?>


และเพื่อให้อ่านง่ายขึ้น:echo '<pre>'; $sth->debugDumpParams(); echo '</pre>';
SandroMarques

10

วิธีแก้ไขคือใส่ข้อผิดพลาดลงในแบบสอบถามโดยสมัครใจและพิมพ์ข้อความของข้อผิดพลาด:

//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]: ข้อผิดพลาดทางไวยากรณ์หรือการละเมิดการเข้าถึง: [... ] ใกล้'ELECT * FROM Person WHERE age = 18'ที่บรรทัด 1

สิ่งสำคัญคือต้องทราบว่าจะพิมพ์เฉพาะ 80 อักขระแรกของข้อความค้นหา


ฉันไม่รู้ว่าทำไมจึงถูกลดคะแนน มันง่ายและได้ผล ทำงานได้รวดเร็ว เร็วกว่าการเปิดล็อกค้นหาบรรทัดที่ถูกต้องในบันทึกจากนั้นปิดใช้งานบันทึกจากนั้นล้างไฟล์บันทึก
Bojan Hrnkas

@BojanHrnkas ความยาวของตัวอย่างข้อผิดพลาดมี จำกัด มาก สำหรับคำค้นหาง่ายๆเช่นนี้การแทนที่ตัวยึดตำแหน่งด้วยตัวแปรด้วยตนเองจะง่ายกว่า และวิธีนี้ใช้ได้เฉพาะเมื่อคุณเปิดใช้งานการจำลอง
สามัญสำนึกของคุณ

9

เพิ่มอีกเล็กน้อยในโค้ดโดย 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;
}

1
มากประโยชน์ผมปรับเปลี่ยนบางอย่างเพื่อแทนที่bindParamการทำงานของPDOStatementชั้นเรียนและการตรวจสอบถ้าค่าเป็นสตริงหรือจำนวนเต็มกับPDO: paramsค่า
Sergio Flores

1
เราจะดูได้ที่ไหน
Mawg กล่าวว่าคืนสถานะ Monica

8

PDOStatement มีคุณสมบัติสาธารณะ $ queryString มันควรจะเป็นสิ่งที่คุณต้องการ

ฉันเพิ่งสังเกตว่า PDOStatement มีวิธีการ debugDumpParams ที่ไม่มีเอกสาร () ซึ่งคุณอาจต้องการดูด้วย


1
debugDumpParams ไม่มีเอกสารphp.net/manual/en/pdostatement.debugdumpparams.php
mloskot

Nope $ queryString ไม่แสดงค่าพารามิเตอร์ที่รวมอยู่
Andreas

5

คุณสามารถขยายคลาส PDOStatement เพื่อจับตัวแปรที่มีขอบเขตและเก็บไว้เพื่อใช้ในภายหลัง จากนั้นอาจเพิ่ม 2 วิธีการหนึ่งสำหรับการล้างตัวแปร (debugBindedVariables) และอีกวิธีหนึ่งเพื่อพิมพ์แบบสอบถามด้วยตัวแปรเหล่านั้น (debugQuery):

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

จากนั้นคุณสามารถใช้คลาสที่สืบทอดมานี้สำหรับการดีบัก purpouses

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

ที่เกิดขึ้นใน

เลือกผู้ใช้จากผู้ใช้ WHERE user = 'user_test'

อาร์เรย์ ([: test] => user_test)


4

ฉันใช้เวลาอย่างดีในการค้นคว้าสถานการณ์นี้เพื่อตอบสนองความต้องการของตัวเอง เธรด SO นี้และอื่น ๆ อีกมากมายช่วยฉันได้มากดังนั้นฉันจึงอยากแบ่งปันสิ่งที่ฉันคิดขึ้นมา

แม้ว่าการเข้าถึงสตริงการสืบค้นแบบสอดแทรกจะเป็นประโยชน์อย่างมากในขณะแก้ไขปัญหา แต่เราต้องการที่จะรักษาบันทึกของข้อความค้นหาบางรายการไว้เท่านั้น (ดังนั้นการใช้บันทึกฐานข้อมูลเพื่อจุดประสงค์นี้จึงไม่เหมาะอย่างยิ่ง) นอกจากนี้เรายังต้องการที่จะสามารถใช้บันทึกเพื่อสร้างเงื่อนไขของตารางใหม่ในช่วงเวลาใดก็ได้ดังนั้นเราจึงจำเป็นต้องทำให้แน่ใจว่าสตริงที่ถูกแก้ไขถูกหลีกเลี่ยงอย่างถูกต้อง ในที่สุดเราต้องการขยายฟังก์ชันนี้ไปยังฐานรหัสทั้งหมดของเราโดยต้องเขียนซ้ำให้น้อยที่สุดเท่าที่จะเป็นไปได้ (กำหนดเวลาการตลาดและอื่น ๆ คุณรู้ว่ามันเป็นอย่างไร)

วิธีแก้ปัญหาของฉันคือการขยายการทำงานของออบเจ็กต์ PDOStatement เริ่มต้นเพื่อแคชค่าที่กำหนดพารามิเตอร์ (หรือการอ้างอิง) และเมื่อคำสั่งถูกเรียกใช้ให้ใช้ฟังก์ชันการทำงานของวัตถุ PDO เพื่อหลีกเลี่ยงพารามิเตอร์อย่างถูกต้องเมื่อมีการแทรกกลับเข้าไปในแบบสอบถาม เชือก จากนั้นเราจะผูกในการดำเนินการวิธีการของวัตถุคำสั่งและเข้าสู่ระบบการค้นหาที่เกิดขึ้นจริงที่ถูกดำเนินการในช่วงเวลานั้น ( หรืออย่างน้อยเป็นที่ซื่อสัตย์ของการทำสำเนาที่เป็นไปได้)

ที่ผมกล่าวว่าเราไม่ได้ต้องการที่จะปรับเปลี่ยนฐานรหัสทั้งหมดเพื่อเพิ่มฟังก์ชันการทำงานนี้เพื่อให้เราเขียนทับเริ่มต้นbindParam()และbindValue()วิธีการของวัตถุ PDOStatement ทำแคชข้อมูลผูกพันของเราแล้วโทรหรือผู้ปกครองparent::bindParam() :: bindValue()สิ่งนี้ทำให้ฐานรหัสที่มีอยู่ของเรายังคงทำงานได้ตามปกติ

สุดท้ายเมื่อวิธีการที่เรียกว่าเราดำเนินการแก้ไขของเราและให้สตริงผลลัพธ์เป็นคุณสมบัติใหม่execute() E_PDOStatement->fullQueryสามารถส่งออกเพื่อดูคิวรีหรือตัวอย่างเช่นเขียนลงในล็อกไฟล์

ส่วนขยายพร้อมกับคำแนะนำในการติดตั้งและการกำหนดค่ามีอยู่ใน github:

https://github.com/noahheck/E_PDOStatement

การปฏิเสธความรับผิด :
เห็นได้ชัดว่าฉันเขียนส่วนขยายนี้ เนื่องจากได้รับการพัฒนาด้วยความช่วยเหลือจากหลาย ๆ เธรดที่นี่ฉันจึงต้องการโพสต์วิธีแก้ปัญหาของฉันที่นี่เผื่อว่ามีใครเจอเธรดเหล่านี้เหมือนที่ฉันทำ


ขอบคุณสำหรับการแบ่งปัน. ไม่โหวตโหวตเพราะคำตอบยาวเกินไปมีรหัสน้อยเกินไป
T30

1

คุณสมบัติ $ queryString ที่กล่าวถึงอาจจะส่งคืนเฉพาะแบบสอบถามที่ส่งเข้ามาโดยไม่มีการแทนที่พารามิเตอร์ด้วยค่า ใน. Net ฉันมีส่วนที่จับได้ของผู้บริหารการค้นหาของฉันทำการค้นหาอย่างง่ายแทนที่พารามิเตอร์ด้วยค่าที่ให้มาเพื่อให้บันทึกข้อผิดพลาดสามารถแสดงค่าจริงที่ถูกใช้สำหรับการสืบค้น คุณควรจะสามารถระบุพารามิเตอร์ใน PHP และแทนที่พารามิเตอร์ด้วยค่าที่กำหนด


1

คุณสามารถใช้ได้ 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 เท่านั้น


0

ฉันรู้ว่าคำถามนี้เก่าไปหน่อย แต่ฉันใช้รหัสนี้มานานแล้ว (ฉันใช้คำตอบจาก @ chris-go) และตอนนี้รหัสเหล่านี้ล้าสมัยด้วย PHP 7.2

ฉันจะโพสต์เวอร์ชันที่อัปเดตของรหัสเหล่านี้ (เครดิตสำหรับรหัสหลักมาจาก@bigwebguy , @mikeและ@ 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 (และหวังว่าจะมีเวอร์ชันในอนาคตด้วย)


-1

ที่เกี่ยวข้องบ้าง ... ถ้าคุณเป็นเพียงการพยายามที่จะฆ่าเชื้อตัวแปรโดยเฉพาะอย่างยิ่งคุณสามารถใช้PDO :: อ้าง ตัวอย่างเช่นหากต้องการค้นหาเงื่อนไข LIKE บางส่วนหากคุณติดอยู่กับกรอบงานที่ จำกัด เช่น CakePHP:

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

-1

คำตอบของ Mikeใช้งานได้ดีจนกว่าคุณจะใช้ค่าการผูก "ใช้ซ้ำ"
ตัวอย่างเช่น:

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)

คำตอบของไมค์สามารถแทนที่คำแรกเท่านั้น: ค้นหา แต่ไม่ใช่คำตอบที่สอง
ดังนั้นฉันจึงเขียนคำตอบของเขาใหม่เพื่อทำงานกับพารามิเตอร์หลายตัวที่สามารถใช้ซ้ำได้อย่างเหมาะสม

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

-1

preg_replace ใช้ไม่ได้สำหรับฉันและเมื่อ binding_ อายุมากกว่า 9, binding_1 และ binding_10 ถูกแทนที่ด้วย str_replace (ทิ้ง 0 ไว้ข้างหลัง) ดังนั้นฉันจึงทำการแทนที่ย้อนหลัง:

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;

}

หวังว่าจะมีคนพบว่ามีประโยชน์


-1

ฉันต้องการบันทึกสตริงการสืบค้นแบบเต็มหลังจากผูกพารามิเตอร์ดังนั้นนี่จึงเป็นส่วนหนึ่งในโค้ดของฉัน หวังว่ามันจะเป็นประโยชน์สำหรับทุกคนหมวกที่มีปัญหาเดียวกัน

/**
 * 
 * @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);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.