배열의 모든 요소를 순회하며 값을 변경해야 할 때 대부분의 개발자는 foreach 루프를 쓴다. 하지만 요소를 제자리에서 수정하려면 참조(&)를 사용해야 하는데, 이 부분을 제대로 이해하지 못하면 의도치 않은 결과가 나온다. 이번에는 array_walk() 함수로 어떻게 간단하게 배열을 일괄 수정할 수 있는지, 그리고 언제 쓰면 좋은지 완벽하게 정리하겠다.
array_walk()는 배열의 모든 요소에 사용자 정의 함수를 적용하는 함수다. map이나 forEach 같은 고차 함수와 비슷해 보이지만, 가장 큰 차이는 원본 배열을 직접 수정한다는 점이다.
기본 문법은 이렇다.
array_walk(&$array, callable $callback, mixed $userdata = null): bool
첫 번째 인자 $array는 반드시 참조(&)로 전달해야 한다. 이렇게 해야 함수 내부에서 원본을 수정할 수 있다. 두 번째 인자는 각 요소마다 호출될 콜백 함수고, 선택사항으로 추가 데이터를 세 번째 인자로 넘길 수 있다.
먼저 foreach로 배열을 수정하려고 할 때 흔히 하는 실수부터 보자.
✗ 잘못된 코드: 참조가 없으면 원본이 안 바뀐다
$numbers = [1, 2, 3, 4, 5];
foreach ($numbers as $num) {
$num = $num * 2; // 원본이 바뀌지 않음
}
var_dump($numbers);
// 결과: [1, 2, 3, 4, 5] — 변경 안 됨
✓ 올바른 코드: foreach에서 참조 사용
$numbers = [1, 2, 3, 4, 5];
foreach ($numbers as &$num) {
$num = $num * 2;
}
var_dump($numbers);
// 결과: [2, 4, 6, 8, 10]
참조를 빼먹으면 순환 끝난 후에도 $num 변수가 마지막 요소를 계속 가리키고 있다는 부작용도 생긴다. 이걸 피하려면 unset($num)을 명시해야 한다. array_walk()는 이런 번거로움을 피할 수 있다.
✓ array_walk()로 깔끔하게
$numbers = [1, 2, 3, 4, 5];
array_walk($numbers, function (&$num) {
$num = $num * 2;
});
var_dump($numbers);
// 결과: [2, 4, 6, 8, 10]
배열을 참조로 전달하고(&$numbers), 콜백 함수의 첫 번째 인자도 참조(&$num)로 받으면 된다. 함수가 끝나면 자동으로 참조 관계가 끊기므로 뒤처리가 필요 없다.
배열 요소가 객체나 연관배열일 때는 더 유용하다. 실제 웹 프로젝트에서 자주 나오는 상황을 보자.
예제 1: 사용자 배열에서 이름 앞에 '님' 붙이기
$users = [
['name' => '김철수', 'age' => 28],
['name' => '이영희', 'age' => 32],
['name' => '박민준', 'age' => 25]
];
array_walk($users, function (&$user) {
$user['name'] = $user['name'] . '님';
});
var_dump($users);
// 결과:
// [0] => Array([name] => 김철수님, [age] => 28)
// [1] => Array([name] => 이영희님, [age] => 32)
// [2] => Array([name] => 박민준님, [age] => 25)
예제 2: 세 번째 인자로 전역 설정값 받기
회원 정보의 상태를 일괄 변경해야 할 때 세 번째 인자를 활용하면 좋다.
$members = [
['id' => 1, 'status' => 'active'],
['id' => 2, 'status' => 'pending'],
['id' => 3, 'status' => 'active']
];
$newStatus = 'inactive';
array_walk($members, function (&$member, $key, $status) {
$member['status'] = $status;
}, $newStatus);
var_dump($members);
// 결과:
// [0] => Array([id] => 1, [status] => inactive)
// [1] => Array([id] => 2, [status] => inactive)
// [2] => Array([id] => 3, [status] => inactive)
콜백의 두 번째 인자 $key는 배열 키이고, 세 번째 인자 $status는 array_walk()의 세 번째 인자로 전달한 값이다.
예제 3: 가격에 부가세 적용하기
$products = [
['name' => '노트북', 'price' => 100000],
['name' => '마우스', 'price' => 20000],
['name' => '키보드', 'price' => 50000]
];
array_walk($products, function (&$product, $key, $taxRate) {
$product['price'] = $product['price'] * (1 + $taxRate);
}, 0.1); // 10% 부가세
var_dump($products);
// 결과:
// [0] => Array([name] => 노트북, [price] => 110000)
// [1] => Array([name] => 마우스, [price] => 22000)
// [2] => Array([name] => 키보드, [price] => 55000)
array_walk()와 비슷해 보이는 함수가 array_map()이다. 하지만 동작이 완전히 다르다.
| 항목 | array_walk() | array_map() |
|---|---|---|
| 반환값 | bool(true/false) | 변환된 새로운 배열 |
| 원본 수정 | ○ 원본 배열 직접 수정 | × 원본 유지, 새 배열 생성 |
| 배열 키 접근 | ○ 콜백에서 키 접근 가능 | × 값만 접근 |
| 복수 배열 처리 | × 한 개 배열만 | ○ 여러 배열 동시 처리 |
| 성능 | ○ 메모리 효율적 | × 새 배열 생성 오버헤드 |
원본을 수정하고 배열 키에도 접근해야 한다면 array_walk()가 정답이다. 하지만 원본은 보존하고 새로운 값을 얻고 싶다면 array_map()을 써야 한다.
// array_map: 새 배열 반환
$numbers = [1, 2, 3, 4, 5];
$doubled = array_map(function ($n) { return $n * 2; }, $numbers);
echo var_dump($doubled);
// 결과: [2, 4, 6, 8, 10] — 새 배열
echo var_dump($numbers);
// 결과: [1, 2, 3, 4, 5] — 원본 유지
// array_walk: 원본 수정
$numbers = [1, 2, 3, 4, 5];
array_walk($numbers, function (&$n) { $n = $n * 2; });
echo var_dump($numbers);
// 결과: [2, 4, 6, 8, 10] — 원본 수정됨
✗ 실수 1: 참조 기호(&)를 빼먹는다
$numbers = [1, 2, 3];
array_walk($numbers, function ($num) { // & 빠짐
$num = $num * 2;
});
var_dump($numbers);
// 결과: [1, 2, 3] — 변경 안 됨
✓ 올바른 방법
array_walk($numbers, function (&$num) { // & 추가
$num = $num * 2;
});
✗ 실수 2: 배열을 참조로 전달하지 않는다
$numbers = [1, 2, 3];
array_walk($numbers, function (&$num) { // 콜백은 참조인데
$num = $num * 2;
}); // array_walk 호출 시 참조가 없음
var_dump($numbers);
// 결과: [1, 2, 3] — 변경 안 됨
✓ 올바른 방법
array_walk($numbers, function (&$num) {
$num = $num * 2;
}); // 함수 선언에서 첫 번째 매개변수가 참조
하지만 배열 자체를 참조로 전달하는 건 아니다. 콜백 함수의 첫 번째 인자만 참조면 된다.
✗ 실수 3: 반환값을 잘못 이해한다
$numbers = [1, 2, 3];
$result = array_walk($numbers, function (&$num) {
$num = $num * 2;
});
var_dump($result); // true나 false
var_dump($numbers); // 수정된 배열 [2, 4, 6]
array_walk()는 bool을 반환한다. 수정된 배열이 아니라 성공/실패 여부만 반환한다. 수정된 값을 원하면 $numbers를 직접 사용해야 한다.
중첩 배열을 다루면 array_walk_recursive()를 써야 한다. 이 함수는 배열이 깊게 중첩되어 있어도 모든 값에 콜백을 적용한다.
$data = [
'user1' => ['name' => '김철수', 'age' => 28],
'user2' => ['name' => '이영희', 'age' => 32]
];
// array_walk: 첫 레벨만 처리
array_walk($data, function (&$value) {
if (is_array($value)) {
// 배열이므로 수정 안 됨
}
});
// array_walk_recursive: 모든 깊이 처리
array_walk_recursive($data, function (&$value, $key) {
if ($key === 'name') {
$value = $value . '님';
}
});
var_dump($data);
// 결과:
// [user1] => Array([name] => 김철수님, [age] => 28)
// [user2] => Array([name] => 이영희님, [age] => 32)
array_walk()는 언뜻 편해 보이지만 실무에서는 주의해야 할 점이 있다. 먼저 이 함수는 루프를 도는 동안 배열 크기가 변하면 예기치 않은 동작을 할 수 있다. 요소를 추가하거나 삭제하면서 순회하지 말자.
또한 성능이 중요한 상황(대용량 데이터)에서는 foreach가 더 빠를 수 있다. 함수 호출 오버헤드가 있기 때문이다. 가독성을 택할지 성능을 택할지는 상황에 따라 판단해야 한다.
마지막으로 복잡한 로직을 콜백에 집어넣으면 코드가 읽기 어려워진다. 간단한 변환이나 상태 업데이트 정도에만 쓰고, 복잡한 처리는 foreach를 명확히 쓰는 게 낫다.
array_walk()는 배열 요소를 제자리에서 수정할 때 쓰는 고급 함수다. 콜백 함수의 첫 번째 인자를 참조로 받으면 원본 배열이 직접 수정된다. array_map()과 달리 새 배열을 생성하지 않으므로 메모리 효율적이고, 배열 키에도 접근할 수 있다. 이 글의 실전 예제들을 참고해 복잡한 데이터 구조를 간단하게 일괄 처리하면, 반복문 없이도 깔끔한 코드를 만들 수 있을 것이다.