테일러 오트웰이 예전에 자신의 블로그에 Tap, Tap, Tap이라는 글을 올리면서 자신이 라라벨에서 가장 좋아하는 헬퍼 메소드가 tap
이라고 소개한 바 있다. 대체 뭐길래 그랬을까? 오늘은 tap
이 무엇이고 어떻게 동작하는지 알아보자.
질문. 다음의 리턴값은?
return $user->update([
'name' => $name,
'age' => $age
]);
아래 코드는 Model.php
의 update()
메소드이다. 주석 중 @return
항목에 bool
이라 되어 있다. 따라서 정답은 bool
, 즉 true
아니면 false
이다.
/**
* Update the model in the database.
*
* @param array $attributes
* @param array $options
* @return bool
*/
public function update(array $attributes = [], array $options = [])
{
if (! $this->exists) {
return false;
}
return $this->fill($attributes)->save($options);
}
업데이트한 $user
를 리턴하고 싶으면 어떻게 해야할까? 뭐 쉽다. $user를 리턴하면 된다.
$user->update([
'name' => $name,
'age' => $age
]);
return $user;
tap
을 쓰면 한 줄로 처리할 수 있다.
return tap($user)->update([
'name' => $name,
'age' => $age
]);
위 코드는 update()
의 결과가 아닌 update()
를 실행한 $user
를 반환한다. 이렇게만 보면 굉장히 소소해 보이는 기능인데, 자주쓰는 사람은 꽤 유용한가보다. 용법만 알고 쓰면 되긴 하지만, 동작원리가 궁금한 사람은 코드도 한 번 읽어보자.
tap
은 7줄 짜리 짧은 메소드이다. 그런데, 이전에는 3줄 짜리 메소드였다.
function tap($value, $callback)
{
$callback($value);
return $value;
}
더 단순하면 더 이해하기 쉬우니 옛 버전으로 먼저 이해해보자. 설명을 보면 주어진 $value를 가지고 클로저($callback)를 호출한 다음 $value를 리턴한다.
고 되어 있다. 앞서 살펴본 $user->update() 예제를 옛 버전으로 처리하려면 아래와 같이 하면 됐다.
return tap($user, function($user) {
$user->update([
'name' => $name,
'age' => $age
]);
});
조금 더 불편하면 되는데, 한 줄이라도 더 줄이고 싶었는지 다음과 같이 클로저를 안 받을 수 있게 메소드를 변경했다.
/**
* Call the given Closure with the given value then return the value.
*
* @param mixed $value
* @param callable|null $callback
* @return mixed
*/
function tap($value, $callback = null)
{
if (is_null($callback)) {
return new HigherOrderTapProxy($value);
}
$callback($value);
return $value;
}
이제 클로저가 주어지지 않을 수 있고, 그 경우 HigherOrderTapProxy
를 리턴한다.
class HigherOrderTapProxy
{
public $target;
public function __construct($target)
{
$this->target = $target;
}
public function __call($method, $parameters)
{
$this->target->{$method}(...$parameters);
return $this->target;
}
}
HigherOrderTapProxy
클래스는 생성자와 __call
매직 메소드 하나만 가지고 있다.
__call
매직 메소드는 미리 정의되지 않은 메소드를 실행한다.
$HigherOrderTapProxy = new HigherOrderTapProxy($target);
$HigherOrderTapProxy->update([
'name' => $name,
'age' => $age
])
HigherOrderTapProxy
클래스에 update
라는 메소드가 없기 때문에 에러가 나야하지만, __call
이 선언되어 있기 때문에 __call
가 이를 처리한다. 호출한 메소드인 ‘update’가 __call
메소드의 첫번째 인자로, update 메소드가 받은 인자들이 배열로 변환되어 __call
메소드의 두번째 인자로 전달된다.
__call
메소드의 내용을 보면 아래와 같은 구문이 보인다.
$this->target->{$method}(...$parameters);
...
는 PHP 5.6에 추가된 기능으로 인수를 넘길 때 ...
를 붙이면 배열을 풀어서 전달해준다.
$args = [1, 3, 5, 7, 9];
MyClass::someMethod(...$args);
위 코드와 아래 코드가 같다.
MyClass::someMethod(1, 3, 4, 7, 9);
HigherOrderTapProxy는 객체를 받아서 그 객체의 메소드를 실행한 뒤 객체 자체를 반환하는 역할을 한다.
tap($user)
는 new HigherOrderTapProxy($user)
를 반환하므로 tap($user)->update()
는 (new HigherOrderTapProxy($user))->__call
에서 처리된다. $this->target은 $user이고, $method는 ‘update’, $parameters는 [[‘name’ => $name, ‘age’ => $age]] 이므로
$this->target->{$method}(...$parameters);
return $this->target;
는 아래와 같은 뜻이 된다.
$user->update([
'name' => $name,
'age' => $age
]);
return $user;
아까 봤던 익숙한 코드가 나왔다 ^^
이번 글에서는 tap
메소드의 사용법을 알아보고, 어떻게 구현된 것인지 코드를 살펴봤다. 코드 자체는 매우 짧지만 초보자들이 읽기엔 조금 어려웠을 수 있을 것 같다. 그래서 관련된 PHP 문법과 기능인 __call
매직 메소드, 가변 길이 인수 목록에 대해서도 간단히 알아보았다.
===
오늘은 메일 발송이 좀 늦었습니다. 죄송합니다. ㅠㅠ
내일부터는 늦지 않게 더 신경쓰겠습니다!
1일 1식 라라벨 6호
2019년 7월 8일
메쉬 코리아 개발자. 바쁜 팀장님 대신 알려주는 신입 PHP 개발자 안내서를 쓰고, 클린 아키텍처 인 PHP를 번역했습니다. 처음부터 제대로 배우는 라라벨(Laravel Up & Running 2nd Edition)을 번역했습니다.