배열에서 특정 조건을 만족하는 요소만 남기고 싶을 때 반복문으로 일일이 처리한 경험이 있을까. 다만 대부분의 개발자들은 array_filter()의 정확한 동작 방식과 콜백 함수의 세 번째 인자가 뭔지 모른 채 단순 반복문만 사용하곤 한다. 이번에는 array_filter()의 원리부터 시작해서 실전 필터링 패턴까지 완벽하게 정리해서 소개하겠다.

 

array_filter() 기초 이해하기

array_filter()는 배열의 각 요소를 주어진 콜백 함수로 평가해서 true를 반환하는 요소만 남기는 함수다. 단순히 null이나 false를 제거하는 게 아니라, 사용자 정의 조건으로 필터링할 수 있다는 게 핵심이다.

함수의 기본 문법은 이렇다.

array_filter(array $array, ?callable $callback = null, int $mode = 0): array

세 개의 인자가 있는데, 각각의 역할을 정확히 알아야 실무에서 제대로 쓸 수 있다.

 

콜백 함수 없이 사용하기

콜백을 생략하면 array_filter()는 단순히 falsy 값들(0, false, null, 빈 문자열, '0')을 제거한다.

$data = [0, 1, false, 2, '', 3, null];
$result = array_filter($data);
print_r($result);
// Array ( [1] => 1 [3] => 2 [5] => 3 )

보다시피 인덱스 1, 3, 5의 요소만 남고 0, false, '', null은 전부 제거됐다. 주목할 점은 배열의 키(index)는 유지된다는 것. 만약 키 재정렬이 필요하면 array_values()를 따로 사용해야 한다.

 

콜백 함수로 조건 필터링하기

실무에서는 거의 항상 콜백을 정의해서 쓴다. 간단한 예제부터 보자.

$numbers = [1, 2, 3, 4, 5, 6];
$even = array_filter($numbers, function($num) {
    return $num % 2 === 0;
});
print_r($even);
// Array ( [1] => 2 [3] => 4 [5] => 6 )

짝수만 필터링하는 콜백이다. 콜백이 true를 반환하는 요소만 결과 배열에 포함된다.

 

세 번째 인자 mode로 키 활용하기

array_filter()의 세 번째 인자 $mode는 많은 개발자가 놓치는 부분이다. 이 인자로 콜백 함수가 받는 인자를 제어할 수 있다.

mode 값 콜백 인자 설명
0 (기본값) $value 요소의 값만 전달
ARRAY_FILTER_USE_KEY $key 요소의 키만 전달
ARRAY_FILTER_USE_BOTH $value, $key 값과 키 모두 전달

각 mode의 실제 사용 사례를 보자.

$user = [
    'name' => 'Alice',
    'age' => 25,
    'email' => 'alice@example.com',
    'phone' => null,
    'address' => ''
];

// mode 0 (기본): 값만 필터링
$filled = array_filter($user);
// Array ( [name] => Alice [age] => 25 [email] => alice@example.com )

// ARRAY_FILTER_USE_KEY: 키로 필터링
$important = array_filter($user, function($key) {
    return in_array($key, ['name', 'email']);
}, ARRAY_FILTER_USE_KEY);
// Array ( [name] => Alice [email] => alice@example.com )

// ARRAY_FILTER_USE_BOTH: 값과 키 모두 사용
$valid = array_filter($user, function($value, $key) {
    if ($key === 'age') return $value >= 18;
    return $value !== null && $value !== '';
}, ARRAY_FILTER_USE_BOTH);
// Array ( [name] => Alice [age] => 25 [email] => alice@example.com )

특히 ARRAY_FILTER_USE_KEY는 키 이름으로 특정 필드만 추출할 때 매우 유용하다.

 

실전 예제 1: 상품 가격 필터링

전자상거래 시스템에서 가격대별로 상품을 필터링하는 경우다.

✗ 잘못된 코드 (반복문으로 일일이 처리)
$products = [
    ['name' => '노트북', 'price' => 1500000, 'stock' => 5],
    ['name' => '마우스', 'price' => 30000, 'stock' => 0],
    ['name' => '키보드', 'price' => 150000, 'stock' => 3],
    ['name' => '모니터', 'price' => 350000, 'stock' => 2],
];

$filtered = [];
foreach ($products as $product) {
    if ($product['price'] >= 100000 && $product['price'] <= 500000 && $product['stock'] > 0) {
        $filtered[] = $product;
    }
}
// 반복문이 길어지고 가독성이 떨어짐

✓ 올바른 코드 (array_filter 사용)

$filtered = array_filter($products, function($product) {
    return $product['price'] >= 100000 
        && $product['price'] <= 500000 
        && $product['stock'] > 0;
});

print_r($filtered);
// Array (
//     [1] => Array ( [name] => 키보드 [price] => 150000 [stock] => 3 )
//     [3] => Array ( [name] => 모니터 [price] => 350000 [stock] => 2 )
// )

array_filter()를 쓰면 조건이 명확하고 코드가 간결해진다. 인덱스가 유지되므로 필요하면 array_values()로 재정렬하면 된다.

$filtered = array_values(array_filter($products, function($product) {
    return $product['price'] >= 100000 
        && $product['price'] <= 500000 
        && $product['stock'] > 0;
}));

 

실전 예제 2: 사용자 권한 필터링

사용자 목록에서 특정 권한을 가진 사용자만 필터링하는 경우다.

$users = [
    ['id' => 1, 'name' => 'Alice', 'role' => 'admin', 'active' => true],
    ['id' => 2, 'name' => 'Bob', 'role' => 'user', 'active' => true],
    ['id' => 3, 'name' => 'Charlie', 'role' => 'moderator', 'active' => false],
    ['id' => 4, 'name' => 'Diana', 'role' => 'admin', 'active' => true],
];

// 활성 상태인 관리자만 필터링
$activeAdmins = array_filter($users, function($user) {
    return $user['role'] === 'admin' && $user['active'] === true;
});

foreach ($activeAdmins as $admin) {
    echo $admin['name'] . ' (' . $admin['id'] . ')' . PHP_EOL;
}
// 출력:
// Alice (1)
// Diana (4)

 

array_filter와 array_map 조합하기

필터링 후 데이터 변환이 필요한 경우가 있다. 이때 array_map과 조합하면 강력하다.

✗ 잘못된 코드 (두 번의 반복 처리)
$orders = [
    ['id' => 1, 'total' => 50000, 'status' => 'completed'],
    ['id' => 2, 'total' => 75000, 'status' => 'pending'],
    ['id' => 3, 'total' => 30000, 'status' => 'completed'],
];

// 먼저 필터링
$completed = [];
foreach ($orders as $order) {
    if ($order['status'] === 'completed') {
        $completed[] = $order;
    }
}

// 다시 매핑
$ids = [];
foreach ($completed as $order) {
    $ids[] = $order['id'];
}
// 비효율적인 이중 반복

✓ 올바른 코드 (array_filter + array_map)

$ids = array_map(
    function($order) { return $order['id']; },
    array_filter($orders, function($order) {
        return $order['status'] === 'completed';
    })
);

print_r($ids);
// Array ( [0] => 1 [2] => 3 )

// array_values로 인덱스 정렬
$ids = array_values($ids);
// Array ( [0] => 1 [1] => 3 )

함수형 체인을 사용하면 코드가 선언적이 되고 부작용이 줄어든다.

 

주의사항과 흔한 실수

✗ 잘못된 방법: 콜백에서 배열 수정하기

$data = ['a' => 1, 'b' => 2, 'c' => 3];

// 절대 금지: 콜백 내에서 배열을 수정하려고 시도
array_filter($data, function(&$value, $key) {
    if ($key === 'b') {
        unset($value); // 이렇게 하면 예상과 다른 결과 발생
    }
    return true;
}, ARRAY_FILTER_USE_BOTH);

// array_filter는 새로운 배열을 반환하므로 원본 배열은 변하지 않음

✓ 올바른 방법: 필터링 결과를 새 변수에 저장

$data = ['a' => 1, 'b' => 2, 'c' => 3];

// 정확한 방식
$filtered = array_filter($data, function($value, $key) {
    return $key !== 'b';
}, ARRAY_FILTER_USE_BOTH);

print_r($filtered);
// Array ( [a] => 1 [c] => 3 )

array_filter()는 새로운 배열을 반환하는 함수이지, 원본 배열을 수정하지 않는다. 이 점을 착각해서 콜백 내에서 원본을 건드리려고 하는 실수가 자주 발생한다.

또 하나 주의할 점은 인덱스가 유지된다는 것이다. 결과를 JSON으로 인코딩하거나 순차적 접근이 필요하면 array_values()를 반드시 붙여야 한다.

$nums = [1, 2, 3, 4, 5];
$even = array_filter($nums, fn($n) => $n % 2 === 0);

echo json_encode($even);
// 출력: {"1":2,"3":4}

echo json_encode(array_values($even));
// 출력: [2,4]

 

PHP 7.4+ Arrow Function으로 간결하게

PHP 7.4부터는 단일 표현식 콜백에 화살표 함수를 쓸 수 있다.

$numbers = [1, 2, 3, 4, 5, 6];

// 기존 방식
$even = array_filter($numbers, function($n) {
    return $n % 2 === 0;
});

// 화살표 함수 (훨씬 간결)
$even = array_filter($numbers, fn($n) => $n % 2 === 0);

// 키 조건도 간단히
$users = ['admin_1', 'user_2', 'admin_3', 'guest_1'];
$admins = array_filter(
    $users,
    fn($user) => str_starts_with($user, 'admin')
);

화살표 함수는 단일 값을 반환하는 간단한 콜백에 최적화되어 있다. 복잡한 로직이 필요하면 일반 function을 쓰는 게 낫다.

 

성능: array_filter vs 수동 반복

array_filter()는 내부적으로 최적화된 C 코드로 동작하므로 PHP 반복문보다 빠르다. 다만 매우 큰 배열(수백만 요소)에서는 메모리 사용량을 고려해야 한다. array_filter()는 새로운 배열을 생성하므로, 필터링 결과가 원본 크기의 절반 이상이면 메모리가 일시적으로 1.5배 이상 필요할 수 있다. 이런 경우는 Generator 기반 필터링이 나을 수 있다.

// Generator로 메모리 효율적 필터링
function filterGenerator($array, $callback) {
    foreach ($array as $key => $value) {
        if ($callback($value, $key)) {
            yield $key => $value;
        }
    }
}

$filtered = filterGenerator($largeArray, fn($item) => $item['active']);
foreach ($filtered as $item) {
    // 한 번에 하나씩만 메모리에 로드됨
}

 

마무리

array_filter()는 배열 필터링의 핵심 함수다. 단순해 보이지만 mode 인자와 콜백 활용법을 정확히 알면 복잡한 데이터 처리도 간결하게 할 수 있다. 특히 화살표 함수와 조합하면 선언적이고 읽기 쉬운 코드를 작성할 수 있다. 이 글에서 소개한 패턴들(가격 필터링, 권한 필터링, 값과 키 동시 필터링)을 실무에 적용하면, 반복문 없이도 안전하고 효율적인 배열 처리가 가능해질 것이다.