2011-09-29 140 views
18

在編寫pdo語句時,是否可以重複變量的值?我的意思是:php pdo準備重複變量

$query = "UPDATE users SET firstname = :name WHERE firstname = :name"; 
$stmt = $dbh -> prepare($query); 
$stmt -> execute(array(":name" => "Jackie")); 

請注意,我重複「:name」姓名持有者,而我只提供一次該值。我該如何做這項工作?

回答

19

簡單的答案是:你不能。 PDO對準備好的語句使用抽象,這有一些限制。不幸的是,這是一個,你要努力,各地使用類似

$query = "UPDATE users SET firstname = :name1 WHERE firstname = :name2"; 
$stmt = $dbh -> prepare($query); 
$stmt -> execute(array(":name1" => "Jackie", ":name2" => "Jackie")); 

在某些情況下,如與PDO/MySQL驅動程序的某些版本的模擬預處理語句,重複的命名參數的支持;然而,這不應該依賴,因爲它很脆弱(例如,它可以使升級需要更多的工作)。

如果您希望支持命名參數的多次出現,您可以始終使用extend PDO and PDOStatement(通過經典繼承或組合),也可以只使用PDOStatement並通過設置PDO::ATTR_STATEMENT_CLASS屬性將您的類設置爲語句類。擴展的PDOStatement(或PDO::prepare)可以提取命名參數,查找重複項並自動生成替換項。它也會記錄這些重複項。 bind和execute方法在傳遞一個命名參數時會測試參數是否重複並將該值綁定到每個替換參數。

注意:以下示例未經測試,可能有錯誤(與代碼註釋中記錄的某些與語句解析相關的錯誤)。

class PDO_multiNamed extends PDO { 
    function prepare($stmt) { 
     $params = array_count_values($this->_extractNamedParams()); 
     # get just named parameters that are repeated 
     $repeated = array_filter($params, function ($count) { return $count > 1; }); 
     # start suffixes at 0 
     $suffixes = array_map(function ($x) {return 0;}, $repeated); 
     /* Replace repeated named parameters. Doesn't properly parse statement, 
     * so may replacement portions of the string that it shouldn't. Proper 
     * implementation left as an exercise for the reader. 
     * 
     * $param only contains identifier characters, so no need to escape it 
     */ 
     $stmt = preg_replace_callback(
      '/(?:' . implode('|', array_keys($repeated)) . ')(?=\W)/', 
      function ($matches) use (&$suffixes) { 
       return $matches[0] . '_' . $suffixes[$matches[0]]++; 
      }, $stmt); 
     $this->prepare($stmt, 
         array(
          PDO::ATTR_STATEMENT_CLASS => array('PDOStatement_multiNamed', array($repeated))) 
      ); 
    } 

    protected function _extractNamedParams() { 
     /* Not actually sufficient to parse named parameters, but it's a start. 
     * Proper implementation left as an exercise. 
     */ 
     preg_match_all('/:\w+/', $stmt, $params); 
     return $params[0]; 
    } 
} 

class PDOStatement_multiNamed extends PDOStatement { 
    protected $_namedRepeats; 

    function __construct($repeated) { 
     # PDOStatement::__construct doesn't like to be called. 
     //parent::__construct(); 
     $this->_namedRepeats = $repeated; 
    } 

    /* 0 may not be an appropriate default for $length, but an examination of 
    * ext/pdo/pdo_stmt.c suggests it should work. Alternatively, leave off the 
    * last two arguments and rely on PHP's implicit variadic function feature. 
    */ 
    function bindParam($param, &$var, $data_type=PDO::PARAM_STR, $length=0, $driver_options=array()) { 
     return $this->_bind(__FUNCTION__, $param, func_get_args()); 
    } 

    function bindValue($param, $var, $data_type=PDO::PARAM_STR) { 
     return $this->_bind(__FUNCTION__, $param, func_get_args()); 
    } 

    function execute($input_parameters=NULL) { 
     if ($input_parameters) { 
      $params = array(); 
      # could be replaced by array_map_concat, if it existed 
      foreach ($input_parameters as $name => $val) { 
       if (isset($this->_namedRepeats[$param])) { 
        for ($i=0; $i < $this->_namedRepeats[$param], ++$i) { 
         $params["{$name}_{$i}"] = $val; 
        } 
       } else { 
        $params[$name] = $val; 
       } 
      } 
      return parent::execute($params); 
     } else { 
      return parent::execute(); 
     } 
    } 

    protected function _bind($method, $param, $args) { 
     if (isset($this->_namedRepeats[$param])) { 
      $result = TRUE; 
      for ($i=0; $i < $this->_namedRepeats[$param], ++$i) { 
       $args[0] = "{$param}_{$i}"; 
       # should this return early if the call fails? 
       $result &= call_user_func_array("parent::$method", $args); 
      } 
      return $result; 
     } else { 
      return call_user_func_array("parent::$method", $args); 
     } 
    } 
} 
+0

我從來在'ON DUPLICATE KEY UPDATE'語句中重複出現同樣的列表時有任何問題... – jeroen

+1

事實上,這取決於PDO驅動程序,您不應該依賴它。 – johannes

+0

有趣的是,它一直爲我工作。你知道關於這個主題的任何文件嗎? – jeroen

0

在我的情況下,當我從dblib freedts切換到sqlsrv PDO驅動程序時出現此錯誤。 Dblib驅動程序處理重複的參數名稱而沒有錯誤。我有相當複雜的動態查詢,有很多工會和大量的重複則params的,所以我用下面的幫助作爲一種解決方法:

function prepareMsSqlQueryParams($query, $params): array 
{ 
    $paramsCount = []; 
    $newParams = []; 
    $pattern = '/(:' . implode('|:', array_keys($params)) . ')/'; 

    $query = preg_replace_callback($pattern, function ($matches) use ($params, &$newParams, &$paramsCount) { 
     $key = ltrim($matches[0], ':'); 
     if (isset($paramsCount[$key])) { 
      $paramsCount[$key]++; 
      $newParams[$key . $paramsCount[$key]] = $params[$key]; 
      return $matches[0] . $paramsCount[$key]; 
     } else { 
      $newParams[$key] = $params[$key]; 
      $paramsCount[$key] = 0; 
      return $matches[0]; 
     } 
    }, $query); 

    return [$query, $newParams]; 
} 

然後你就可以使用這種方式:

$query = "UPDATE users SET firstname = :name WHERE firstname = :name"; 
$params = [":name" => "Jackie"]; 
// It will return "UPDATE users SET firstname = :name WHERE firstname = :name1"; with appropriate parameters array 
list($query, $params) = prepareMsSqlQueryParams($query, $params); 
$stmt = $dbh->prepare($query); 
$stmt->execute(params);