2015-12-02 72 views
1

我正在尋找一種方法來動態更改MYSQLi調用中使用的變量數量。我在5年前偶然發現了PHP.net上的一篇非常有用的帖子(http://php.net/manual/en/mysqli-stmt.bind-param.php#100879)。然而,我然後在事情上有點瘋狂,並希望得到幫助,知道我的工作改編是否仍然安全/有效/充滿錯誤,我不夠聰明。簡化和使MYSQLi查詢動態的好解決方案?

的想法是五倍:

  1. 爲了允許動態地期望容易地使用盡可能多的變量。
  2. 使書寫查詢與舊MYSQL一樣簡單(同時仍利用MYSQLi準備語句的現代性和安全性)。
  3. 爲了否定需要手動添加類,而是讓PHP處理它。
  4. 爲了自動區分期望返回的查詢(SELECT和EXPLAIN)和那些沒有(INSERT,DELETE和UPDATE)的查詢。
  5. 提供一種通過更改單個變量來調試單個行或整個頁面的簡單方法。

所有這些都實現,我希望,像這樣的東西:

doMYSQL( 'INSERT INTO表(ID,姓名)VALUES($ ID,$名)');

請注意,如果您願意,在下面的函數中,查詢(包含變量內聯,如舊的MYSQL)被單引號包圍 - 變量被解析爲實際變量名稱,而不是其值。這些值只在準備MYSQLi準備語句的階段纔會發生(因此,據我所知,應該有與禁止令攻擊相同的安全性)。

現在,官方說明。我很樂意提供任何有關如何使這一點變得更好的反饋,或者是否有明顯的錯誤。最後一個註釋(「雜項代碼」)下的所有代碼都來自PHP.net的帖子,其中大部分我都不明白,所以對此的任何評論也會有所幫助。如果這個功能通過吧,它肯定會讓我的生活更輕鬆,所以希望其他人也可以使用它:)。

只是爲了澄清,這對我嘗試過的所有測試都有效,所以我沒有理由認爲有任何問題。我只是有足夠的經驗知道我沒有足夠的經驗知道是否有任何紅旗。因此,我向你們致敬,並要求協助驗證功能的安全性。

謝謝!

<?php 
/* 
doMYSQL($sql, $debug_local [optional]); 
$sql = Statement to execute; 
$debug_local = 'print' to show query on page but not run, 'both' to show it and run, leave blank for normal execution. 
(You can add a $debug variable at the top of the page to control all doMYSQL functions at once, though local ones take precedence. 
*/ 

function doMYSQL($sql, $debug_local = 'none') 
{ 
    $mysqli = new mysqli("localhost", "username", "password", "database"); 
    $print = $sql; // Save unaltered copy in case 'print' is enabled later 

    // Get debug settings (priority is user-set $debug_local, then global $debug, then default to 'none') 
    global $debug; 
    if (($debug == 'print' OR $debug == 'both') AND $debug_local == 'none'){$debug_local = $debug;} 

    // Create list of variables in the query 
    preg_match_all('/\$\w+/',$sql,$matches); 

    // For each variable found, find its value and add its kind and value to $params 
    $params = array(); 
    foreach ($matches[0] AS $match) 
    { 
     $match = substr($match,1); // Get rid of the now-unneccessary '$'' on the variable name 
     global $$match; // Get the global value for that variable 
     $kind = gettype($$match); // Get the kind for that variable 

     // Convert PHP kind to mysqli kind for bind_result 
     if ($kind == "integer"){$kind = 'i';} 
     if ($kind == "double"){$kind = 'd';} 
     if ($kind == "string"){$kind = 's';} 

     $params[0] .= $kind; // Adds to ongoing list of types in $param[0] 
     $params[] = $$match; // Adds to ongoing list of values in $params[1+] 
     $sql = str_replace("$"."$match", '?', $sql); // Switch variable with '?' in the query 
     $print = str_replace("$"."$match", $$match."[$kind]", $print); // Switch variable with '?' in the query  
    } 

    // If debug is print or both, print 
    if ($debug_local == "print" OR $debug_local == "both") 
    { 
    echo "MYSQLi Debug: $print<br>"; 
    } 


    // If debug is not 'print', run it 
    if ($debug_local != 'print') 
    { 
    // Get first word; if a select/explain, set $close to false; otherwise set to 'true.' If irregular query, error message. 
    $temp = explode(' ',trim($sql),2); 
    $firstword = strtolower($temp[0]); 
    if ($firstword == 'select' OR $firstword == 'explain'){$close=false;} 
    else if ($firstword == 'update' OR $firstword == 'delete' OR $firstword == 'insert'){$close=true;} 
    else {echo "Invalid first word on query $query!<br>";} 


    // Start misc code found on the PHP link 
    $stmt = $mysqli->prepare($sql) or die ("Failed to prepared the statement!"); 

    call_user_func_array(array($stmt, 'bind_param'), refValues($params)); 

    $stmt->execute(); 

    if($close){ 
     $result = $mysqli->affected_rows; 
    } else { 
     $meta = $stmt->result_metadata(); 

     while ($field = $meta->fetch_field()) { 
      $parameters[] = &$row[$field->name]; 
     } 

     call_user_func_array(array($stmt, 'bind_result'), refValues($parameters)); 

     while ($stmt->fetch()) { 
     $x = array(); 
     foreach($row as $key => $val) { 
      $x[$key] = $val; 
     } 
     $results[] = $x; 
     } 

     $result = $results; 
    } 

    $stmt->close(); 
    $mysqli->close(); 

    return $result; 
    } 
} 

function refValues($arr) 
{ 
    if (strnatcmp(phpversion(),'5.3') >= 0) //Reference is required for PHP 5.3+ 
    { 
     $refs = array(); 
     foreach($arr as $key => $value) 
      $refs[$key] = &$arr[$key]; 
     return $refs; 
    } 
    return $arr; 
} 

Examples (generic): 

doMYSQL('SELECT * FROM table WHERE id = $id'); 
doMYSQL('SELECT * FROM table'); 
doMYSQL('INSERT INTO table(id, name) VALUES ($id,$name)'); 


Examples (with data): 
$user = 1; 
$location = 'California'; 

$result = doMYSQL('SELECT * FROM watchlists_locations WHERE user = $user AND location = $location'); 
print_r($result); 

doMYSQL('INSERT INTO watchlists_locations(user, location) VALUES ($user,"1000")'); 
?> 

回答

-1

呵呵我得到了你要去的,但它並沒有這麼複雜:)

如果你想使用的mysqli我只想只使用雙引號,並通過發送您的SQL比如「SELECT * FROM table WHERE id = $ id」。任何來自用戶輸入的內容都會首先通過mysqli_real_escape_string()運行。

至於根據查詢類型返回適當的響應,這裏是我使用的功能的一個縮減版本。

function query($sql) { 

    $arr = explode(' ',trim($sql)); 
    $command = strtolower($arr[0]); 

    switch ($command) { 
     case 'call': 
     case 'select': 
      // run query and return results 
     break; 
     case 'insert': 
     case 'replace': 
      // run query, then return insert_id 
     break; 
     case 'update': 
     case 'delete': 
      // run query and return resulting integer (rows affected) 
     break; 
    } 

} 

雖然如果你想安全快速地綁定變量,我會報廢'mysqli',並採用PDO方法。

$result = query("SELECT * FROM table WHERE id = :id", [':id' => $id]); 

function query($sql, $params) { 

    $db = new PDO('mysql:database=yourdb;host=127.0.0.1', 'user', 'password'); 
    $stmt = $db->prepare($sql); 

    $arr = explode(' ',trim($sql)); 
    $command = strtolower($arr[0]); 

    switch ($command) { 
     case 'call': 
     case 'select': 
      // run query and return results 
      $stmt->execute($params); 
      return $stmt->fetchAll(); 
     break; 
     case 'insert': 
     case 'replace': 
      // run query, then return insert_id 
      return $stmt->execute($params); 
     break; 
     case 'update': 
     case 'delete': 
      // run query and return resulting integer (rows affected) 
      return $stmt->execute($params); 
     break; 
    } 

} 
+0

謝謝你的職位 - 我聽說準備好的發言不僅僅是依靠mysqli_real_escape_string更安全()(以及該函數訪問數據庫,無論如何,所以沒有真正的速度/帶寬優勢)。否則,我可以簡化爲幾個獨立的部分,但我喜歡將所有這些功能都集中在一個屋檐下,只要它能夠工作:)。 我也看過PDO。在這一點上,我只是試圖避免學習一個新的系統,如果我可以讓mysqli爲我所需要的工作。如果我的大功能起作用,現在看起來我基本上可以編寫普通的MYSQL查詢。我很懶:P –