Pobieranie surowego ciągu zapytania SQL z instrukcji przygotowanych w PDO

133

Czy istnieje sposób na wykonanie surowego ciągu SQL podczas wywoływania PDOStatement :: execute () na przygotowanej instrukcji? Byłoby to niezwykle przydatne do celów debugowania.

Wilco
źródło
1
Dla PHP> = 5.1, spójrz na php.net/manual/en/pdostatement.debugdumpparams.php
Mawg mówi, że przywróć Monikę
1
Sprawdź jednowierszową funkcję pdo-debug .
Sliq,
Najczystszym sposobem, jaki znalazłem, jest biblioteka E_PDOStatement . Po prostu to robisz $stmt = $pdo->prepare($query); /* ... */ echo $stmt->fullQuery;. Działa poprzez rozszerzenie klasy PDOStatement , dzięki czemu jest tak elegancki, jak pozwala na to PDO API.
ComFreek

Odpowiedzi:

113

Zakładam, że masz na myśli, że chcesz otrzymać końcowe zapytanie SQL z interpolowanymi wartościami parametrów. Rozumiem, że byłoby to przydatne do debugowania, ale nie jest to sposób, w jaki działają przygotowane instrukcje. Parametry nie są łączone z przygotowaną instrukcją po stronie klienta, więc PDO nigdy nie powinno mieć dostępu do ciągu zapytania w połączeniu z jego parametrami.

Instrukcja SQL jest wysyłana do serwera bazy danych podczas przygotowywania (), a parametry są wysyłane osobno, gdy wykonujesz wykonywanie (). Ogólny dziennik zapytań MySQL pokazuje ostateczny kod SQL z wartościami interpolowanymi po wykonaniu (). Poniżej znajduje się fragment mojego ogólnego dziennika zapytań. Uruchomiłem zapytania z mysql CLI, a nie z PDO, ale zasada jest taka sama.

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

Możesz również uzyskać to, co chcesz, jeśli ustawisz atrybut PDO PDO :: ATTR_EMULATE_PREPARES. W tym trybie PDO interpoluje parametry do zapytania SQL i wysyła całe zapytanie podczas wykonywania (). To nie jest prawdziwie przygotowane zapytanie. Możesz obejść korzyści płynące z przygotowanych zapytań, interpolując zmienne do ciągu SQL przed wykonaniem execute ().


Komentarz od @afilina:

Nie, tekstowe zapytanie SQL nie jest łączone z parametrami podczas wykonywania. Więc PDO nie ma nic do pokazania.

Wewnętrznie, jeśli używasz PDO :: ATTR_EMULATE_PREPARES, PDO tworzy kopię zapytania SQL i interpoluje do niej wartości parametrów przed przygotowaniem i wykonaniem. Ale PDO nie ujawnia tego zmodyfikowanego zapytania SQL.

Obiekt PDOStatement ma właściwość $ queryString, ale jest ona ustawiana tylko w konstruktorze dla PDOStatement i nie jest aktualizowana, gdy zapytanie jest przepisywane z parametrami.

Byłoby rozsądnym żądaniem funkcji dla PDO, aby poprosić ich o ujawnienie przepisanego zapytania. Ale nawet to nie dałoby „kompletnego” zapytania, chyba że użyjesz PDO :: ATTR_EMULATE_PREPARES.

Dlatego powyżej pokazuję obejście polegające na używaniu ogólnego dziennika zapytań serwera MySQL, ponieważ w tym przypadku nawet przygotowane zapytanie z symbolami zastępczymi parametrów jest przepisywane na serwerze, z wartościami parametrów wpisywanymi do ciągu zapytania. Ale dzieje się to tylko podczas logowania, a nie podczas wykonywania zapytania.

Bill Karwin
źródło
10
Jak uzyskać zapytanie o dziurę, gdy PDO :: ATTR_EMULATE_PREPARES ma wartość TRUE?
Yasen Zhelev
2
@Yasen Zhelev: Jeśli PDO emuluje przygotowanie, to interpoluje wartości parametrów do zapytania przed przygotowaniem zapytania. Dlatego MySQL nigdy nie widzi wersji zapytania z symbolami zastępczymi parametrów. MySQL rejestruje tylko pełne zapytanie.
Bill Karwin
2
@ Bill: „Parametry nie są łączone z przygotowaną instrukcją po stronie klienta” - czekaj - ale czy łączą się po stronie serwera? Albo jak mysql wstawia wartości do bazy danych?
Stann,
1
@afilina, nie, nie możesz. Zobacz moje wyjaśnienie powyżej.
Bill Karwin
3
Wow, głos przeciw? Proszę, nie strzelaj do posłańca. Po prostu opisuję, jak to działa.
Bill Karwin,
108
/**
 * 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;
}
bigwebguy
źródło
6
dlaczego nie po prostu użyć strtr(): szybsze, prostsze, takie same wyniki. strtr($query, $params);
Tony Chiboucas
Jaki jest pożytek z tego?
Chciałem tylko wpaść i podziękować, ale byłem poza całą dodatkową klasą za to, które teraz usunąłem na korzyść tego, ponieważ jest malutkie i genialne :). Tak cholernie przydatne do debbugowania wszystkich zapytań, które aplikacja wykonuje na każdej stronie, logując je: D
NaughtySquid
1
Widziałem tę funkcję i bardzo mnie to ucieszyło, chociaż czegoś nie rozumiem, dlaczego sprawdzasz, czy $keyjest a stringa nie $value? Czy coś mi brakuje? Pytam o to z powodu tego wyjścia, drugi parametr nie jest postrzegany jako ciąg:string(115) "INSERT INTO tokens (token_type, token_hash, user_id) VALUES ('resetpassword', hzFs5RLMpKwTeShTjP9AkTA2jtxXls86, 1);"
Kerwin Sneijders
2
To dobry początek, ale kończy się niepowodzeniem, jeśli sama wartość parametru $ param zawiera znak zapytania („?”).
Chickenchilli
32

Zmodyfikowałem metodę tak, aby obejmowała obsługę danych wyjściowych tablic dla instrukcji typu WHERE IN (?).

AKTUALIZACJA: Właśnie dodano sprawdzanie wartości NULL i zduplikowane parametry $, aby rzeczywiste wartości parametrów $ param nie były modyfikowane.

Świetna robota bigwebguy i dzięki!

/**
 * 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;
}
Mikrofon
źródło
2
Myślę, że musisz to zrobić $values = $params;zamiast $values = array().
testowanie
Kolejnym małym kawałkiem, którego tutaj brakuje, są struny. Aby je uchwycić, umieść to nad is_arrayczekiem:if (is_string($value)) $values[$key] = "'" . $value . "'";
treeface
Jest to ograniczona wartość powiązania do tylko raz w preg_replace. dodaj tę linię po $values = $params; $values_limit = []; $words_repeated = array_count_values(str_word_count($sql, 1, ':_')); dodaniu tego najpierw w środku, jeśli w foreach $values_limit[$key] = (isset($words_repeated[':'.$key]) ? intval($words_repeated[':'.$key]) : 1);i to w pierwszej kolejności w foreach $values_limit = [];użyj ponownie foreach pętli $ wartości do preg_replace zisset($values_limit[$key])
vee
na przykład pętla $ wartości. 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
To pęka w każdym miejscu.
Joel Mellon
12

Pewnie trochę późno, ale teraz jest PDOStatement::debugDumpParams

Zrzuca informacje zawarte w przygotowanej instrukcji bezpośrednio na wyjściu. Poda używane zapytanie SQL, liczbę użytych parametrów (Params), listę parametrów wraz z ich nazwą, typem (paramtype) jako liczbę całkowitą, ich nazwę lub pozycję klucza oraz pozycję w zapytaniu (jeśli to jest obsługiwany przez sterownik PDO, w przeciwnym razie będzie to -1).

Możesz znaleźć więcej w oficjalnych dokumentach php

Przykład:

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

?>
Jimmy Kane
źródło
i dla lepszej czytelności:echo '<pre>'; $sth->debugDumpParams(); echo '</pre>';
SandroMarques
10

Dodał trochę więcej do kodu przez Mike'a - przejdź wartościami, aby dodać pojedyncze cudzysłowy

/**
 * 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;
}
Chris Go
źródło
1
Bardzo użytecznie, zrobiłem kilka modyfikacji, aby zastąpić bindParam funkcję PDOStatement klasy i zatwierdź, jeśli wartość jest ciągiem znaków lub liczba całkowita z PDO: params wartości.
Sergio Flores
1
gdzie możemy to zobaczyć?
Mawg mówi, że przywróć Monikę
10

Rozwiązaniem jest dobrowolne umieszczenie błędu w zapytaniu i wydrukowanie komunikatu o błędzie:

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

Wyjście standardowe:

SQLSTATE [42000]: Błąd składni lub naruszenie dostępu: [...] w pobliżu „ELECT * FROM Person WHERE age = 18” w wierszu 1

Należy zauważyć, że wypisuje tylko pierwsze 80 znaków zapytania.

JacopoStanchi
źródło
Nie wiem, dlaczego to zostało odrzucone. To jest proste i działa. Działa szybko. Znacznie szybsze niż włączenie logowania, wyszukanie odpowiedniego wiersza w dzienniku, następnie wyłączenie dziennika, a następnie wyczyszczenie plików dziennika.
Bojan Hrnkas
@BojanHrnkas długość próbki błędu jest bardzo ograniczona. W przypadku tak prostego zapytania łatwiej jest ręcznie zastąpić symbol zastępczy zmienną. Ta metoda działa tylko wtedy, gdy włączysz emulację.
Twój zdrowy rozsądek
8

PDOStatement ma publiczną właściwość $ queryString. Powinno być tym, czego chcesz.

Właśnie zauważyłem, że PDOStatement ma nieudokumentowaną metodę debugDumpParams (), której możesz również chcieć się przyjrzeć.

Szklany robot
źródło
1
W debugDumpParams nie udokumentowano php.net/manual/en/pdostatement.debugdumpparams.php
mloskot
Nie. $ queryString nie pokazuje dołączonych wartości parametrów.
Andreas
5

Spędziłem dużo czasu na badaniu tej sytuacji na własne potrzeby. Ten i kilka innych wątków SO bardzo mi pomogło, więc chciałem się podzielić tym, co wymyśliłem.

Chociaż dostęp do interpolowanego ciągu zapytania jest znaczącą korzyścią podczas rozwiązywania problemów, chcieliśmy mieć możliwość prowadzenia dziennika tylko niektórych zapytań (dlatego korzystanie z dzienników bazy danych do tego celu nie było idealne). Chcieliśmy również mieć możliwość wykorzystania dzienników do odtworzenia stanu tabel w dowolnym momencie, dlatego musieliśmy upewnić się, że interpolowane ciągi znaków są prawidłowo usuwane. Na koniec chcieliśmy rozszerzyć tę funkcjonalność na całą naszą bazę kodu, aby ponownie napisać jak najmniejszą jej część (terminy, marketing itp., Wiesz, jak to jest).

Moim rozwiązaniem było rozszerzenie funkcjonalności domyślnego obiektu PDOStatement w celu buforowania sparametryzowanych wartości (lub referencji), a po wykonaniu instrukcji użyć funkcjonalności obiektu PDO, aby odpowiednio uciec od parametrów, gdy są one ponownie wstrzykiwane do zapytania strunowy. Moglibyśmy wtedy powiązać się z wykonaniem metody obiektu instrukcji i zarejestrować rzeczywiste zapytanie, które zostało wykonane w tym czasie ( lub przynajmniej tak wiernie odtworzyć, jak to możliwe) .

Jak powiedziałem, nie chcieliśmy modyfikować całej bazy kodu w celu dodania tej funkcjonalności, więc nadpisujemy wartości domyślne bindParam()i bindValue()metody obiektu PDOStatement, buforujemy powiązane dane, a następnie wywołujemy parent::bindParam()lub parent :: bindValue(). Pozwoliło to naszej istniejącej bazie kodu nadal funkcjonować normalnie.

Na koniec, gdy execute()metoda jest wywoływana, wykonujemy naszą interpolację i udostępniamy wynikowy ciąg jako nową właściwość E_PDOStatement->fullQuery. Może to zostać wyprowadzone w celu wyświetlenia zapytania lub, na przykład, zapisane w pliku dziennika.

Rozszerzenie wraz z instrukcją instalacji i konfiguracji jest dostępne na github:

https://github.com/noahheck/E_PDOStatement

ZRZECZENIE SIĘ :
Oczywiście, jak wspomniałem, napisałem to rozszerzenie. Ponieważ został opracowany przy pomocy wielu wątków tutaj, chciałem zamieścić tutaj swoje rozwiązanie na wypadek, gdyby ktoś inny natknął się na te wątki, tak jak ja.

Noah Heck
źródło
Dzięki za udostępnienie. Nie głosuj za, ponieważ zbyt długa odpowiedź ze zbyt małą ilością kodu
T30
5

Możesz rozszerzyć klasę PDOStatement, aby przechwytywać ograniczone zmienne i przechowywać je do późniejszego wykorzystania. Następnie można dodać 2 metody, jedną do czyszczenia zmiennych (debugBindedVariables) i drugą do drukowania zapytania z tymi zmiennymi (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);
  }
}

Następnie możesz użyć tej odziedziczonej klasy do celów debugowania.

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

W rezultacie

WYBIERZ użytkownika Z użytkowników WHERE user = 'user_test'

Tablica ([: test] => test_użytkownika)

Otamay
źródło
2

Wiem, że to pytanie jest trochę stare, ale używam tego kodu od dawna (użyłem odpowiedzi z @ chris-go), a teraz ten kod jest przestarzały w PHP 7.2

Opublikuję zaktualizowaną wersję tego kodu (Kredyt za główny kod pochodzi z @bigwebguy , @mike i @ chris-go , wszystkie odpowiedzi na to pytanie):

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

Zwróć uwagę, że zmiany w kodzie dotyczą funkcji array_walk (), zastępując funkcję create_function przez funkcję anonimową. To sprawia, że ​​ten dobry fragment kodu jest funkcjonalny i kompatybilny z PHP 7.2 (i mam nadzieję, że także w przyszłych wersjach).

Sakura Kinomoto
źródło
1

Wspomniana właściwość $ queryString prawdopodobnie zwróci tylko przekazane zapytanie, bez zastąpienia parametrów ich wartościami. W .Net mam część catch mojego modułu wykonawczego zapytań, która wykonuje proste wyszukiwanie, zastępując parametry ich wartościami, które zostały dostarczone, tak aby dziennik błędów mógł pokazać rzeczywiste wartości, które były używane w zapytaniu. Powinieneś być w stanie wyliczyć parametry w PHP i zastąpić je przypisaną wartością.

Kibbee
źródło
1

Możesz użyć sprintf(str_replace('?', '"%s"', $sql), ...$params);

Oto przykład:

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

Zauważ, że działa to tylko dla PHP> = 5.6

kurdtpage
źródło
-1

W pewnym sensie ... jeśli próbujesz tylko wyczyścić określoną zmienną, możesz użyć PDO :: quote . Na przykład, aby wyszukać wiele częściowych warunków LIKE, jeśli utkniesz w ograniczonej strukturze, takiej jak CakePHP:

$pdo = $this->getDataSource()->getConnection();
$results = $this->find('all', array(
    'conditions' => array(
        'Model.name LIKE ' . $pdo->quote("%{$keyword1}%"),
        'Model.name LIKE ' . $pdo->quote("%{$keyword2}%"),
    ),
);
Synexis
źródło
-1

Odpowiedź Mike'a działa dobrze, dopóki nie użyjesz wartości wiązania „ponownego użycia”.
Na przykład:

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)

Odpowiedź Mike'a może zastąpić tylko pierwszą: szukaj, ale nie drugą.
Dlatego przepisuję jego odpowiedź, aby działała z wieloma parametrami, które można ponownie prawidłowo wykorzystać.

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;
}
vee
źródło
-1

preg_replace nie działało dla mnie, a kiedy binding_ miał ponad 9, binding_1 i binding_10 zostały zastąpione przez str_replace (pozostawiając 0 z tyłu), więc wykonałem zamiany wstecz:

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;

}

Mam nadzieję, że ktoś uzna to za przydatne.

Markos F.
źródło
-1

Muszę rejestrować pełny ciąg zapytania po parametrze wiązania, więc jest to fragment mojego kodu. Mam nadzieję, że przyda się każdemu kapelusz ma ten sam problem.

/**
 * 
 * @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);
}
ducminh1903
źródło