연재의 마지막 회차입니다. 지금까지 다룬 실패들 — 반환 누락 · 이른 시점 · 우선순위 경쟁 · 잘못된 체인 위치 · 실패한 제거 — 은 화면에서 대체로 같아 보입니다. “안 먹는다”. 그래서 추측하지 않고 확인하는 순서가 필요합니다.
확인 순서 네 단계
순서를 지키는 이유는 각 단계의 답이 다음 단계의 전제이기 때문입니다. 등록조차 안 됐는데 우선순위를 조정하는 것은 시간 낭비이고, 훅 자체가 불리지 않는 화면에서 콜백 내부를 들여다보는 것도 마찬가지입니다.
등록부를 직접 읽습니다
워드프레스는 모든 훅 등록을 전역 $wp_filter 에 보관합니다. 훅 이름으로 꺼내면 우선순위별 콜백 목록이 그대로 들어 있습니다.
add_action( 'wp_footer', function () {
$hook = $GLOBALS['wp_filter']['the_content'] ?? null;
if ( ! $hook ) {
return;
}
foreach ( $hook->callbacks as $priority => $group ) {
foreach ( $group as $cb ) {
$fn = $cb['function'];
if ( is_string( $fn ) ) {
$name = $fn;
} elseif ( is_array( $fn ) ) {
$owner = is_object( $fn[0] ) ? get_class( $fn[0] ) : $fn[0];
$name = $owner . '::' . $fn[1];
} else {
$name = 'Closure';
}
error_log( sprintf( '%4d %s (args %d)', $priority, $name, $cb['accepted_args'] ) );
}
}
}, 9999 );
이 한 덩어리가 네 가지를 동시에 알려 줍니다. 내 콜백이 목록에 있는가(등록), 몇 번에 있는가(우선순위), 나보다 뒤에 누가 있는가(덮어쓰기 후보), 그리고 인자를 몇 개 받도록 등록됐는가.
이름 대신 Closure 로 찍히는 항목이 있다면, 그것이 바로 6회차에서 말한 이름으로 내릴 수 없는 콜백입니다. 보이기는 하지만 잡을 손잡이가 없습니다.
더 가벼운 확인들
has_filter() 는 특히 유용합니다. 콜백까지 지정하면 걸려 있는 우선순위 숫자를 돌려주므로, 6회차의 “우선순위가 달라서 제거가 실패” 를 코드로 확인할 수 있습니다. 걸려 있지 않으면 false 입니다.
did_action() 은 두 번째 단계 — 훅이 이 화면에서 실제로 불리는가 — 를 한 줄로 끝냅니다. 0이라면 콜백을 아무리 들여다봐도 소용이 없습니다.
덜 쓰는 편이 좋은 것
all 이라는 특수 훅에 콜백을 걸면 실행되는 모든 훅을 받아볼 수 있습니다. “이 시점에 무슨 훅들이 도는가” 를 알아낼 때 유용하지만, 요청 한 번에 수천 번 불리므로 임시로 켰다가 반드시 끕니다.
쿼리와 훅을 화면에서 보여 주는 개발 도구도 있습니다. 다만 그런 도구는 대개 드롭인을 설치해 모든 요청에 오버헤드를 얹으므로 개발 · 스테이징 전용입니다. 프로덕션에 켜 둔 채 운영하면 성능과 정보 노출을 동시에 잃습니다.
연재를 마치며
여덟 회차를 정리하면 이렇습니다. 필터는 반드시 값을 돌려주고, 등록은 init 의 세상 위에서 하고, 우선순위는 이기려 하기보다 자리를 고르고, pre_get_posts 는 가드 두 줄로 시작하고, the_content 는 원본과 완성 중 어느 쪽을 볼지 먼저 정하고, 제거는 반환값을 확인하고, 우리 코드에는 남이 붙을 자리를 남깁니다.
공통점은 하나입니다 — 훅은 코어를 고치지 않아도 되게 만들어 준 장치이고, 그 이점을 지키려면 우리도 남의 코드를 고치지 않는 방식으로 써야 한다는 것입니다.
운영 중인 사이트에서 이 원칙들이 실제로 어떻게 적용되는지는 작업 과정에 절차 그대로 공개돼 있고, 버전 업그레이드 · 성능 최적화 · 보안 점검을 한 건으로 맡기고 싶다면 최적화 지원 사업이 그 작업입니다.