파일 업로드 폼을 만들었는데 $_FILES 배열이 텅 비어있다. 백엔드 코드는 멀쩡한데 왜 자꾸 파일이 안 넘어오는 걸까. 대부분의 개발자들은 이 상황에서 PHP 코드만 디버깅하다가 며칠을 낭비한다. 사실 $_FILES 배열이 비는 원인은 PHP 설정, HTML 폼 속성, 서버 리소스 등 여러 곳에 숨어있다. 이번에는 실무에서 직접 겪은 $_FILES 배열 비는 이유 5가지와 각각의 완벽한 해결책을 정리해서 소개하겠다.
가장 흔한 실수다. 파일 업로드를 받으려면 HTML 폼에 반드시 enctype 속성을 지정해야 한다. 이걸 빠뜨리면 아무리 PHP 코드가 완벽해도 $_FILES는 항상 빈 배열이다.
✗ 잘못된 코드
<form method="POST" action="upload.php">
<input type="file" name="document">
<button type="submit">업로드</button>
</form>
이 경우 $_FILES는 완전히 비어있다. 브라우저가 파일을 multipart 형식으로 인코딩하지 않기 때문이다.
✓ 올바른 코드
<form method="POST" action="upload.php" enctype="multipart/form-data">
<input type="file" name="document">
<button type="submit">업로드</button>
</form>
enctype="multipart/form-data"를 추가하면 브라우저가 자동으로 파일 데이터를 multipart 형식으로 인코딩해서 전송한다. 이제 $_FILES['document']에 파일 정보가 들어온다.
PHP.ini의 post_max_size 설정보다 큰 파일을 업로드하면, $_FILES뿐 아니라 $_POST도 함께 비워진다. 이것도 $_FILES가 빈 것처럼 보이지만, 실은 요청 자체가 거부된 것이다.
현재 설정 확인
<?php
echo "post_max_size: " . ini_get('post_max_size');
echo "upload_max_filesize: " . ini_get('upload_max_filesize');
?>
일반적인 기본값은 post_max_size=8M, upload_max_filesize=2M이다.
✗ 잘못된 상황
<?php
// 10MB 파일 업로드 시도했는데 post_max_size가 8M로 설정됨
var_dump($_FILES); // array(0) { }
var_dump($_POST); // array(0) { }
// 둘 다 비어있음
?>
✓ 올바른 해결법 (php.ini 수정)
post_max_size = 100M
upload_max_filesize = 50M
memory_limit = 256M
post_max_size는 upload_max_filesize보다 항상 커야 한다(multipart 헤더와 필드도 포함되므로). memory_limit도 충분히 높여야 대용량 파일 처리 중 메모리 오류가 안 난다.
PHP 임시 업로드 디렉토리(/tmp 또는 upload_tmp_dir)의 디스크 용량이 부족하거나 쓰기 권한이 없으면, 파일이 이동되지 않는다. 이 경우 $_FILES는 에러 정보를 담고 있다.
✗ 배열은 있지만 에러 확인 안 함
<?php
if ($_FILES['document']['error'] !== UPLOAD_ERR_OK) {
var_dump($_FILES['document']);
// array(5) {
// ["name"] => string(10) "file.pdf"
// ["type"] => string(15) "application/pdf"
// ["tmp_name"] => string(0) ""
// ["error"] => int(6) // UPLOAD_ERR_NO_TMP_DIR
// ["size"] => int(0)
// }
}
?>
error 코드 6은 임시 디렉토리가 없거나 접근 불가라는 뜻이다.
✓ 올바른 디렉토리 권한 설정
chmod 755 /var/www/uploads
chown www-data:www-data /var/www/uploads
또는 php.ini에서 업로드 임시 디렉토리 변경:
upload_tmp_dir = /var/www/uploads/tmp
JavaScript로 폼을 동적으로 처리할 때, input[type=file]에 파일을 선택하지 않고 제출하면 $_FILES는 빈 배열이다. 또는 JavaScript에서 form을 완전히 재구성할 때 file input을 누락하기도 한다.
✗ 잘못된 JavaScript
<form id="uploadForm" enctype="multipart/form-data">
<input type="file" id="fileInput" name="document">
<button type="button" id="submitBtn">업로드</button>
</form>
<script>
document.getElementById('submitBtn').addEventListener('click', function() {
const formData = new FormData();
formData.append('name', 'John'); // 파일 input을 append하지 않음
fetch('upload.php', { method: 'POST', body: formData });
});
</script>
✓ 올바른 JavaScript
<script>
document.getElementById('submitBtn').addEventListener('click', function() {
const fileInput = document.getElementById('fileInput');
const formData = new FormData();
// 파일이 선택되었는지 확인
if (fileInput.files.length === 0) {
alert('파일을 선택하세요');
return;
}
formData.append('document', fileInput.files[0]);
formData.append('name', 'John');
fetch('upload.php', { method: 'POST', body: formData });
});
</script>
매우 드물지만, php.ini에서 file_uploads = Off로 설정되어 있으면 모든 파일 업로드가 거부된다. 공유 호스팅에서 보안상 이렇게 설정하는 경우가 가끔 있다.
확인 방법
<?php
echo ini_get('file_uploads'); // 0 = Off, 1 = On
?>
✓ 올바른 설정 (php.ini 또는 .htaccess)
file_uploads = On
만약 공유 호스팅에서 이 설정을 바꿀 수 없다면, 호스팅 업체에 요청해야 한다.
위의 모든 경우를 대비한 안전한 업로드 검증 함수를 만들어보자.
<?php
function validateFileUpload($fileInputName) {
// 1. 파일이 업로드되었는지 확인
if (!isset($_FILES[$fileInputName])) {
return ['success' => false, 'error' => '파일 입력이 없습니다'];
}
$file = $_FILES[$fileInputName];
// 2. 업로드 에러 코드 확인
if ($file['error'] !== UPLOAD_ERR_OK) {
$errorMessages = [
UPLOAD_ERR_INI_SIZE => 'upload_max_filesize 초과',
UPLOAD_ERR_FORM_SIZE => 'HTML MAX_FILE_SIZE 초과',
UPLOAD_ERR_PARTIAL => '파일이 부분적으로만 업로드됨',
UPLOAD_ERR_NO_FILE => '파일이 선택되지 않음',
UPLOAD_ERR_NO_TMP_DIR => '임시 디렉토리 없음',
UPLOAD_ERR_CANT_WRITE => '디스크 쓰기 실패',
UPLOAD_ERR_EXTENSION => 'PHP 확장 프로그램이 업로드 중단'
];
return ['success' => false, 'error' => $errorMessages[$file['error']] ?? '알 수 없는 에러'];
}
// 3. 임시 파일이 존재하는지 확인
if (!is_uploaded_file($file['tmp_name'])) {
return ['success' => false, 'error' => '업로드된 파일이 아닙니다'];
}
// 4. 파일 크기 확인
$maxSize = 50 * 1024 * 1024; // 50MB
if ($file['size'] > $maxSize) {
return ['success' => false, 'error' => '파일이 너무 큽니다'];
}
// 5. 파일 확장자 확인 (MIME 타입도 검증)
$allowedExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx'];
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $allowedExtensions)) {
return ['success' => false, 'error' => '허용되지 않는 파일 형식입니다'];
}
return ['success' => true, 'file' => $file];
}
// 사용 예
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$result = validateFileUpload('document');
if ($result['success']) {
$file = $result['file'];
$uploadDir = '/var/www/uploads/';
$fileName = time() . '_' . preg_replace('/[^a-zA-Z0-9._-]/', '', $file['name']);
if (move_uploaded_file($file['tmp_name'], $uploadDir . $fileName)) {
echo json_encode(['success' => true, 'message' => '업로드 성공']);
} else {
echo json_encode(['success' => false, 'error' => '파일 이동 실패']);
}
} else {
echo json_encode($result);
}
}
?>
| 원인 | 확인 방법 | 해결책 |
|---|---|---|
| HTML enctype 누락 | 폼 태그 검사 | enctype="multipart/form-data" 추가 |
| post_max_size 초과 | phpinfo() 확인 | php.ini에서 post_max_size 증가 |
| 디렉토리 권한/용량 | $_FILES['error'] 코드 확인 | chmod 755, chown www-data 설정 |
| 클라이언트 파일 미선택 | JavaScript 디버깅 | fileInput.files.length 확인 후 FormData append |
| file_uploads 비활성화 | ini_get('file_uploads') | php.ini에서 file_uploads = On 설정 |
PHP 파일 업로드는 간단해 보이지만, 실제로는 클라이언트, 브라우저, PHP 설정, OS 권한 등 여러 계층이 관여한다. $_FILES 배열이 비는 문제는 대부분 이 중 한 가지 계층에서 발생한다. 위의 5가지 원인을 체계적으로 확인하고, 제공한 검증 함수를 사용하면 안전한 파일 업로드 시스템을 만들 수 있을 것이다. 특히 에러 코드를 무시하고 넘어가는 습관이 가장 위험하니, 항상 $_FILES['error']를 먼저 확인하는 것을 기억하자.