파일 업로드 처리나 서버 리소스 관리 시 특정 파일이 실제로 존재하는지 먼저 확인해야 한다. 대부분의 개발자는 file_exists()나 is_file()을 아무 생각 없이 쓰거나, 둘의 차이를 정확히 모르고 상황에 맞지 않는 함수를 선택해서 버그를 만든다. 심하면 디렉토리인데 is_file()로 체크해서 false가 나오는 게 당연한데 마치 파일이 없다고 착각하는 경우도 많다. 이번에는 is_file()과 file_exists()가 정확히 뭐 하는 건지, 언제 어떤 함수를 써야 하는지 완벽하게 정리해서 소개하겠다.

 

1단계. 기초 개념 차이 정확히 알기

file_exists()는 파일과 디렉토리 모두 존재 여부를 확인한다. 경로가 파일이든 폴더든 상관없이 그 경로에 뭔가 있으면 true를 반환한다. is_file()은 훨씬 더 까다롭다. 해당 경로가 존재할 뿐만 아니라 그것이 정확히 파일이어야만 true를 반환한다. 디렉토리가 있으면 무조건 false다.

다시 말해, 파일이 있으면 둘 다 true를 반환하지만, 디렉토리가 있으면 file_exists()는 true인데 is_file()은 false를 반환한다. 이 차이를 모르고 코드를 짜면 디렉토리를 파일로 접근하려고 할 때 서류를 내지 않고 지나갈 수 있다.

 

2단계. 동작 흐름 비교
상황 file_exists() is_file()
파일 존재 true true
디렉토리 존재 true false
경로 없음 false false
심볼릭 링크(파일) true true
심볼릭 링크(디렉토리) true false

 

3단계. 실전 예제로 차이 직접 확인하기

 

예제 1. 파일이 있는 경우
<?php
// 파일: /var/www/html/test.txt 존재함

✗ 잘못된 코드
if (file_exists('/var/www/html/test.txt')) {
    echo "파일이 존재해요";
} else {
    echo "파일이 없어요";
}
// 결과: "파일이 존재해요" (맞음, 근데 디렉토리인지 확실하지 않음)

✓ 올바른 코드
if (is_file('/var/www/html/test.txt')) {
    echo "파일이 확실히 존재해요";
} else {
    echo "파일이 없거나 디렉토리예요";
}
// 결과: "파일이 확실히 존재해요"
?>

 

예제 2. 디렉토리가 있는 경우
<?php
// 디렉토리: /var/www/html/uploads/ 존재함

✗ 잘못된 코드
if (file_exists('/var/www/html/uploads')) {
    // 이 블록에 진입
    $fp = fopen('/var/www/html/uploads', 'r');
    $content = fread($fp, 1024);
    fclose($fp);
    // 결과: 디렉토리는 파일이 아니므로 fopen이 경고 또는 실패
}

✓ 올바른 코드
if (is_dir('/var/www/html/uploads')) {
    echo "디렉토리가 존재해요";
} else if (is_file('/var/www/html/uploads')) {
    echo "파일이 존재해요";
} else {
    echo "뭐가 있는지 모르겠어요";
}
// 결과: "디렉토리가 존재해요"
?>

 

예제 3. 실무에서 파일 업로드 검증
<?php
// 사용자가 업로드한 파일 처리

✗ 잘못된 코드
if (file_exists($_FILES['attachment']['tmp_name'])) {
    // 임시 파일이 존재한다고 해서 곧바로 처리
    // 근데 이게 디렉토리라면?(드물지만 가능)
    move_uploaded_file($_FILES['attachment']['tmp_name'], '/var/www/uploads/' . $_FILES['attachment']['name']);
}

✓ 올바른 코드
if (is_file($_FILES['attachment']['tmp_name']) && is_uploaded_file($_FILES['attachment']['tmp_name'])) {
    // 명확하게 파일이고, 실제 업로드된 파일인지 확인
    move_uploaded_file($_FILES['attachment']['tmp_name'], '/var/www/uploads/' . $_FILES['attachment']['name']);
} else {
    echo "유효한 파일이 아닙니다";
}
?>

 

4단계. 주의사항과 흔한 실수

 

✗ 실수 1. 권한 문제로 false가 나와도 파일이 없다고 착각

file_exists()와 is_file()은 파일이 존재하더라도 읽기 권한이 없으면 false를 반환할 수 있다. 특히 다른 사용자 소유의 파일이면 권한 체크가 먼저 작동한다.

<?php
// 파일은 존재하지만 권한이 없는 경우
// /root/secret.txt 는 존재하지만 www-data 사용자가 읽을 수 없음

var_dump(is_file('/root/secret.txt')); // false (권한 부족)
var_dump(file_exists('/root/secret.txt')); // false (권한 부족)

// 파일이 없는 건지 권한이 없는 건지 구분하려면
if (is_file($path)) {
    echo "파일 존재, 읽기 권한 있음";
} else if (file_exists(dirname($path))) {
    echo "디렉토리는 있는데 파일이 없거나 권한 없음";
} else {
    echo "디렉토리 자체가 없음";
}
?>

 

✗ 실수 2. 심볼릭 링크 다루기 실수

심볼릭 링크는 원본이 존재하지 않으면 깨진 링크가 된다. file_exists()는 링크를 따라가서 원본 존재 여부를 확인하지만, is_link()는 링크 자체의 존재만 본다.

<?php
// /var/www/html/link.txt → /var/www/data/real.txt (원본이 없음, 깨진 링크)

var_dump(is_link('/var/www/html/link.txt')); // true (링크 자체 존재)
var_dump(file_exists('/var/www/html/link.txt')); // false (원본 없음)
var_dump(is_file('/var/www/html/link.txt')); // false (원본이 파일이 아니거나 없음)

// 깨진 심볼릭 링크 정리하려면
if (is_link('/var/www/html/link.txt') && !file_exists('/var/www/html/link.txt')) {
    echo "깨진 심볼릭 링크 발견";
    unlink('/var/www/html/link.txt');
}
?>

 

✗ 실수 3. 경로 자체가 상대 경로일 때 혼동

상대 경로는 PHP 스크립트가 실행되는 현재 디렉토리를 기준으로 한다. 예상과 다르게 작동할 수 있다.

<?php
// PHP 스크립트: /var/www/html/index.php
// 현재 작업 디렉토리: /var/www/html

var_dump(is_file('test.txt')); // /var/www/html/test.txt 체크
var_dump(is_file('./test.txt')); // /var/www/html/test.txt 체크 (같음)
var_dump(is_file('/absolute/path/test.txt')); // 절대 경로

// 안전하게 하려면 항상 절대 경로 사용
var_dump(is_file(__DIR__ . '/test.txt'));
?>

 

5단계. 마무리와 실무 팁

파일과 디렉토리를 정확히 구분하는 것은 서버 리소스 접근 오류를 원천 차단하는 일이다. file_exists()는 뭔가 있는지만 보고, is_file()은 정확히 파일인지 확인한다는 단순한 원칙을 기억하자. 실무에서는 업로드 처리나 설정 파일 읽기 전에 항상 is_file()로 파일 존재를 먼저 확인하는 습관이 중요하다. 여기에 is_readable()을 함께 쓰면 권한도 검증할 수 있다. 이 글의 예제 3번 업로드 검증 코드를 참고해서 실제 프로젝트에 적용하면, 파일 시스템 접근 오류로 인한 장애를 크게 줄일 수 있을 것이다.