PHP · 심층 가이드
PHP 함수형 완전 정리
배열의 값 의미론을 이용한 불변 업데이트, 함수 합성과 부분 적용, 제너레이터 지연 평가로 PHP에서 부작용 없는 데이터 변환 코드를 작성하는 방법을 다룹니다.
PHP는 함수형 언어가 아니지만 함수형 스타일과 궁합이 나쁘지 않습니다. 배열이 값으로 복사되기 때문에 $new = [...$old, 'x' => 1] 같은 표현이 원본을 건드리지 않고, 그래서 불변 업데이트를 위해 별도 라이브러리가 필요 없습니다. 부족한 쪽은 문법 편의입니다. 파이프 연산자도, 커링도, 표준 compose도 없어서 여러 변환을 이어 붙이려면 헬퍼를 직접 만들어야 합니다. 이 가이드의 절반은 그 빠진 조각을 손으로 채우는 연습입니다.
배열 불변 업데이트와 순수 포매터 함수로 부작용 없는 함수의 모양을 잡은 다음, map filter 체이닝에서 중첩 호출이 읽기 어려워지는 순간을 체감하고 함수 합성 헬퍼로 풀어내는 순서를 권합니다. 인자를 미리 고정하는 기법은 커링 흉내 내기와 부분 적용으로 prefix 함수 만들기가 같은 아이디어를 두 각도에서 보여 줍니다. 집계 쪽은 reduce 로 그룹핑이 축이고, 데이터가 커지면 제너레이터 파이프라인과 지연 평가로 큰 목록 처리로 같은 로직을 스트리밍으로 옮기게 됩니다.
체이닝의 대가는 메모리입니다. array_map과 array_filter는 매 단계마다 완전한 새 배열을 만들기 때문에, 단계가 네 개면 원본 크기의 배열이 네 벌 존재하는 순간이 생깁니다. 수십만 행짜리 CSV에서는 이것만으로 메모리 한도를 넘습니다. 실무에서 또 하나 성가신 건 인자 순서로, array_map(콜백, 배열)과 array_filter(배열, 콜백)이 서로 반대입니다. 합성 헬퍼를 만들 때 이 불일치를 흡수하는 얇은 래퍼를 먼저 정의해 두면 이후 코드가 훨씬 조용해집니다.
01배열 불변 업데이트
원본을 직접 바꾸지 않고 새 배열을 만들어 갱신하는 불변 업데이트 예제입니다.
PHP code
<?php
$user = ['name' => 'mida', 'role' => 'member'];
$updated = [...$user, 'role' => 'admin'];
print_r([$user, $updated]);함수형 스타일에서는 부수 효과를 줄이고 입력과 출력을 분리하는 관점으로 읽으면 훨씬 명확해집니다.
원본 데이터를 직접 변경하면 함수형 스타일의 장점이 사라지고 디버깅이 어려워집니다.
02순수 포매터 함수
입력만으로 출력이 결정되는 순수 함수를 만드는 예제입니다.
PHP code
<?php
function formatPrice(int $amount): string {
return number_format($amount) . ' KRW';
}
echo formatPrice(120000);함수형 스타일에서는 부수 효과를 줄이고 입력과 출력을 분리하는 관점으로 읽으면 훨씬 명확해집니다.
원본 데이터를 직접 변경하면 함수형 스타일의 장점이 사라지고 디버깅이 어려워집니다.
03map filter 체이닝
filter와 map을 조합해 데이터를 단계적으로 변환하는 함수형 예제입니다.
PHP code
<?php
$numbers = [1, 2, 3, 4, 5];
$result = array_map(
fn (int $n): int => $n * 10,
array_filter($numbers, fn (int $n): bool => $n % 2 === 1)
);
print_r($result);함수형 스타일에서는 부수 효과를 줄이고 입력과 출력을 분리하는 관점으로 읽으면 훨씬 명확해집니다.
원본 데이터를 직접 변경하면 함수형 스타일의 장점이 사라지고 디버깅이 어려워집니다.
04함수 합성 헬퍼
여러 함수를 하나의 파이프라인으로 조합하는 compose 예제입니다.
PHP code
<?php
function compose(callable ...$functions): Closure {
return function (mixed $value) use ($functions): mixed {
foreach (array_reverse($functions) as $fn) {
$value = $fn($value);
}
return $value;
};
}
$pipeline = compose('trim', 'strtolower');
echo $pipeline(' PHP ');함수형 스타일에서는 부수 효과를 줄이고 입력과 출력을 분리하는 관점으로 읽으면 훨씬 명확해집니다.
원본 데이터를 직접 변경하면 함수형 스타일의 장점이 사라지고 디버깅이 어려워집니다.
05커링 흉내 내기
인자를 나눠 받아 새 함수를 돌려주는 커링 스타일 예제입니다.
PHP code
<?php
function equals(string $expected): Closure {
return fn (string $actual): bool => $expected === $actual;
}
$isPhp = equals('php');
var_dump($isPhp('php'));함수형 스타일에서는 부수 효과를 줄이고 입력과 출력을 분리하는 관점으로 읽으면 훨씬 명확해집니다.
원본 데이터를 직접 변경하면 함수형 스타일의 장점이 사라지고 디버깅이 어려워집니다.
06Option 비슷한 null 처리
null 여부에 따라 변환을 생략하는 Option 유사 헬퍼 예제입니다.
PHP code
<?php
function mapNullable(mixed $value, callable $mapper): mixed {
return $value === null ? null : $mapper($value);
}
echo mapNullable('php', 'strtoupper');함수형 스타일에서는 부수 효과를 줄이고 입력과 출력을 분리하는 관점으로 읽으면 훨씬 명확해집니다.
원본 데이터를 직접 변경하면 함수형 스타일의 장점이 사라지고 디버깅이 어려워집니다.
07제너레이터 파이프라인
generator를 조합해 지연 평가 파이프라인을 만드는 예제입니다.
PHP code
<?php
function numbers(): Generator {
yield from range(1, 5);
}
function even(Generator $source): Generator {
foreach ($source as $value) {
if ($value % 2 === 0) {
yield $value;
}
}
}
foreach (even(numbers()) as $value) {
echo $value . PHP_EOL;
}함수형 스타일에서는 부수 효과를 줄이고 입력과 출력을 분리하는 관점으로 읽으면 훨씬 명확해집니다.
원본 데이터를 직접 변경하면 함수형 스타일의 장점이 사라지고 디버깅이 어려워집니다.
08reduce 로 그룹핑
reduce를 사용해 분류별 그룹핑 결과를 만드는 예제입니다.
PHP code
<?php
$rows = [
['category' => 'backend', 'title' => 'PHP'],
['category' => 'backend', 'title' => 'Go'],
['category' => 'frontend', 'title' => 'TypeScript'],
];
$grouped = array_reduce($rows, function (array $carry, array $row): array {
$carry[$row['category']][] = $row['title'];
return $carry;
}, []);
print_r($grouped);함수형 스타일에서는 부수 효과를 줄이고 입력과 출력을 분리하는 관점으로 읽으면 훨씬 명확해집니다.
원본 데이터를 직접 변경하면 함수형 스타일의 장점이 사라지고 디버깅이 어려워집니다.
09match 를 표현식으로 쓰기
상태를 문장보다 표현식으로 돌려받는 함수형 스타일의 match 예제입니다.
PHP code
<?php
function badge(string $level): string {
return match ($level) {
'b' => 'beginner',
'i' => 'intermediate',
'a' => 'advanced',
default => 'unknown',
};
}
echo badge('i');함수형 스타일에서는 부수 효과를 줄이고 입력과 출력을 분리하는 관점으로 읽으면 훨씬 명확해집니다.
원본 데이터를 직접 변경하면 함수형 스타일의 장점이 사라지고 디버깅이 어려워집니다.
10부분 적용으로 prefix 함수 만들기
설정값을 미리 고정해 재사용 가능한 함수를 만드는 예제입니다.
PHP code
<?php
function withPrefix(string $prefix): Closure {
return fn (string $value): string => $prefix . $value;
}
$logPrefix = withPrefix('[php] ');
echo $logPrefix('ready');함수형 스타일에서는 부수 효과를 줄이고 입력과 출력을 분리하는 관점으로 읽으면 훨씬 명확해집니다.
원본 데이터를 직접 변경하면 함수형 스타일의 장점이 사라지고 디버깅이 어려워집니다.
11DTO 리스트 변환
배열 기반 데이터를 DTO 컬렉션으로 변환하는 함수형 매핑 예제입니다.
PHP code
<?php
readonly class UserDto {
public function __construct(public int $id, public string $name) {}
}
$rows = [['id' => 1, 'name' => 'Kim'], ['id' => 2, 'name' => 'Lee']];
$dtos = array_map(fn (array $row): UserDto => new UserDto($row['id'], $row['name']), $rows);
print_r($dtos);함수형 스타일에서는 부수 효과를 줄이고 입력과 출력을 분리하는 관점으로 읽으면 훨씬 명확해집니다.
원본 데이터를 직접 변경하면 함수형 스타일의 장점이 사라지고 디버깅이 어려워집니다.
12결과 객체 비슷한 배열
예외 대신 Result 모양의 배열을 반환하는 함수형 오류 처리 예제입니다.
PHP code
<?php
function divide(int $a, int $b): array {
return $b === 0
? ['ok' => false, 'error' => 'division by zero']
: ['ok' => true, 'value' => $a / $b];
}
print_r(divide(10, 2));함수형 스타일에서는 부수 효과를 줄이고 입력과 출력을 분리하는 관점으로 읽으면 훨씬 명확해집니다.
원본 데이터를 직접 변경하면 함수형 스타일의 장점이 사라지고 디버깅이 어려워집니다.
13함수 목록 순차 적용
함수 리스트를 줄줄이 적용해 문자열을 변환하는 예제입니다.
PHP code
<?php
$steps = [
'trim',
'strtolower',
fn (string $value): string => str_replace(' ', '-', $value),
];
$value = array_reduce($steps, fn (string $carry, callable $step): string => $step($carry), ' Hello PHP ');
echo $value;함수형 스타일에서는 부수 효과를 줄이고 입력과 출력을 분리하는 관점으로 읽으면 훨씬 명확해집니다.
원본 데이터를 직접 변경하면 함수형 스타일의 장점이 사라지고 디버깅이 어려워집니다.
14지연 평가로 큰 목록 처리
큰 배열을 한 번에 메모리에 올리지 않고 generator로 지연 처리하는 예제입니다.
PHP code
<?php
function lines(int $count): Generator {
for ($i = 1; $i <= $count; $i++) {
yield "row-{$i}";
}
}
foreach (lines(3) as $line) {
echo $line . PHP_EOL;
}함수형 스타일에서는 부수 효과를 줄이고 입력과 출력을 분리하는 관점으로 읽으면 훨씬 명확해집니다.
원본 데이터를 직접 변경하면 함수형 스타일의 장점이 사라지고 디버깅이 어려워집니다.
정리하며
- 원본을 수정하는 대신 스프레드로 새 배열을 만들어 함수의 부작용을 없앱니다.
- array_map과 array_filter의 인자 순서가 반대이니 래퍼로 통일해 두고 씁니다.
- 단계가 많은 변환은 배열 체이닝 대신 제너레이터 파이프라인으로 메모리를 고정합니다.
- 그룹핑, 집계, 분할은 reduce 하나로 표현하면 중간 배열을 만들지 않습니다.
더 깊이 들어가고 싶다면 PHP 학습 라이브러리에서 다른 주제 가이드를 이어서 보거나, 언어 비교에서 같은 개념이 다른 언어에서 어떻게 표현되는지 확인해 보세요.