fopen('data.txt', 'r') mở một tệp và trả về một handle, tức một stream resource mà các hàm xử lý tệp khác nhận làm đối số đầu tiên. Đọc bằng fgets() (một dòng) hoặc fread() (một số byte), ghi bằng fwrite() và đóng bằng fclose(). Đối số thứ hai là mode: 'r' để đọc, 'w' để ghi, 'a' để ghi nối vào cuối.
Đầu ra:
Got: apple
Got: banana
Got: cherry
fopen() trả về false khi không mở được tệp (tệp không tồn tại ở mode r, thiếu thư mục, không có quyền), và PHP in ra một cảnh báo như sau:
Warning: fopen(missing.txt): Failed to open stream: No such file or directory in /home/index.php on line 2
Vì vậy hãy kiểm tra false trước khi dùng handle. Để đọc hoặc ghi cả một tệp nhỏ trong một lần gọi, file_get_contents() và file_put_contents() ngắn gọn hơn; fopen() dành cho những tệp bạn đọc từng phần hoặc giữ mở.
Các mode của fopen
| Mode | Đọc | Ghi | Nếu tệp đã tồn tại | Nếu tệp chưa có | Bắt đầu ở |
|---|---|---|---|---|---|
r | có | không | mở tệp | thất bại | đầu tệp |
r+ | có | có | mở tệp, giữ nội dung | thất bại | đầu tệp |
w | không | có | xóa trắng tệp | tạo tệp | đầu tệp |
w+ | có | có | xóa trắng tệp | tạo tệp | đầu tệp |
a | không | có | giữ nội dung | tạo tệp | cuối tệp, mọi lần ghi đều nối thêm |
a+ | có | có | giữ nội dung | tạo tệp | cuối tệp, mọi lần ghi đều nối thêm |
x | không | có | thất bại | tạo tệp | đầu tệp |
x+ | có | có | thất bại | tạo tệp | đầu tệp |
c | không | có | giữ nội dung | tạo tệp | đầu tệp |
c+ | có | có | giữ nội dung | tạo tệp | đầu tệp |
Có hai hàng khiến nhiều người bất ngờ. r+ ghi đè lên các byte có sẵn ngay tại chỗ, không chèn thêm, còn a+ luôn ghi vào cuối tệp, kể cả sau khi bạn di chuyển con trỏ bằng fseek() (việc di chuyển chỉ ảnh hưởng đến thao tác đọc):
Đầu ra:
Xbc
Xbc
Xbc
end
Trên Windows bạn có thể gặp 'rb' hoặc 'wb': flag b nghĩa là chế độ nhị phân và ngăn Windows tự chuyển đổi ký tự xuống dòng. Trên Linux và macOS nó không có tác dụng gì, nên thêm vào vừa vô hại vừa giúp code chạy được ở mọi nơi.
Đọc tệp từng dòng với fgets()
fgets() trả về dòng tiếp theo, kèm cả ký tự \n, và trả về false khi hết tệp. Vì mỗi lúc chỉ có một dòng trong bộ nhớ, vòng lặp này xử lý được tệp log ở mọi kích thước, và bạn có thể dừng sớm bằng break:
Đầu ra:
2 errors in 5 lines
line 2: database timeout
line 4: disk full
Hãy so sánh với false bằng !==, đừng viết while ($line = fgets($fh)) trơn: một dòng chỉ chứa "0" và không có ký tự xuống dòng (dòng cuối của tệp có thể như vậy) là falsy và sẽ làm vòng lặp kết thúc sớm.
Ghi và ghi nối với fwrite()
fwrite() ghi một chuỗi tại vị trí hiện tại và trả về số byte đã ghi, hoặc false khi thất bại. fputs() là cùng một hàm dưới tên khác. Mở với 'a' để thêm vào cuối tệp mà không đụng đến nội dung đang có:
Đầu ra:
wrote 12 bytes
wrote 13 bytes
wrote 15 bytes
visit /home
visit /about
visit /contact
visit /home
PHP_EOL là ký tự xuống dòng của hệ điều hành mà PHP đang chạy (\n trên Linux và macOS, \r\n trên Windows). Với tệp mà chương trình khác sẽ đọc, hãy chọn rõ một loại, thường là "\n".
Đọc và ghi tệp CSV với fgetcsv() và fputcsv()
fputcsv() ghi một mảng thành một hàng CSV và đặt trong ngoặc kép các trường chứa dấu phẩy, dấu ngoặc kép hoặc ký tự xuống dòng. fgetcsv() đọc ngược một hàng thành mảng. Hãy truyền rõ ký tự phân cách, ký tự bao và một ký tự escape rỗng: như vậy bạn có CSV chuẩn, và PHP 8.4 sẽ in thông báo deprecated nếu bỏ trống đối số escape.
Đầu ra:
name,city,note
Ada,London,"likes ""math"", a lot"
Linus,Helsinki,kernel
Ada lives in London: likes "math", a lot
Linus lives in Helsinki: kernel
Tách dòng bằng explode(',', $line) sẽ hỏng ngay khi một trường chứa dấu phẩy; fgetcsv() lo phần ngoặc kép cho bạn. Với tệp phân cách bằng dấu chấm phẩy (thường gặp khi xuất từ Excel ở châu Âu), hãy truyền ';' làm ký tự phân cách.
Di chuyển trong tệp với fseek(), ftell() và rewind()
Một tệp đang mở có một vị trí. Thao tác đọc và ghi bắt đầu từ đó và đẩy vị trí tiến lên. ftell() cho bạn biết vị trí hiện tại, fseek() di chuyển đến một offset tính bằng byte, còn rewind() quay về đầu tệp. Với w+ bạn có thể ghi một tệp rồi đọc lại qua cùng một handle:
Đầu ra:
11
world
hello world
Khóa tệp với flock()
Hai request cùng lúc đọc một bộ đếm, cộng thêm một rồi ghi lại có thể cùng đọc được 41 và cùng ghi 42. flock() với LOCK_EX buộc request thứ hai phải chờ đến khi request đầu nhả khóa. Mode c+ là lựa chọn đúng ở đây: nó tạo tệp nếu cần và, khác với w+, không xóa trắng tệp trước khi bạn giữ được khóa.
Đầu ra:
Visitor number 1
Visitor number 2
Visitor number 3
Khóa chỉ mang tính tư vấn: nó chỉ chặn được code khác cũng gọi flock(). Để ghi nối một dòng, file_put_contents($path, $line, FILE_APPEND | LOCK_EX) làm điều tương tự chỉ trong một lần gọi.
Lỗi thường gặp: while (!feof($fh))
Nhiều hướng dẫn cũ dùng feof() để lặp. Hàm này chỉ trả về true sau khi một lần đọc đã chạm đến cuối tệp, nên vòng lặp chạy nhiều hơn số dòng một lần và lệnh fgets() cuối cùng trả về false:
Đầu ra:
string(2) "a
"
string(2) "b
"
string(2) "c
"
bool(false)
4 rounds for 3 lines
Giá trị false thừa đó sẽ thành một hàng trống trong báo cáo hoặc một phần tử rỗng trong mảng. Hãy lặp trực tiếp trên thao tác đọc, while (($line = fgets($fh)) !== false), như mọi ví dụ khác trên trang này.
Câu hỏi thường gặp
fopen trong PHP có những mode nào?
r để đọc, w ghi sau khi xóa trắng tệp, a ghi vào cuối tệp, x tạo tệp mới và thất bại nếu tệp đã tồn tại, còn c ghi mà không xóa nội dung. Thêm + (r+, w+, a+, x+, c+) cho phép vừa đọc vừa ghi. Mọi mode trừ r và r+ đều tạo tệp khi tệp chưa có.
Mode w và a trong fopen khác nhau thế nào?
w xóa trắng tệp ngay khi mở và ghi từ đầu. a giữ nguyên nội dung và mọi lệnh fwrite() đều ghi vào cuối tệp, kể cả sau fseek(), nên đây là mode dành cho tệp log.
Làm sao đọc tệp CSV trong PHP?
Mở tệp bằng fopen($path, 'r') rồi gọi fgetcsv() trong một vòng lặp: while (($row = fgetcsv($fh, null, ',', '"', '')) !== false) { ... }. Mỗi lần gọi trả về một hàng dưới dạng mảng các trường, dấu ngoặc kép và dấu phẩy bên trong trường đã được xử lý sẵn cho bạn.
Có cần gọi fclose trong PHP không?
PHP tự đóng mọi tệp đang mở khi script kết thúc, nên một script ngắn vẫn chạy được mà không cần nó. Dù vậy, hãy gọi fclose() ngay khi dùng xong: hàm này đẩy dữ liệu ghi còn trong bộ đệm ra tệp, nhả các khóa lấy bằng flock() và giúp script chạy lâu không bị cạn file handle.