3

サイトを PHP Mysql API から PDO に変換中ですが、データ型の問題に遭遇しました。

以前は、すべての変数を文字列であるかのようにエスケープしていました。例えば、

SET varname = '$varvalue'

もちろん、PDO を使用すると、

SET varname = :varvalue

次に、変数の型に基づいてデータ型を設定して、$varvalue の値のバインドを処理するクラスを作成します。

問題は、varname が文字列であるはずなのに、$varvalue が何らかの理由で null である場合に発生します。以前は、$varvalue が null の場合、'$varvalue' は単に '' になっていました。ここで、$varvalue を null として「適切に」バインドしていますが、データベース フィールドは null を許可していません。

これを修正する最も正しい方法は、$varvalue が正しい値で関数に渡されるようにすることであることはわかっていますが、私たちには大規模なレガシー コード ベースがあり、それを実装するには非常に多くの作業が必要になります。別の解決策は、変数を正しい型にバインドするときに、すべての変数を明示的にキャストすることです。可能であれば、モデル内のすべての変数を明示的にキャストする必要がないようにするソリューションが望ましいです。ありますか?

4

3 に答える 3

2

これはあなたが待っていた答えではないかもしれませんが、言及する必要があります: 例外を使用してください。

PDOException戻り値に依存する代わりに、タイプの例外をスローするように PDO を構成できます。

$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

これらの例外をキャッチしてログに記録したり、電子メールで送信したりすることができるため、どのコードが間違った値を渡していたかを正確に特定して修正できます。

これはやや骨の折れる操作です。Web サイトでキャッチされていないすべての例外を報告し始め、受信トレイがエラーで雑然としていたとき、私たちはこれに自分自身で耐えなければなりませんでした。数日かかりましたが、本当に悪いコードをすべて取り除くことができました :)

于 2012-08-21T09:07:53.647 に答える
1

以前は空の文字列を気にしていなかったので、var 値が null かどうかだけをチェックしてみませんか?

$stmt->bindParam(':varvalue', (is_null($varvalue) ? '' $varvalue), PDO::PARAM_STR);
于 2012-08-17T21:43:21.277 に答える
1

おそらく、PDO を直接使用するのではなく、アプリケーションの抽象化を使用する必要があります。抽象化レイヤーでは、実行したい任意の型変換を実行できます。

これが絶対に不可能で、 PDO を直接使用する必要がある場合は、サブクラス化と委譲を試して、必要な型キャストを実行する PDO に似たオブジェクトを生成できます。ただし、 null を予期する列がある場合、抽象化レイヤーが非常に複雑になります。データベースのイントロスペクションやその他のトリックが必要になる場合があります。おそらく、PDO API を正確に保存することはできません。

PDOStatement委任

これは非常に簡単ですbindValue。ただし、bindParamは参照を使用しており、それらを書き換えずに型キャストすることはできないため、が呼び出されbindValueたときにこれらを呼び出しに変換する回避策が必要executeです。

最初にサブクラス化PDOして、新しい wraped を返します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次に、null キャスト セマンティクスを持つデリゲートを作成します。最初の試行はオーバーライドのみ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();
于 2012-08-18T00:42:59.087 に答える