PHP로 데이터베이스 조회 결과나 API 응답 데이터를 다루다 보면, 특정 필드(Key)를 기준으로 배열 데이터를 그룹화해야 하는 상황을 한 번쯤 경험해봤을 것이다.
다만 PHP 표준 함수 라이브러리에는 array_column이나 array_map 등은 있지만, 정작 실무에서 가장 많이 필요한 array_group_by() 형태의 그룹화 함수가 제공되지 않아 매번 foreach 문을 지저분하게 작성하는 경우가 많다.
이번에는 PHP에서 배열 데이터를 효율적으로 묶어주는 array_group_by() 서포트 함수가 정확히 무엇이고 왜 필요한지, 그리고 다중 키(Multi-level) 그룹화까지 지원하는 실용적인 서포트 함수를 어떻게 작성하는지 완벽하게 정리해서 소개하겠다.

 

1. PHP 배열 그룹화의 개념과 필요성

데이터베이스 쿼리 결과(2차원 배열)를 웹 화면에 출력할 때, '카테고리별 상품 목록'이나 '부서별 임직원 리스트'처럼 특정 기준 키를 바탕으로 재구성해야 할 때가 있다.
SQL 문에서 GROUP BY를 사용하는 것과 비슷하지만, 웹 애플리케이션 단에서 이미 가져온 2차원 배열을 메모리 내에서 재구성해야 하는 경우 커스텀 그룹화 함수가 절실히 필요해진다.

아래 표는 단일 평면 배열 구조와 기준 키로 그룹화된 배열 구조의 차이를 비교한 것이다.

구분원본 배열 (Flat Array)그룹화 배열 (Grouped Array)
구조1차원 객체/배열의 연속적인 목록특정 키 값을 기준 Key로 갖는 다차원 배열
접근성전체 순회(O(N))로 특정 그룹 검색그룹 Key로 즉시 접근(O(1)) 가능
가독성뷰(View) 단에서 조건문 반복 필요템플릿 엔진에서 중첩 loop로 깔끔히 출력

 

2. 일반적인 처리 방식과 서포트 함수의 비교

대부분의 개발자들은 습관적으로 매번 foreach 문을 작성하여 임시 배열에 결과를 채워 넣는다.
하지만 프로젝트 규모가 커지거나 여러 곳에서 동일한 방식의 그룹화가 필요한 경우, 중복 코드가 늘어나고 유지보수가 매우 까다로워진다.

 

일반적인 foreach 방식 (비효율적)

✗ 잘못된 작성 방식 (매번 일회성 코드를 중복 작성하는 경우)

<?php
$products = [
    ['category' => '전자제품', 'name' => '노트북', 'price' => 1500000],
    ['category' => '전자제품', 'name' => '마우스', 'price' => 30000],
    ['category' => '식품', 'name' => '사과', 'price' => 5000],
];

// 매번 빈 배열을 선언하고 조건문으로 키 존재 여부를 확인해야 함
$grouped = [];
foreach ($products as $item) {
    $key = $item['category'];
    if (!isset($grouped[$key])) {
        $grouped[$key] = [];
    }
    $grouped[$key][] = $item;
}
?>

이러한 방식은 단일 키 그룹화에는 작동하지만, 중첩 키(예: 카테고리별 -> 제조사별) 처리가 불가능하며 코드의 재사용성이 현저히 떨어진다.

 

3. 실전 서포트 함수 구현 - array_group_by()

단일 키뿐만 아니라 여러 개의 키를 배열로 전달하면 N차원 그룹화까지 지원하는 유연한 서포트 함수를 만들어보자.
익명 함수(Closure)나 익스프레션을 전달받을 수 있도록 설계하면 훨씬 더 강력한 활용이 가능해진다.

 

✓ 재사용 가능한 array_group_by() 서포트 함수
<?php
if (!function_exists('array_group_by')) {
    /**
     * 배열을 특정 키(들) 또는 콜백 함수 결과에 따라 그룹화하는 서포트 함수
     *
     * @param array $array 그룹화할 원본 2차원 배열
     * @param string|int|Closure|array $key 그룹 기준이 될 키 또는 콜백 함수
     * @return array 그룹화된 배열
     */
    function array_group_by(array $array, $key)
    {
        if (!is_string($key) && !is_int($key) && !is_float($key) && !$key instanceof Closure && !is_array($key)) {
            trigger_error('array_group_by(): key는 문자열, 숫자, 콜백 또는 배열이어야 합니다.', E_USER_WARNING);
            return null;
        }

        // 다중 키 그룹화를 위한 재귀 처리
        if (is_array($key)) {
            if (empty($key)) {
                return $array;
            }

            $currentKey = array_shift($key);
            $grouped = array_group_by($array, $currentKey);

            if (!empty($key)) {
                foreach ($grouped as $subKey => $subArray) {
                    $grouped[$subKey] = array_group_by($subArray, $key);
                }
            }

            return $grouped;
        }

        $result = [];

        foreach ($array as $item) {
            $groupKey = null;

            if ($key instanceof Closure) {
                $groupKey = $key($item);
            } elseif (is_object($item) && isset($item->{$key})) {
                $groupKey = $item->{$key};
            } elseif (is_array($item) && isset($item[$key])) {
                $groupKey = $item[$key];
            } else {
                $groupKey = '_undefined_';
            }

            $result[$groupKey][] = $item;
        }

        return $result;
    }
}
?>

 

실전 사용예제 및 실행 결과
<?php
$users = [
    ['team' => '개발팀', 'role' => '백엔드', 'name' => '김철수'],
    ['team' => '개발팀', 'role' => '프론트엔드', 'name' => '이영희'],
    ['team' => '개발팀', 'role' => '백엔드', 'name' => '박민수'],
    ['team' => '디자인팀', 'role' => 'UI/UX', 'name' => '최지은'],
];

// 1. 단일 키 그룹화
$byTeam = array_group_by($users, 'team');

// 2. 다중 키(중첩) 그룹화
$byTeamAndRole = array_group_by($users, ['team', 'role']);

print_r($byTeamAndRole);
?>

위 코드의 실행 결과는 다음과 같이 계층적으로 깔끔하게 구성된다.

Array
(
    [개발팀] => Array
        (
            [백엔드] => Array
                (
                    [0] => Array ([team] => 개발팀, [role] => 백엔드, [name] => 김철수)
                    [1] => Array ([team] => 개발팀, [role] => 백엔드, [name] => 박민수)
                )
            [프론트엔드] => Array
                (
                    [0] => Array ([team] => 개발팀, [role] => 프론트엔드, [name] => 이영희)
                )
        )
    [디자인팀] => Array
        (
            [UI/UX] => Array
                (
                    [0] => Array ([team] => 디자인팀, [role] => UI/UX, [name] => 최지은)
                )
        )
)

 

4. 주의사항 및 흔한 실수

array_group_by() 서포트 함수를 활용할 때 흔히 발생하는 실수와 올바른 대처 방안을 정리했다.

  • 존재하지 않는 키 참조: 배열 요소에 그룹 기준 키가 존재하지 않는 경우 Notice나 Warning이 발생하지 않도록 기본값 처리(예: _undefined_)를 해주어야 한다.
  • 객체 및 배열 혼용: DB ORM(예: Eloquent, Doctrine) 데이터는 객체로 넘어오고 일반 쿼리문은 배열로 넘어올 수 있으므로, 두 구조 모두 대응하도록 검사해야 한다.
  • 대용량 데이터 메모리 오버헤드: 복사본 배열을 새로 생성하므로, 수십만 건 이상의 초대형 배열을 다룰 때는 메모리 임계값을 초과할 수 있다.

 

✗ 잘못된 예시: 데이터 타입 미검증 및 에러 발생
<?php
// 대상 키가 없는 항목이 포함되어 있으면 오작동 원인이 됨
$data = [
    ['id' => 1, 'type' => 'A'],
    ['id' => 2], // 'type' 키 누락!
];

// 조건 검사가 없는 단순 코드는 연관키 유실이나 에러를 초래함
$result = [];
foreach ($data as $row) {
    $result[$row['type']][] = $row; // Undefined index 경고 발생
}
?>

 

✓ 올바른 예시: 안전한 키 검사 및 디폴트 파티셔닝
<?php
// 서포트 함수 내부처럼 isset() 검사 및 타입 확인을 거쳐 예외를 방지함
$groupKey = isset($item[$key]) ? $item[$key] : 'ETC';
$result[$groupKey][] = $item;
?>

 

5. 마무리 및 핵심 요약

PHP 배열 그룹화 처리 문제는 실무 백엔드 개발 과정에서 반복적으로 마주치는 대표적인 패턴이다. 매번 지저분하게 반복문을 짜는 대신, 검증된 서포트 함수 하나를 공통 헬퍼 클래스에 등록해두면 코드의 가독성과 개발 생산성이 획기적으로 향상된다. 작성한 array_group_by() 함수를 프로젝트 공통 헬퍼 파일에 추가하여 다음 개발 시 직접 적용해보면 훨씬 깔끔하고 안전한 배열 제어를 경험할 수 있을 것이다.