您可能应该为您的应用程序使用抽象,而不是直接使用 PDO。在您的抽象层中,您可以执行您想要执行的任何类型转换。
如果这绝对不是一个选项并且您必须直接使用 PDO,那么您可以尝试子类化和委托以生成执行所需类型转换的 PDO 类似对象。但是,如果有任何列确实期望为空,那么您的抽象层将会大大复杂化。您可能需要对数据库或其他一些技巧进行自省。您可能无法准确地保留 PDO API。
PDOStatement
代表团
这对于bindValue
. 但是,bindParam
使用引用并且我们不能在不重写它们的情况下对它们进行类型转换,因此我们需要一种解决方法,在bindValue
调用时将它们转换execute
为调用。
首先我们子类化PDO
,所以它返回我们新的 Wrapped PDOStatement
。
class PDO_nullcast extends PDO {
public function prepare($statement, $driver_options=array()) {
$prepared = parent::prepare($statement, $driver_options);
$delegated_prepared = new PDOStatement_nullcast($prepared);
return $delegated_prepared;
}
}
然后我们创建一个PDOStatement_nullcast
具有空转换语义的委托。我们的第一次尝试只会覆盖bindValue
.
class PDOStatement_nullcast {
protected $pstmt;
protected $bindparams; // this is for later
function __construct(PDOStatement $pstmt) {
$this->pstmt = $pstmt;
$this->bindparams = array();
}
function __get($k) {
return $this->pstmt->{$k};
}
function __set($k, $v) {
$this->pstmt->{$k} = $v;
}
function __call($k, $a) {
return call_user_func_array(array($this->pstmt, $k), $a);
}
function bindValue($parameter, $value, $data_type=PDO::PARAM_STR) {
$newvalue = $this->castValue($value, $data_type);
return $this->pstmt->bindValue($parameter, $newvalue, $data_type);
}
static public function castValue($val, $typehint) {
$newval = $val;
if ($val===NULL) {
if ($typehint===PDO::PARAM_STR) {
$newval = '';
} else if ($typehint===PDO::PARAM_INT) {
$newval = 0;
} else if ($typehint===PDO::PARAM_BOOL) {
$newval = false;
}
} else {
if ($typehint===PDO::PARAM_STR) {
$newval = (string) $val;
} else if ($typehint===PDO::PARAM_INT) {
$newval = (int) $val;
} else if ($typehint===PDO::PARAM_BOOL) {
$newval = (bool) $val;
}
}
return $newval;
}
}
这是一些演示代码。我们将使用下表作为示例:
CREATE TABLE `typetest` (
`intcol` int(11) NOT NULL,
`strcol` varchar(255) NOT NULL,
`intnullcol` int(11) DEFAULT NULL,
`intstrcol` varchar(255) DEFAULT NULL,
)
现在是 PHP 代码。假设您有一个PDO_nullcast
对象分配给$db
:
$sql = 'INSERT INTO typetest (`intcol`, `strcol`, `intnullcol`, `intstrcol`) VALUES (?,?,?,?)';
$insert = $db->prepare($sql);
$insert->bindValue(1, null, PDO::PARAM_INT);
$insert->bindValue(2, null, PDO::PARAM_STR);
$insert->bindValue(3, null, PDO::PARAM_INT);
$insert->bindValue(4, null, PDO::PARAM_STR);
$insert->execute();
$insert->closeCursor();
$select = $d->prepare('SELECT * FROM typetest');
$select->execute();
$res = $select->fetchAll();
$select->closeCursor();
var_dump($res);
您可以将castValue
函数更改为您想要的语义。
但是,这不会处理这种bindParam
情况。在这里,我们需要在内部保留一个引用,直到execute
在我们的包装器上被调用,然后将它们转换为bindValue
调用。但是,我们无法处理bindParam
这种方式的所有用途!INOUT 参数没有解决方法,因为我们无法通过类型转换保留引用。
我们可以像这样拦截bindParam
和execute
调用来得到我们想要的(在PDOStatement_nullcast
上面的类中添加以下方法):
function bindParam($parameter, &$variable, $data_type=PDO::PARAM_STR, $length=null, $driver_options=null) {
if (isset($length) || isset($driver_options) || ($data_type & PDO::PARAM_INPUT_OUTPUT)) {
// in either of these cases, we cannot wrap!
return $this->pstmt->bindParam($parameter, $variable, $data_type, $length, $driver_options);
}
// note we preserve a reference to the variable
$this->bindparams[] = array($parameter, &$variable, $data_type);
return true; // this is a bit of a lie--we can't know if we would have an error until later.
}
function execute($input_parameters=null) {
if ($input_parameters!==null) {
return $this->pstmt->execute($input_parameters);
}
// for-loop is to preserve references more clearly
// foreach is trickier
for ($i=0; $i < count($this->bindparams); $i++) {
call_user_func_array(array($this,'bindValue'), $this->bindparams[$i]);
}
return $this->pstmt->execute();
}
这是一些使用的测试代码bindParam
:
$var = null;
$insert->bindParam(1, $var, PDO::PARAM_INT);
$insert->bindParam(2, $var, PDO::PARAM_STR);
$insert->bindParam(3, $var, PDO::PARAM_INT);
$insert->bindParam(4, $var, PDO::PARAM_STR);
error_log($var);
$insert->execute();
$var = 1;
$insert->execute();
$var = 2;
$insert->execute();