【问题标题】:Good solution for simplifying and making MYSQLi queries dynamic?简化和使 MYSQLi 查询动态化的好解决方案?
【发布时间】:2015-12-02 05:43:57
【问题描述】:

我一直在寻找一种方法来动态更改 MYSQLi 调用中使用的变量数量。 5 年前,我在 PHP.net 上偶然发现了一篇非常有用的帖子 (http://php.net/manual/en/mysqli-stmt.bind-param.php#100879)。然而,我后来有点疯狂,想知道我对他的作品的改编是否仍然安全/高效/充满错误,我不够聪明,无法看到。

这个想法有五个方面:

  1. 允许轻松动态地使用任意数量的变量。
  2. 让编写查询像旧的 MYSQL 一样简单(同时仍然利用 MYSQLi 准备好的语句的现代性和安全性)。
  3. 无需手动添加类,而是让 PHP 处理。
  4. 自动区分期望返回的查询(SELECT 和 EXPLAIN)和不期望返回的查询(INSERT、DELETE 和 UPDATE)。
  5. 提供一种通过更改单个变量来调试单个行或整个页面的简便方法。

我希望所有这些都可以通过以下方式完成:

doMYSQL('INSERT INTO table(id, name) VALUES ($id,$name)');

请注意,如果您愿意,在下面的函数中,查询(内联变量,如旧的 MYSQL)用单引号括起来 - 变量被解析为实际变量名称,而不是其值。这些值仅在准备 MYSQLi 准备语句的阶段发生一次(因此,据我所知,应该有相同的安全性来防止禁令攻击)。

现在,官方说明。我很想得到任何关于如何使它变得更好的反馈,或者某个地方是否存在明显的错误。最后一个注释下面的所有代码(“杂项代码”)都来自 PHP.net 帖子,其中大部分我不明白,所以任何关于它的 cmets 也会有所帮助。如果这个功能确实通过了门槛,它肯定会让我的生活更轻松,所以希望其他人也能找到它的用途:)。

澄清一下,这在我尝试过的所有测试中都有效,所以我没有理由认为有什么问题。我只是有足够的经验知道我没有足够的经验知道是否有任何危险信号。因此,我向大家致敬,并请求帮助验证函数的安全性。

<?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")');
?>

【问题讨论】:

    标签: php mysqli prepared-statement bindparam


    【解决方案1】:

    呵呵,我明白你想要什么,但它不必那么复杂:)

    如果您想使用 mysqli,我只需使用双引号并通过“SELECT * FROM table WHERE id = $id”发送您的 SQL。来自用户输入的任何内容都首先通过 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;
        }
    
    }  
    

    【讨论】:

    • 感谢您的帖子 - 我听说准备好的语句比仅依赖 mysqli_real_escape_string() 更安全(并且该函数无论如何都会命中数据库,因此没有真正的速度/带宽优势)。否则,我可以将其简化为几个单独的部分,但我喜欢将所有这些功能集中在一个屋檐下,只要它有效 :)。我也看过PDO。在这一点上,我只是想避免学习一个新系统,如果我能让 mysqli 为我需要的东西工作。如果我的大功能有效,现在看来我基本上可以编写正常的 MYSQL 查询了。我很懒:P
    猜你喜欢
    • 2021-10-31
    • 1970-01-01
    • 2020-02-02
    • 1970-01-01
    • 2022-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多