1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-06-08

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

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

一応動作しています。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?