파일 입출력
파일을 왜 열고 닫아야 하나요?
처음 파일 관련 함수를 보면 이런 생각이 듭니다.
"그냥 쓰기(파일명, 내용) 이렇게 하면 되는 거 아닌가? 왜 열고 닫고 이 난리야?"
이유가 있습니다.
일기장에 비유해봅시다.
일기를 쓰려면 일기장을 꺼내서 펼쳐야 합니다. (fopen)
그냥 표지에 대고 펜으로 긁으면 안 되니까요.
일기를 다 쓰면 덮어서 제자리에 놓습니다. (fclose)
파일도 마찬가지입니다.
열기 → 읽기/쓰기 → 닫기 순서가 있습니다.
열려있는 파일은 다른 프로세스가 동시에 접근하면 내용이 꼬일 수 있습니다.
닫는 것은 "나 다 썼어, 이제 다른 사람도 써" 라고 알려주는 행위입니다.
파일 쓰기
file_put_contents — 가장 간단한 방법
파일에 내용을 쓸 때 가장 많이 쓰는 함수입니다.
<?php
// 파일에 내용 쓰기 (없으면 새로 만들고, 있으면 덮어씁니다)
file_put_contents('hello.txt', '안녕하세요!');
// 내용 추가 (FILE_APPEND 플래그를 쓰면 덮어쓰지 않고 뒤에 붙입니다)
file_put_contents('log.txt', "방문 기록\n", FILE_APPEND);
한 줄로 끝납니다. 내부적으로 열기/쓰기/닫기를 알아서 처리해줍니다.
fopen / fwrite / fclose — 직접 제어
여러 번 나눠 쓰거나 세밀하게 제어해야 할 때 씁니다.
<?php
$fp = fopen('memo.txt', 'w'); // 파일 열기 (w: 쓰기 모드, 없으면 새로 생성)
if ($fp === false) {
throw new RuntimeException('파일을 열 수 없습니다.');
}
fwrite($fp, "첫 번째 줄\n");
fwrite($fp, "두 번째 줄\n");
fclose($fp); // 파일 닫기 — 꼭 해주세요
$fp는 파일 포인터(핸들)입니다.
메모장에서 깜빡이는 커서처럼, 파일의 어디에서 읽고 쓸지 위치를 기억합니다.
파일 열기 모드
fopen의 두 번째 인자로 모드를 지정합니다.
| 모드 | 설명 |
|---|---|
r | 읽기 전용. 파일이 없으면 실패 |
w | 쓰기 전용. 파일이 없으면 새로 만들고, 있으면 내용을 지웁니다 |
a | 추가 쓰기. 파일이 없으면 새로 만들고, 있으면 끝에 이어씁니다 |
r+ | 읽기/쓰기. 파일이 없으면 실패 |
w+ | 읽기/쓰기. 파일이 없으면 새로 만들고, 있으면 내용을 지웁니다 |
파일 읽기
file_get_contents — 가장 간단한 방법
<?php
$content = file_get_contents('hello.txt');
echo $content; // 안녕하세요!
파일 전체를 문자열로 읽어옵니다.
file — 줄 단위로 읽기
<?php
$lines = file('memo.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
echo $line . "\n";
}
file()은 각 줄을 배열의 원소로 돌려줍니다.
FILE_IGNORE_NEW_LINES는 줄 끝의 \n을 제거하고, FILE_SKIP_EMPTY_LINES는 빈 줄을 건너뜁니다.
파일/디렉토리 관련 함수들
파일을 다루다 보면 자주 쓰는 함수들입니다.
<?php
// 파일 존재 여부 확인
if (file_exists('memo.txt')) {
echo '파일이 있습니다.';
}
// 파일 크기 (바이트)
$size = filesize('memo.txt');
// 파일 삭제
unlink('memo.txt');
// 파일 복사
copy('원본.txt', '복사본.txt');
// 파일 이름 변경 (이동도 됩니다)
rename('old.txt', 'new.txt');
// 디렉토리 생성
mkdir('uploads', 0755, true); // true: 중간 경로도 자동 생성
실전: 로그 파일 쌓기
파일 입출력이 가장 많이 쓰이는 곳 중 하나가 로그 파일입니다.
<?php
declare(strict_types=1);
function writeLog(string $message): void {
$logFile = __DIR__ . '/logs/app.log';
$dir = dirname($logFile);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
$line = date('[Y-m-d H:i:s]') . ' ' . $message . PHP_EOL;
file_put_contents($logFile, $line, FILE_APPEND | LOCK_EX);
}
writeLog('사용자 홍길동 로그인');
writeLog('상품 #42 조회');
LOCK_EX는 파일에 잠금을 걸어 동시에 여러 프로세스가 쓸 때 내용이 뒤섞이지 않도록 합니다.
트래픽이 많은 사이트에서는 이 옵션을 꼭 붙이세요.
실전: CSV 파일 읽기
데이터를 엑셀이나 CSV로 내보낼 때 파일 입출력을 씁니다.
<?php
declare(strict_types=1);
// CSV 읽기
$fp = fopen('users.csv', 'r');
while (($row = fgetcsv($fp)) !== false) {
[$name, $email, $age] = $row;
echo "{$name} ({$email}), {$age}세\n";
}
fclose($fp);
<?php
declare(strict_types=1);
// CSV 쓰기
$users = [
['홍길동', 'hong@example.com', 30],
['김철수', 'kim@example.com', 25],
];
$fp = fopen('export.csv', 'w');
// UTF-8 BOM — 엑셀에서 한글이 깨지지 않도록
fwrite($fp, "\xEF\xBB\xBF");
foreach ($users as $user) {
fputcsv($fp, $user);
}
fclose($fp);
파일 입출력은 단순해 보이지만 동시 접근, 권한, 경로 문제가 실제 서비스에서 자주 발생합니다.
항상 파일이 존재하는지, 쓰기 권한이 있는지 확인하는 습관을 들이세요.
그리고fopen을 쓸 때는 반드시fclose로 닫아야 합니다.