0

我正在寻找一种方法来动态更改 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 帖子,其中大部分我不明白,因此对此的任何评论也会有所帮助。如果这个功能确实通过了门槛,它肯定会让我的生活更轻松,所以希望其他人也能找到它的用途:)。

澄清一下,这对我尝试过的所有测试都有效,所以我没有理由认为有什么问题。我只是有足够的经验知道我没有足够的经验知道是否有任何危险信号。因此,我向你们所有人致敬,并在验证该功能的安全性方面寻求帮助。

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

1 回答 1

-2

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

如果您想使用 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;
    }

}  
于 2015-12-02T06:15:07.983 回答