2011-09-29 45 views
18

Al escribir una declaración de pdo, ¿es posible repetir el valor de una variable? Quiero decir:php pdo prepare variables repetitivas

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

Tenga en cuenta que repito el nombre del titular ": nombre", mientras que proporciono el valor una sola vez. ¿Cómo puedo hacer que esto funcione?

Respuesta

19

La respuesta simple es: No se puede. PDO utiliza una abstracción para declaraciones preparadas que tiene algunas limitaciones. Por desgracia, este es uno, usted tiene que trabajar alrededor de usar algo como

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

En ciertos casos, tales como declaraciones preparadas emulados con algunas versiones del driver PDO/MySQL, son compatibles repetidas parámetros con nombre; sin embargo, esto no se debe confiar, ya que es frágil (puede hacer que las actualizaciones requieran más trabajo, por ejemplo).

Si desea soportar múltiples apariciones de un parámetro llamado, siempre extend PDO and PDOStatement (por herencia clásica o por la composición), o simplemente PDOStatement y puede configurar su clase como la clase comunicado estableciendo el atributo PDO::ATTR_STATEMENT_CLASS. La PDOStatement extendida (o PDO::prepare) podría extraer los parámetros nombrados, buscar repeticiones y generar automáticamente reemplazos. También registraría estos duplicados. Los métodos de vinculación y ejecución, al pasar un parámetro con nombre, probarían si el parámetro se repite y vincularán el valor a cada parámetro de reemplazo.

Nota: el siguiente ejemplo no se ha probado y es probable que tenga errores (algunos relacionados con el análisis de sentencias se anotan en los comentarios del código).

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

nunca he tuvo problemas para repetir la misma lista en una declaración 'ON DUPLICATE KEY UPDATE' ... – jeroen

+1

De hecho, depende un poco del controlador PDO, no debe confiar en que funcione. – johannes

+0

Interesante, siempre me ha funcionado. ¿Conoces alguna documentación sobre ese tema? – jeroen

0

En mi caso, este error apareció cuando cambié de controladores dblib a sqlsrv PDO. El controlador Dblib manejó nombres de parámetros duplicados sin errores. Tengo consultas dinámicas bastante complicadas con una gran cantidad de sindicatos y una gran cantidad de params duplicados, así que utiliza después como ayudante de solución:

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

continuación, puede utilizar de esta manera:

$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);