본문 바로가기
기술/PHP

코드이그나이터4(CodeIgniter4)에서 model의 escape가 이상하게 동작한다고!?

by 포도빛 2020. 8. 23.

The wrong escape in CodeIgniter4's model!?

최근(?)에 CodeIgniter4가 출시됨에 따라 새로운 웹 프로젝트는 CodeIgniter4로 개발하게 되었다.

 

당연하듯이 CodeIgniter3의 문법이 거의 동일하게 작동하는줄 알고 열심히 모델을 구현하는 도중 이상한 문제에 도달했었다.

 

문제의 코드:

/** @var TestModel $model */
$model = model('TestModel');
try {
	$model->set('name', 'phodobit')
		->set('point', 0)
		->set('update_date', 'CURRENT_TIMESTAMP()', FALSE)
		->update();
} catch(\Exception $ex) {

}

이 코드를 실행해보면 Database Syntax Error가 발생하게 된다.

 

모델을 잘못 짠건가..?라는 생각에 모델을 다시 보았는데 Syntax Error가 발생할 만한 코드는 존재하지 않았다.

 

흠..? CI3의 코드랑 비교를 해보자

// 코드이그나이터3
$builder = $this->db
	->set('name', 'phodobit')
	->set('point', 0)
	->set('update_date', 'CURRENT_TIMESTAMP()', FALSE)
    ->update('test');

아니 이건 왜 잘되는거야 ㅠㅠ...

 

일단 Syntax Error이 나타나니 마지막으로 시도한 SQL 구문을 불러서 보았다.

/* 코드이그나이터4 */
UPDATE `test` SET name = phodobit, point = 0, update_date = CURRENT_TIMESTAMP()

/* 코드이그나이터3 */
UPDATE `test` SET `name` = 'phodobit', `point` = 0, update_date = CURRENT_TIMESTAMP()

뭐지 이건!? 왜 CI4에서 key(column)와 value에 escape가 되지 않은거지;;

 

혹시 몰라서 CI4 코드에서 set() 메소드(함수)에 있는 escape 파라미터의 값을 TRUE로 바꿔보았다.

뭐가 문제니.......?

이번엔 escape가 동작을 하긴 했는데 원치 않는 구간도 escape가 되어버렸다.

혹시 몰라 set() 구문을 $model->set('update_date', 'CURRENT_TIMESTAMP()', FALSE)->set()...->update(); 로 해보았더니 이번엔 모든 key와 value가 escape=TRUE를 한 것과 동일한 결과가 나타났다.

 

"아 혹시 이거 버그인가?"

라는 생각으로 바로 코드이그나이터4 github issue에 글을 남겨보았다.

 

Bug: A wrong escape on BaseBuilder::set() · Issue #3127 · codeigniter4/CodeIgniter4

Describe the bug I guess it is a bug that the function named BaseBuilder::set() building wrong escape character CodeIgniter 4 version 4.0.3 on master branch. Affected module(s) i don't know Exp...

github.com

(영어를 잘 못해서 끔찍한 의사소통이 이루어진다)

 

issue에 올렸던 예시에서는 set()을 이 글처럼 하되, [VIEW_COUNT + 1]라는 예시를 들었었다.

그러나 돌아온 답변은 "버그가 아니다"라는 답과 +1 처리는 increment()를 사용하라는 답변이었다. (이걸 원한게 아닌데 ㅠㅠ...)

 

"도대체 왜 안되는건가?"

CI4에서 사용하는 Model의 코드를 보면 set()을 호출할때마다 Model 객체 멤버변수에 escape를 할지 말지 결정하고 SQL을 조합할때 결국에 하나뿐인 escape 멤버변수만 바라보기 때문에 전체가 escape되거나 말거나였다.

// 코드이그나이터4 Model의 set()
public function set($key, ?string $value = '', bool $escape = null)
{
  $data = is_array($key)
    ? $key
    : [$key => $value];

  $this->tempData['escape'] = $escape; // 여기에 하필 저장을 해버리다니 ㅠㅠ..
  $this->tempData['data']   = array_merge($this->tempData['data'] ?? [], $data);

  return $this;
}

 

"어떻게 해결 할 수 있는가?"

이런 복합적인 데이터 처리를 할때는 Model에서 builder를 가져온 다음에 작업해야만 한다.

 

그러나 주의할 점은 Controller 등 외부에서 Model의 builder를 가져올 수는 없다.

왜냐하면 현재 CodeIgniter4(최신 4.0.3)에서 Model의 멤버객체인 $builder를 가져오는 메소드는 protected로 선언된 builder()메소드이므로 외부에서 $model->builder()를 호출하는 경우 __call() 메소드에서 $this(모델 객체)를 리턴해버리기 때문이다.

// 컨트롤러에서 builder와 model의 비교한 경우 같다고 나타난다.
$model = model('TestModel');
var_dump($model === $model->builder());
// 실행결과: bool(true)

 

결국엔 아래와 같이 모델에서 builder를 가져온 후 처리하면 되는 것이다.

// 반드시 *Model*에서 코딩되어야 한다.
public function test() {
	try {
		$this->builder() // builder를 가져와서 하는 것이 매우 중요!
			->set('name', 'phodobit')
			->set('point', 0)
			->set('update_date', 'CURRENT_TIMESTAMP()', FALSE)
			->update();
	} catch(\Exception $ex) {
	}
  
	return $this->getLastQuery();
}
// 실행결과:
// UPDATE `users` SET `name` = 'phodobit', `point` = '0', update_date = CURRENT_TIMESTAMP()

 

댓글