الحصول على سلسلة استعلام SQL أولية من بيانات PDO المعدة

StackOverflow https://stackoverflow.com/questions/210564

سؤال

هل هناك طريقة لتنفيذ سلسلة SQL الأولية عند استدعاء PDOStatement::execute() في عبارة معدة؟لأغراض تصحيح الأخطاء، سيكون هذا مفيدًا للغاية.

هل كانت مفيدة؟

المحلول

أفترض أنك تقصد أنك تريد استعلام SQL النهائي، مع إضافة قيم المعلمات إليه.أدرك أن هذا قد يكون مفيدًا لتصحيح الأخطاء، لكن هذه ليست الطريقة التي تعمل بها البيانات المعدة.لا يتم دمج المعلمات مع بيان مُعد من جانب العميل، لذلك لا ينبغي مطلقًا أن تتمتع شركة تنمية نفط عمان بإمكانية الوصول إلى سلسلة الاستعلام المدمجة مع معلماتها.

يتم إرسال عبارة 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 وتقوم بإدخال قيم المعلمات فيه قبل القيام بالتحضير والتنفيذ.لكن شركة تنمية نفط عمان لا تكشف عن استعلام SQL المعدل هذا.

يحتوي كائن PDOStatement على الخاصية $queryString، ولكن يتم تعيينها فقط في مُنشئ PDOStatement، ولا يتم تحديثها عند إعادة كتابة الاستعلام باستخدام المعلمات.

سيكون من المعقول طلب ميزة لشركة تنمية نفط عمان أن تطلب منها الكشف عن الاستعلام المعاد كتابته.ولكن حتى هذا لن يمنحك الاستعلام "الكامل" إلا إذا كنت تستخدم PDO::ATTR_EMULATE_PREPARES.

لهذا السبب أعرض الحل البديل أعلاه لاستخدام سجل الاستعلام العام لخادم MySQL، لأنه في هذه الحالة تتم إعادة كتابة الاستعلام المُجهز باستخدام العناصر النائبة للمعلمات على الخادم، مع إعادة ملء قيم المعلمات في سلسلة الاستعلام.ولكن يتم ذلك فقط أثناء التسجيل، وليس أثناء تنفيذ الاستعلام.

نصائح أخرى

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

ولقد عدلت طريقة لتشمل التعامل مع إخراج صفائف لعبارات مثل WHERE IN (؟).

وUPDATE: أدرجت فقط الاختيار لقيمة NULL وتكرار $ بارامس لا يتم تعديل $ الفعلي من القيم المعلمة.

والعظمى 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 له خاصية العامة $ سلسلة استعلام مواقع المعلومات. وينبغي أن يكون على ما تريد.

ولقد لاحظ فقط أن PDOStatement له debugDumpParams طريقة غير موثقة () التي قد تحتاج أيضا للنظر في.

وA متأخرا بعض الشيء ربما ولكن الآن هناك PDOStatement::debugDumpParams

<اقتباس فقرة>   

ومقالب المعلومات الواردة في بيان معد مباشرة على   الإخراج. وسوف توفر الاستعلام SQL في الاستخدام، وعدد من   المعايير المستخدمة (بارامس)، وقائمة من المعلمات، مع اسمها،   اكتب (paramtype) كعدد، اسم المفتاح أو موقف، و   موقف في الاستعلام (إذا كان هذا معتمد من قبل سائق شركة تنمية نفط عمان،   خلاف ذلك، سيكون -1).

ويمكنك أن تجد أكثر على مسؤول مستندات فب

مثال:

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

?>

وأضاف أكثر قليلا إلى رمز مايك - المشي القيم لإضافة علامات الاقتباس المفردة

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

وقضيت قدرا كبيرا من الوقت في البحث عن هذا الوضع لتلبية الاحتياجات الخاصة. هذا والعديد من المواضيع SO ساعدتني كثيرا، لذلك أردت أن حصة ما خطرت لي.

وحين وجود الوصول إلى سلسلة الاستعلام محرف هو فائدة كبيرة في حين استكشاف الأخطاء وإصلاحها، أردنا أن تكون قادرة على الحفاظ على سجل فقط بعض الاستفسارات (لذلك، وذلك باستخدام سجلات قاعدة البيانات لهذا الغرض لم يكن مثاليا). أردنا أيضا أن تكون قادرة على استخدام السجلات لإعادة حالة من الجداول في أي وقت من الأوقات، لذلك، نحن بحاجة لجعل وهرب بعض السلاسل محرف بشكل صحيح. وأخيرا، أردنا أن تمتد هذه الوظيفة لدينا قاعدة كود كامل الحاجة إلى إعادة كتابة اقل من ذلك قدر الإمكان (المواعيد النهائية، والتسويق، وعلى هذا الأساس، كنت أعرف كيف هو).

وكان لي حل لتوسيع وظائف الكائن PDOStatement الافتراضي لتخزين قيم معلمات (أو مراجع)، وعندما يتم تنفيذ العبارة، استخدام وظيفة من وجوه PDO للهروب من المعلمات بشكل صحيح عندما يتم حقنها مرة أخرى في إلى سلسلة الاستعلام. نحن يمكن بعد التعادل في تنفيذ أسلوب الكائن بيان وتسجيل الاستعلام الفعلي التي تم تنفيذها في ذلك الوقت (<م> أو على الأقل المؤمنين الاستنساخ ممكن) .

وكما قلت، لم نكن نريد لتعديل رمز قاعدة بالكامل لإضافة هذه الوظيفة، لذلك نحن الكتابة فوق bindParam() وbindValue() الطرق الافتراضية للكائن PDOStatement، هل لدينا التخزين المؤقت للبيانات المقيدة، ثم استدعاء parent::bindParam() أو الأم :: bindValue(). يسمح هذا قاعدتنا التعليمات البرمجية الموجودة على مواصلة العمل كالمعتاد.

وأخيرا، عندما يتم استدعاء أسلوب execute()، ونحن أداء الاستيفاء لدينا وتوفير السلسلة الناتجة كما E_PDOStatement->fullQuery الفندق الجديد. هذا يمكن أن يكون الناتج لعرض الاستعلام أو، على سبيل المثال، وكتب إلى ملف السجل.

والتمديد، جنبا إلى جنب مع تعليمات التركيب والتكوين، تتوفر على جيثب:

https://github.com/noahheck/E_PDOStatement

<القوي> شروط : في
من الواضح، وكما ذكرت، وأنا كتبت هذا التمديد. لأنه تم تطويره بمساعدة العديد من المواضيع هنا، وأنا أرغب في وظيفة لي الحل هنا في حالة أي شخص آخر يأتي عبر هذه المواضيع، تماما كما فعلت.

ويمكنك توسيع الطبقة 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());

والناتجة في

<اقتباس فقرة>   

ومستخدم الاختيار من بين المستخدمين حيث المستخدم = 'user_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 حرفًا من الاستعلام.

والخاصية $ سلسلة استعلام مواقع المعلومات المذكورة من المحتمل فقط العودة الاستعلام مرت في، دون المعلمات استبدال قيمها. في صافي، لدي الجزء المصيد من بلدي executer الاستعلام تفعل بحث بسيط يحل على المعلمات مع قيمهم التي تم توفيرها بحيث سجل خطأ يمكن أن تظهر القيم الفعلية التي كانت تستخدم للاستعلام. يجب أن تكون قادرا على تعداد المعلمات في PHP، واستبدال المعلمات مع القيمة المسندة إليها.

وذات الصلة إلى حد ما ... إذا كنت مجرد محاولة لتطهير متغير معين يمكنك استخدام شركة تنمية نفط عمان :: الاقتباس . على سبيل المثال، للبحث عن عدة شروط مثل جزئية إذا كنت عالقا مع إطار محدود مثل للكيك:

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

ولست بحاجة لتسجيل سلسلة الاستعلام كاملة بعد المعلمة ربط ذلك هذا هو قطعة في قانون بلدي. نأمل، فإنه من المفيد للقبعة الجميع لديه نفس القضية.

/**
 * 
 * @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, @ مايك و @ كريس غو, ، كلهم ​​يجيبون على هذا السؤال):

/**
 * 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

مايك الجواب تعمل جيدا حتى كنت تستخدم "إعادة استخدام" قيمة الربط.
على سبيل المثال:

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

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

و}

والأمل هناك من يرى أنه من المفيد.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top