PHP PDO语句能接受表或列名作为参数吗?

为什么我不能将表名传递给准备好的PDO语句?

$stmt = $dbh->prepare('SELECT * FROM :table WHERE 1'); if ($stmt->execute(array(':table' => 'users'))) { var_dump($stmt->fetchAll()); } 

是否有另一种安全的方式来插入一个表名到SQL查询中? 安全我的意思是我不想这样做

 $sql = "SELECT * FROM $table WHERE 1" 

请参阅以下内容: http : //us3.php.net/manual/en/book.pdo.php#69304

表和列名称不能被PDO中的参数replace。

在这种情况下,您只需要手动过滤和清理数据。 一种方法是将简写parameter passing给将dynamic执行查询的函数,然后使用switch()语句创build一个用于表名或列名的有效值的白名单。 这样,用户input不会直接进入查询。 举个例子:

 function buildQuery( $get_var ) { switch($get_var) { case 1: $tbl = 'users'; break; } $sql = "SELECT * FROM $tbl"; } 

通过不留下任何默认情况下或使用返回错误消息的默认情况下,您确保只使用您要使用的值。

要理解为什么绑定表(或列)名称不起作用,您必须了解预处理语句中的占位符是如何工作的:它们不会简单地replace为(适当转义的)string,并执行结果SQL。 相反,一个DBMS要求“准备”一个语句,就会提出一个完整的查询计划来执行该查询,包括使用哪个表和索引,而不pipe填充占位符的方式如何。

SELECT name FROM my_table WHERE id = :value计划将与您替代的:value相同:value ,但看似相似的SELECT name FROM :table WHERE id = :value无法计划,因为DBMS不知道您的表是什么实际上是要从中select。

这不是像PDO这样的抽象类库可以或者应该解决的问题,因为它会打败准备好的语句的两个关键目的:1)允许数据库事先决定如何运行查询,并使用相同的多次计划; 和2)通过将查询的逻辑与variablesinput分离来防止安全问题。

我看到这是一个旧post,但我发现它很有用,并认为我会分享类似于@kzqaibuild议的解决scheme:

我有一个函数接收两个参数,如…

 function getTableInfo($inTableName, $inColumnName) { .... } 

在内部,我检查了我设置的数组,以确保只有具有“祝福”表的表和列才可访问:

 $allowed_tables_array = array('tblTheTable'); $allowed_columns_array['tblTheTable'] = array('the_col_to_check'); 

然后运行PDO之前的PHP检查看起来像…

 if(in_array($inTableName, $allowed_tables_array) && in_array($inColumnName,$allowed_columns_array[$inTableName])) { $sql = "SELECT $inColumnName AS columnInfo FROM $inTableName"; $stmt = $pdo->prepare($sql); $stmt->execute(); $result = $stmt->fetchAll(PDO::FETCH_ASSOC); } 

使用前者本身并不比后者安全,您需要清理input,无论它是参数数组还是简单variables的一部分。 所以我没有看到使用$table的后一种forms的任何错误,只要你确保在使用$table的内容是安全的(alphanum加下划线?)。

我的一部分想知道如果你能提供你自己的定制消毒function,就像这样简单:

 $value = preg_replace('/[^a-zA-Z_]*/', '', $value); 

我没有真正想过,但似乎删除除字符和下划线之外的任何东西都可能起作用。

至于在这个线程中的主要问题,其他post明确了为什么我们不能在准备语句时将值绑定到列名,所以这里是一个解决scheme:

 class myPdo{ private $user = 'dbuser'; private $pass = 'dbpass'; private $host = 'dbhost'; private $db = 'dbname'; private $pdo; private $dbInfo; public function __construct($type){ $this->pdo = new PDO('mysql:host='.$this->host.';dbname='.$this->db.';charset=utf8',$this->user,$this->pass); if(isset($type)){ //when class is called upon, it stores column names and column types from the table of you choice in $this->dbInfo; $stmt = "select distinct column_name,column_type from information_schema.columns where table_name='sometable';"; $stmt = $this->pdo->prepare($stmt);//not really necessary since this stmt doesn't contain any dynamic values; $stmt->execute(); $this->dbInfo = $stmt->fetchAll(PDO::FETCH_ASSOC); } } public function pdo_param($col){ $param_type = PDO::PARAM_STR; foreach($this->dbInfo as $k => $arr){ if($arr['column_name'] == $col){ if(strstr($arr['column_type'],'int')){ $param_type = PDO::PARAM_INT; break; } } }//for testing purposes i only used INT and VARCHAR column types. Adjust to your needs... return $param_type; } public function columnIsAllowed($col){ $colisAllowed = false; foreach($this->dbInfo as $k => $arr){ if($arr['column_name'] === $col){ $colisAllowed = true; break; } } return $colisAllowed; } public function q($data){ //$data is received by post as a JSON object and looks like this //{"data":{"column_a":"value","column_b":"value","column_c":"value"},"get":"column_x"} $data = json_decode($data,TRUE); $continue = true; foreach($data['data'] as $column_name => $value){ if(!$this->columnIsAllowed($column_name)){ $continue = false; //means that someone possibly messed with the post and tried to get data from a column that does not exist in the current table, or the column name is a sql injection string and so on... break; } } //since $data['get'] is also a column, check if its allowed as well if(isset($data['get']) && !$this->columnIsAllowed($data['get'])){ $continue = false; } if(!$continue){ exit('possible injection attempt'); } //continue with the rest of the func, as you normally would $stmt = "SELECT DISTINCT ".$data['get']." from sometable WHERE "; foreach($data['data'] as $k => $v){ $stmt .= $k.' LIKE :'.$k.'_val AND '; } $stmt = substr($stmt,0,-5)." order by ".$data['get']; //$stmt should look like this //SELECT DISTINCT column_x from sometable WHERE column_a LIKE :column_a_val AND column_b LIKE :column_b_val AND column_c LIKE :column_c_val order by column_x $stmt = $this->pdo->prepare($stmt); //obviously now i have to bindValue() foreach($data['data'] as $k => $v){ $stmt->bindValue(':'.$k.'_val','%'.$v.'%',$this->pdo_param($k)); //setting PDO::PARAM... type based on column_type from $this->dbInfo } $stmt->execute(); return $stmt->fetchAll(PDO::FETCH_ASSOC);//or whatever } } $pdo = new myPdo('anything');//anything so that isset() evaluates to TRUE. var_dump($pdo->q($some_json_object_as_described_above)); 

以上只是一个例子,所以不用多说,copy-> paste就行不通了。 根据您的需求调整。 现在,这可能不会提供100%的安全性,但它允许一些控制列名称,当他们“进来”作为dynamicstring,并可能在用户端更改。 此外,由于它们是从information_schema中提取的,因此不需要使用表列名称和types构build一些数组。