(PHP) 디렉토리 내 모든 폴더/파일 뒤지기

엽토군·2023년 12월 15일
0

1분 코드 스니펫

목록 보기
9/10

tl;dr

다음 3개 SPL 클래스를 활용하면 된다.

  1. RecursiveIteratorIterator
  2. RecursiveDirectoryIterator
  3. CallbackFilterIterator

예제

예컨대 특정 경로 아래 모든 .php 파일 갯수를 세려면 어떻게 해야 될까?
차선책을 먼저 보고, 그 다음 최선책을 보기로 한다.

차선책

전통적으로 PHP 커뮤니티는 이 문제를 이런 식으로 접근했다.

// variated from: https://stackoverflow.com/a/24784144
function countPhpInDir($dir) {
	$count = 0;
    $files = scandir($dir);
    foreach ($files as $key => $value) {
        $path = realpath($dir . DIRECTORY_SEPARATOR . $value);
        if (!is_dir($path) && substr($path, -4) === '.php') {
        	$count++;
        } else if ($value != "." && $value != "..") {
        	$count += countPhpInDir($path);
        }
    }
    return $count;
}
echo "total " . countPhpInDir(경로) . " PHP files";

'접근'이라기엔 지나치게 문자적인 해결이다. 그냥 재귀함수 삽 하나를 들고 폴더로 들어가서, 일일이 삽질해 퍼내 가며 직접 찾는다.
창피할 정도로 단순 무식한 구현이지만, PHP 업계는 꽤 오랫동안 이런 수제 코드 조각들로 이 문제를 해결해 왔다. 어쨌든 굴러는 가니까.

최선책

상기 코드와 똑같은 것을, 다음과 같이 2가지 클래스만으로 구현할 수 있다.

$dir   = new RecursiveDirectoryIterator(경로);
$files = new RecursiveIteratorIterator($dir);

$count = 0;
foreach ($files as $file) {
	if ($file->getExtension() === 'php') {
    	$count++;
    }
}
echo "total $count PHP files";

확실히 차선책보다는 낫다.

  • 엄청 깔끔하다. 무슨 일이 일어나고 있는지가 한눈에 보인다.
  • SplFileInfo 클래스를 쓸 수 있어서 매우 편리하다.

그런데 괜한 욕심이 더 난다. 저 foreach 루프는 최선일까? 나는 단지 PHP 파일만 골라서 보고 싶은 거 아닌가? 더 깨끗하고 논리적인 방법이 없을까?
이때 3번째 클래스를 사용한다.

$dir = new RecursiveDirectoryIterator(경로);
$php = new RecursiveCallbackFilterIterator($dir, function ($current, $key, $iterator) {
    return $iterator->hasChildren() // $current가 Iterator라면 pass
    	|| $current->getExtension() === 'php';
});
$files = new RecursiveIteratorIterator($php);

$count = iterator_count($files);
echo "total $count PHP files";

아까보다 좀더 낫다.

  • 인지 비용이 좀더 절감된다. '참'이 될 조건만 기술한 뒤 iterator_count() 하는 코드로 바뀌어 있다. (적어도 겉으로는) 실제로 foreach 순회를 할 필요가 없다.
  • 기능 단위가 분절된다. 예컨대 $php 반복자만 따로 라이브러리로 분리해서 재사용하는 것이 가능할 것이다.

사용된 SPL 클래스 3가지를 간단히 해설하자면 다음과 같다.

  • CallbackFilterIterator
    • 반복 대상 중 콜백이 참이 되는 것만 반복시킨다.
  • RecursiveDirectoryIterator
    • 특정 디렉토리에서 파일이 발견되면 SplFileInfoyield한다.
    • 특정 디렉토리에서 디렉토리가 발견되면 RecursiveDirectoryIteratoryield한다.
  • RecursiveIteratorIterator
    • 반복자가 아닌 것이 나올 때까지 반복자를 반복한다.

이론이 좀 어렵다면 그냥 위의 모범답안 2개를 눈과 손에 익혀 두면 된다.

코멘트

흔히들 PHP를 생각할 때 foreach if else 루프 지옥과 날코딩의 짬뽕으로 "어쨌든 굴러는 가는" 구현체를 생각한다.
하지만 그건 그간의 PHP 사용 관행이었을 뿐이고, 알고 보면, Standard PHP Library는 생각보다 방대하다.

SPL이 아닌 '기본 내장 구현체'나 일반적으로 동봉되는 extensions들까지 합치면, 쓸모 있는 기성 구현체는 더 많다.

오늘날에 PHP 엔지니어 명함을 내밀고 다니자면 이 정도는 가급적 학습해서 활용하려고 해야 할 것이다. 바퀴를 다시 만들 필요는 없고, 어떤 바퀴들은 우리가 그간 찾지 않았다 뿐이지 실은 분명 거기 있었기 때문이다. 우리가 그간 그 바퀴들을 찾으려 하지 않았던 건, 어쩌면 바퀴를 다시 만드는 비용이 너무 싼 나머지, 각자 허술한 바퀴를 너무 자주 너무 많이 너무 오랫동안 만드는 데 안주해 버렸기 때문인지도 모른다. 그 바퀴들도 구르기야 하겠지만, 기왕이면 그런 엔지니어링 말고, 주어진 언어의 사양을 활용할 줄 아는 엔지니어링을 하려고 하자.

profile
5년차 PHP 개발자입니다.

1개의 댓글

comment-user-thumbnail
2024년 4월 24일

php 5 에서는 spl 이 scandir , glob 이런거 보다 느려서 안썻죠.
spl 배열같은것도 기본배열보다 느리고 구현도 이상하고
spl 패키지들 몽땅싸잡아서 끔찍하다고 욕도먹고... ㅠ
php 7~8 사이에 개선이 있어서 괜찮은거같은데 기존코드를 엎진않아서 쓰는데는
많이 못봤어요.

답글 달기