2011-09-29 45 views
18

Khi viết câu lệnh pdo, bạn có thể lặp lại giá trị của biến không? Ý tôi là:php pdo chuẩn bị các biến lặp đi lặp lại

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

Xin lưu ý rằng tôi lặp lại tên chủ ": name" trong khi tôi chỉ cung cấp giá trị một lần. Làm thế nào tôi có thể thực hiện công việc này?

Trả lời

19

Câu trả lời đơn giản là: Bạn không thể. PDO sử dụng một trừu tượng cho các câu lệnh chuẩn bị có một số hạn chế. Thật không may đây là một trong, bạn phải làm việc xung quanh bằng một cái gì đó giống như

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

Trong những trường hợp nhất định, chẳng hạn như chuẩn bị phát biểu mô phỏng với một số phiên bản của trình điều khiển PDO/MySQL, lặp đi lặp lại tên các thông số được hỗ trợ; tuy nhiên, điều này không nên dựa vào, vì nó dễ vỡ (ví dụ: nó có thể nâng cấp yêu cầu công việc nhiều hơn).

Nếu bạn muốn hỗ trợ nhiều lần xuất hiện của thông số được đặt tên, bạn luôn có thể extend PDO and PDOStatement (bằng kế thừa cổ điển hoặc theo thành phần) hoặc chỉ PDOStatement và đặt lớp của bạn làm lớp tuyên bố bằng cách đặt thuộc tính PDO::ATTR_STATEMENT_CLASS. PDOStatement mở rộng (hoặc PDO::prepare) có thể trích xuất các tham số có tên, tìm kiếm lặp lại và tự động tạo các thay thế. Nó cũng sẽ ghi lại các bản sao này. Các phương thức liên kết và thực thi, khi truyền một tham số có tên, sẽ kiểm tra xem tham số được lặp lại và liên kết giá trị với mỗi tham số thay thế.

Lưu ý: ví dụ sau chưa được kiểm tra và có thể có lỗi (một số liên quan đến phân tích cú pháp câu lệnh được ghi chú trong các chú thích mã).

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

Tôi đã không bao giờ đã có bất kỳ vấn đề lặp đi lặp lại cùng một danh sách trong một tuyên bố 'ON DUPLICATE KEY UPDATE' ... – jeroen

+1

Trong thực tế nó phụ thuộc một chút vào trình điều khiển PDO, bạn không nên dựa vào nó làm việc. – johannes

+0

Thú vị, nó luôn làm việc cho tôi. Bạn có biết bất kỳ tài liệu nào về chủ đề đó không? – jeroen

0

Trong trường hợp của tôi, lỗi này xuất hiện khi tôi chuyển từ trình điều khiển tự do dblib sang sqlsrv PDO. Trình điều khiển Dblib xử lý các tên tham số trùng lặp mà không có lỗi. Tôi có thắc mắc động khá phức tạp với rất nhiều đoàn thể và rất nhiều params trùng lặp vì vậy tôi sử dụng sau helper như một cách giải quyết:

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]; 
} 

Sau đó, bạn có thể sử dụng nó theo cách này:

$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); 
Các vấn đề liên quan