webentwicklung-frage-antwort-db.com.de

Verwenden Sie den gebundenen Parameter mehrmals

Ich versuche, eine ziemlich einfache Suchmaschine für meine Datenbank zu implementieren, in der der Benutzer verschiedene Arten von Informationen enthalten kann. Die Suche selbst besteht aus mehreren Union-Selects, bei denen die Ergebnisse immer in 3 Spalten zusammengeführt werden.

Die zurückgegebenen Daten werden jedoch aus verschiedenen Tabellen abgerufen.

Jede Abfrage verwendet $ term für das Matchmaking, und ich habe es als vorbereiteten Parameter an ": term" gebunden.

Nun heißt es im Handbuch:

Sie müssen einen eindeutigen Parametermarker für jeden Wert angeben, den Sie beim Aufruf von PDOStatement :: execute () an die Anweisung übergeben möchten. Sie können eine benannte Parametermarkierung mit demselben Namen in einer vorbereiteten Anweisung nicht zweimal verwenden.

Ich dachte mir, dass anstelle von: term Parameter durch: termX (x für term = n ++) eine bessere Lösung gefunden werden muss?

Oder muss ich nur die X-Nummer von: TermX binden?

Bearbeiten Meine Lösung dazu posten:

$query = "SELECT ... FROM table WHERE name LIKE :term OR number LIKE :term";

$term = "hello world";
$termX = 0;
$query = preg_replace_callback("/\:term/", function ($matches) use (&$termX) { $termX++; return $matches[0] . ($termX - 1); }, $query);

$pdo->prepare($query);

for ($i = 0; $i < $termX; $i++)
    $pdo->bindValue(":term$i", "%$term%", PDO::PARAM_STR);

Okay, hier ist ein Beispiel. Ich habe keine Zeit für sqlfiddle, aber ich werde später eine hinzufügen, wenn es notwendig ist.

(
    SELECT
        t1.`name` AS resultText
    FROM table1 AS t1
    WHERE
        t1.parent = :userID
        AND
        (
            t1.`name` LIKE :term
            OR
            t1.`number` LIKE :term
            AND
            t1.`status` = :flagStatus
        )
)
UNION
(
    SELECT
        t2.`name` AS resultText
    FROM table2 AS t2
    WHERE
        t2.parent = :userParentID
        AND
        (
            t2.`name` LIKE :term
            OR
            t2.`ticket` LIKE :term
            AND
            t1.`state` = :flagTicket
        )
)
26
Daniel

Ich bin jetzt ein paar Mal über dasselbe Problem gegangen und ich glaube, ich habe eine ziemlich einfache und gute Lösung gefunden. Für den Fall, dass ich Parameter mehrfach verwenden möchte, speichere ich sie einfach in einem MySQL User-Defined Variable.
Dies macht den Code viel lesbarer und Sie benötigen keine zusätzlichen Funktionen in PHP:

$sql = "SET @term = :term";

try
{
    $stmt = $dbh->prepare($sql);
    $stmt->bindValue(":term", "%$term%", PDO::PARAM_STR);
    $stmt->execute();
}
catch(PDOException $e)
{
    // error handling
}


$sql = "SELECT ... FROM table WHERE name LIKE @term OR number LIKE @term";

try
{
    $stmt = $dbh->prepare($sql);
    $stmt->execute();
    $stmt->fetchAll();
}
catch(PDOException $e)
{
    //error handling
}

Der einzige Nachteil ist, dass Sie eine zusätzliche MySQL-Abfrage durchführen müssen - aber imho lohnt sich das absolut.
Da User-Defined Variables in MySQL sitzungsgebunden ist, müssen Sie sich auch keine Sorgen machen, dass die Variable @term in Mehrbenutzerumgebungen Nebenwirkungen verursacht. 

15
low_rents

Ich habe zwei Funktionen erstellt, um das Problem zu lösen, indem doppelt verwendete Begriffe umbenannt wurden. Eine zum Umbenennen der SQL und eine zum Umbenennen der Bindungen.

    /**
     * Changes double bindings to seperate ones appended with numbers in bindings array
     * example: :term will become :term_1, :term_2, .. when used multiple times.
     *
     * @param string $pstrSql
     * @param array $paBindings
     * @return array
     */
    private function prepareParamtersForMultipleBindings($pstrSql, array $paBindings = array())
    {
        foreach($paBindings as $lstrBinding => $lmValue)
        {
            // $lnTermCount= substr_count($pstrSql, ':'.$lstrBinding);
            preg_match_all("/:".$lstrBinding."\b/", $pstrSql, $laMatches);

            $lnTermCount= (isset($laMatches[0])) ? count($laMatches[0]) : 0;

            if($lnTermCount > 1)
            {
                for($lnIndex = 1; $lnIndex <= $lnTermCount; $lnIndex++)
                {
                    $paBindings[$lstrBinding.'_'.$lnIndex] = $lmValue;
                }

                unset($paBindings[$lstrBinding]);
            }
        }

        return $paBindings;
    }

    /**
     * Changes double bindings to seperate ones appended with numbers in SQL string
     * example: :term will become :term_1, :term_2, .. when used multiple times.
     *
     * @param string $pstrSql
     * @param array $paBindings
     * @return string
     */
    private function prepareSqlForMultipleBindings($pstrSql, array $paBindings = array())
    {
        foreach($paBindings as $lstrBinding => $lmValue)
        {
            // $lnTermCount= substr_count($pstrSql, ':'.$lstrBinding);
            preg_match_all("/:".$lstrBinding."\b/", $pstrSql, $laMatches);

            $lnTermCount= (isset($laMatches[0])) ? count($laMatches[0]) : 0;

            if($lnTermCount > 1)
            {
                $lnCount= 0;
                $pstrSql= preg_replace_callback('(:'.$lstrBinding.'\b)', function($paMatches) use (&$lnCount) {
                    $lnCount++;
                    return sprintf("%s_%d", $paMatches[0], $lnCount);
                } , $pstrSql, $lnLimit = -1, $lnCount);
            }
        }

        return $pstrSql;
    }

Verwendungsbeispiel:

$lstrSqlQuery= $this->prepareSqlForMultipleBindings($pstrSqlQuery, $paParameters);
$laParameters= $this->prepareParamtersForMultipleBindings($pstrSqlQuery, $paParameters);
$this->prepare($lstrSqlQuery)->execute($laParameters);

Erklärung zur Variablenbenennung:
p: Parameter, l: lokal in Funktion
str: string, n: numerisch, a: array, m: mixed

9
pascalvgemert

Ich weiß nicht, ob sich das seit der Veröffentlichung der Frage geändert hat, aber jetzt im Handbuch nachlesen:

Sie können eine benannte Parametermarkierung mit demselben Namen in einer vorbereiteten Anweisung nur einmal verwenden.sofern der Emulationsmodus nicht aktiviert ist.

http://php.net/manual/de/pdo.prepare.php - (Hervorhebungsmine.)

Technisch gesehen ist es also möglich, emulierte Vorbereitungen mit $PDO_obj->setAttribute( PDO::ATTR_EMULATE_PREPARES, true ); zuzulassen. Auch wenn dies keine gute Idee ist (wie in diese Antwort diskutiert -, ist das Deaktivieren emulierter vorbereiteter Anweisungen eine Möglichkeit, sich vor bestimmten Injektionsangriffen zu schützen; obwohl einige haben das Gegenteil geschrieben dass es keinen Unterschied macht zur Sicherheit, ob Vorbereitungen nachgeahmt werden oder nicht (Ich weiß es nicht, aber ich glaube nicht, dass letzterer den oben genannten Angriff im Sinn hatte.) 

Ich füge diese Antwort der Vollständigkeit halber hinzu; Als ich emulate_prepares auf der Website, an der ich gerade arbeite, abschaltete, führte dies dazu, dass die Suche abgebrochen wurde, da eine ähnliche Abfrage verwendet wurde (SELECT ... FROM tbl WHERE (Field1 LIKE :term OR Field2 LIKE :term) ...). Die Funktion funktionierte einwandfrei, bis ich PDO::ATTR_EMULATE_PREPARES explizit auf false setzte .

(PHP 5.4.38, MySQL 5.1.73 FWIW) 

Diese Frage ist, was mich ablenkte, dass Sie einen benannten Parameter nicht zweimal in derselben Abfrage verwenden können (was für mich nicht intuitiv erscheint, aber na ja). (Irgendwie habe ich das im Handbuch vermisst, obwohl ich diese Seite oft angesehen habe.)

5

Eine funktionierende Lösung:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE);
$query = "SELECT * FROM table WHERE name LIKE :term OR number LIKE :term";
$term  = "hello world";
$stmt  = $pdo->prepare($query);
$stmt->execute(array('term' => "%$term%"));
$data  = $stmt->fetchAll();
2

Benutzerdefinierte Variablen sind eine Möglichkeit, dieselbe Variable mehrmals zu verwenden, um Werte an die Abfragen zu binden. Ja, das funktioniert gut.

//Setting this doesn't work at all, I tested it myself 
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE);

Ich wollte keine benutzerdefinierten Variablen verwenden überhaupt wie eine der hier veröffentlichten Lösungen. Ich wollte auch nicht umbenennen wie die andere hier veröffentlichte Lösung. Daher ist es meine Lösung, die ohne Verwendung benutzerdefinierter Variablen und ohne Umbenennen von Objekten in Ihrer Abfrage mit weniger Code funktioniert. Dabei ist es egal, wie oft der Parameter in der Abfrage verwendet wird. Ich benutze dies für alle meine Projekte und es funktioniert gut.

//Example values
var $query = "select * from test_table where param_name_1 = :parameter and param_name_2 = :parameter";
var param_name = ":parameter";
var param_value = "value";

//Wrap these lines of codes in a function as needed sending 3 params $query, $param_name and $param_value. 
//You can also use an array as I do!

//Lets check if the param is defined in the query
if (strpos($query, $param_name) !== false)
{
    //Get the number of times the param appears in the query
    $ocurrences = substr_count($query, $param_name);
    //Loop the number of times the param is defined and bind the param value as many times needed
    for ($i = 0; $i < $ocurrences; $i++) 
    {
        //Let's bind the value to the param
        $statement->bindValue($param_name, $param_value);
    }
}

Und hier ist eine einfache Arbeitslösung!

Hoffe das hilft jemandem in naher Zukunft.

0
revobtz