cakephp1.2からcakephp2.10に上げた時のモデルでの違いでエラー１

cakephp1.2からcakephp2系にあげて、エラーになったことと対処した内容です。

postgres関数からPDOに変わっていたこともあり、実装がかなり変わっています。

# プリペアドステートメントに変わった影響

cakephp1.2は、変数を自前で変換してエスケープしてSQLを実行している実装になっていた。

cakephp2.10ではPDOのプリペアドステートメントを利用しているので、実装はDBのエンジンごとにまとまった実装になっていると思います。

いまのところエラーになった部分をいくつか対処した内容です。

## queryの変数のパラメータで余計な内容はNG

このプログラムが1.2ではOKで、2系ではエラーになります。

```test.php
$sql = "SELECT * FROM users WHERE id = :id ";

$params = [
    'id' => 1
    , 'company_id' => 2
];

$this->query($sql, $params);
```

理由としてはPDOの利用に代わり、PDO自体が余計なパラメータを許容していないからです。

むしろ便利にしていたのは1.2では自前でパラメータの部分を作成していたため、動作する仕様だったみたいです。

```cake/libs/model/datasources/dbo_source.php
return $this->fetchAll(String::insert($args[0], $args[1]), $cache);
```

```cake/libs/string.php
function insert($str, $data, $options = array()) {
		$defaults = array(
			'before' => ':', 'after' => null, 'escape' => '\\', 'format' => null, 'clean' => false
		);
		$options += $defaults;
		$format = $options['format'];
 
		if (!isset($format)) {
			$format = sprintf(
				'/(?<!%s)%s%%s%s/',
				preg_quote($options['escape'], '/'),
				str_replace('%', '%%', preg_quote($options['before'], '/')),
				str_replace('%', '%%', preg_quote($options['after'], '/'))
			);
		}
		if (!is_array($data)) {
			$data = array($data);
		}
 
		if (array_keys($data) === array_keys(array_values($data))) {
			$offset = 0;
			while (($pos = strpos($str, '?', $offset)) !== false) {
				$val = array_shift($data);
				$offset = $pos + strlen($val);
				$str = substr_replace($str, $val, $pos, 1);
			}
		} else {
			asort($data);
 
			$hashKeys = array_map('md5', array_keys($data));
			$tempData = array_combine(array_keys($data), array_values($hashKeys));
			foreach ($tempData as $key => $hashVal) {
				$key = sprintf($format, preg_quote($key, '/'));
				$str = preg_replace($key, $hashVal, $str);
			}
			$dataReplacements = array_combine($hashKeys, array_values($data));
			foreach ($dataReplacements as $tmpHash => $data) {
				$str = str_replace($tmpHash, $data, $str);
			}
		}
 
		if (!isset($options['format']) && isset($options['before'])) {
			$str = str_replace($options['escape'].$options['before'], $options['before'], $str);
		}
		if (!$options['clean']) {
			return $str;
		}
		return String::cleanInsert($str, $options);
	}
```

## 対処

パラメータを条件関係なくいれれたから楽だったんですが、変わったのは仕方がないです。

対処としては、呼び出しているところでパラメータを必要な時だけパラメータにする。

ただ呼び出し箇所が多かったので、ソースで対処してみました。

```lib/Cake/Model/DataSource/DboSource.php
protected function _execute($sql, $params = array(), $prepareOptions = array()) {
	$sql = trim($sql);
	if (preg_match('/^(?:CREATE|ALTER|DROP)\s+(?:TABLE|INDEX)/i', $sql)) {
		$statements = array_filter(explode(';', $sql));
		if (count($statements) > 1) {
			$result = array_map(array($this, '_execute'), $statements);
			return array_search(false, $result) === false;
		}
	}

	try {
		$query = $this->_connection->prepare($sql, $prepareOptions);
		$query->setFetchMode(PDO::FETCH_LAZY);
		if (!$query->execute($this->_executeParam($sql, $params))) {
			$this->_result = $query;
			$query->closeCursor();
			return false;
		}
		if (!$query->columnCount()) {
			$query->closeCursor();
			if (!$query->rowCount()) {
				return true;
			}
		}
		return $query;
	} catch (PDOException $e) {
		if (isset($query->queryString)) {
			$e->queryString = $query->queryString;
		} else {
			$e->queryString = $sql;
		}
		throw $e;
	}
}


protected function _executeParam( $sql, $params ){
    if(empty($params)) return $params;
    if(!is_array($params)) return $params;

    // ソート
    uksort($params, function($a, $b){
        $al = mb_strlen($a);
        $bl = mb_strlen($b);
        return $al >= $bl ? ($al == $bl ? 0 : -1 ) : 1;
    });

    $ret = [];
    foreach($params as $key => $param){
        if( is_numeric($key) ) {
            $ret[$key] = $param;
            continue;
        }

        if( strpos ($sql, ":".$key ) !== false) {
            $ret[$key] = $param;
        }
    }
    if(empty($ret)) return[];

    return $ret;
}

```

単純に文字列からパラメータがある場合にのみ、パラメータとして渡すように変更してみました。

一応動作しています。