Bài 13. Làm việc với tệp tin và upload

PHP đọc ghi tệp để lưu nhật ký, xuất CSV, đọc cấu hình, và nhận tệp người dùng tải lên (ảnh đại diện, bài nộp). Bài này học các hàm tệp, thư mục, CSV/JSON và quy trình upload an toàn.

50 phútTrung cấp
Bạn sẽ học được
  • Đọc, ghi, nối tệp văn bản
  • Làm việc với thư mục và CSV, JSON
  • Nhận tệp upload qua $_FILES
  • Kiểm tra kích thước, loại tệp, đổi tên và lưu an toàn
Thuật ngữ cần biết
Handle
Con trỏ tệp trả về bởi fopen.
$_FILES
Mảng chứa thông tin tệp tải lên.
MIME type
Loại nội dung tệp: image/jpeg, application/pdf.
tmp_name
Đường dẫn tệp tạm máy chủ lưu trước khi bạn di chuyển.

Khái niệm và lý thuyết

Hai cách làm việc với tệp: hàm nhanh file_get_contents/file_put_contents cho tệp nhỏ, và bộ fopen/fgets/fwrite/fclose khi đọc từng dòng hoặc tệp lớn. Upload: trình duyệt gửi tệp bằng form multipart/form-data; PHP lưu vào thư mục tạm và báo qua $_FILES; mã của bạn phải kiểm tra rồi move_uploaded_file về thư mục đích. Thư mục đích cần quyền ghi và không được cho phép chạy PHP.

Đọc ghi tệp

<?php
$tep = __DIR__ . "/data/nhat_ky.txt";
file_put_contents($tep, date("c") . " Truy cập trang chủ\n", FILE_APPEND | LOCK_EX);
echo nl2br(htmlspecialchars(file_get_contents($tep)));
$dong = file($tep, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);  // mảng từng dòng
echo count($dong), " dòng";
// Đọc từng dòng tệp lớn
$h = fopen($tep, "r");
while (($d = fgets($h)) !== false) { /* xử lý */ }
fclose($h);
var_dump(file_exists($tep), filesize($tep), is_writable(__DIR__ . "/data"));
unlink($tep);   // xóa tệp

Thư mục, CSV, JSON

<?php
if (!is_dir("data")) mkdir("data", 0755, true);
foreach (scandir("data") as $f) if ($f[0] !== ".") echo $f, "<br>";
foreach (glob("uploads/*.jpg") as $anh) echo basename($anh), " ", pathinfo($anh, PATHINFO_EXTENSION), "<br>";

// Ghi và đọc CSV (mở được bằng Excel)
$ds = [["SV001", "Lò Văn An", 8.5], ["SV002", "Cầm Thị Bình", 6]];
$f = fopen("data/diem.csv", "w");
fwrite($f, "\xEF\xBB\xBF");                    // BOM để Excel hiện tiếng Việt
fputcsv($f, ["Mã", "Họ tên", "Điểm"]);
foreach ($ds as $r) fputcsv($f, $r);
fclose($f);
$f = fopen("data/diem.csv", "r"); fgetcsv($f);  // bỏ dòng tiêu đề
while (($r = fgetcsv($f)) !== false) echo implode(" | ", $r), "<br>";
fclose($f);

// JSON làm "CSDL mini"
file_put_contents("data/cau_hinh.json", json_encode(["theme" => "sang"], JSON_PRETTY_PRINT));
$ch = json_decode(file_get_contents("data/cau_hinh.json"), true);

Upload tệp an toàn

<form method="post" enctype="multipart/form-data">
  <input type="file" name="anh" accept="image/*" required>
  <button>Tải lên</button>
</form>
<?php
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_FILES["anh"])) {
    $f = $_FILES["anh"];
    $loi = match ($f["error"]) {
        UPLOAD_ERR_OK => null,
        UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => "Tệp quá lớn",
        UPLOAD_ERR_NO_FILE => "Chưa chọn tệp",
        default => "Lỗi tải lên ($f[error])",
    };
    if (!$loi && $f["size"] > 2 * 1024 * 1024) $loi = "Tối đa 2 MB";
    $cho_phep = ["image/jpeg" => "jpg", "image/png" => "png", "image/webp" => "webp"];
    $mime = $loi ? null : mime_content_type($f["tmp_name"]);   // kiểm tra nội dung thật, không tin $f["type"]
    if (!$loi && !isset($cho_phep[$mime])) $loi = "Chỉ nhận ảnh JPG, PNG, WebP";
    if (!$loi) {
        $ten_moi = bin2hex(random_bytes(8)) . "." . $cho_phep[$mime];   // đổi tên, bỏ tên gốc
        $dich = __DIR__ . "/uploads/" . $ten_moi;
        if (move_uploaded_file($f["tmp_name"], $dich)) {
            echo "<img src='uploads/$ten_moi' width='200'>";
            // lưu $ten_moi vào CSDL
        } else $loi = "Không lưu được tệp";
    }
    if ($loi) echo "<p style='color:red'>$loi</p>";
}
Khóa $_FILES["anh"]Ý nghĩa
nameTên gốc (không tin cậy)
typeMIME do trình duyệt báo (không tin cậy)
tmp_nameĐường dẫn tạm
errorMã lỗi, 0 là thành công
sizeKích thước byte
Mẹo hay

Giới hạn upload trong php.ini: upload_max_filesize và post_max_size (mặc định 2M/8M). Upload nhiều tệp: name="anh[]" multiple, $_FILES["anh"]["name"] là mảng.

Lưu ý

Không bao giờ dùng tên tệp gốc để lưu (có thể là ../../config.php hay virus.php). Luôn đổi tên, kiểm tra MIME thật và đặt .htaccess chặn chạy PHP trong thư mục uploads: php_flag engine off.

Thực hành
  1. Ghi nhật ký mỗi lượt truy cập (thời gian, IP, trang) vào tệp log.
  2. Xuất danh sách sinh viên ra CSV mở được bằng Excel có tiếng Việt.
  3. Làm form đổi ảnh đại diện: giới hạn 1 MB, chỉ JPG/PNG, hiển thị sau khi tải.
  4. Cho phép nộp bài PDF/DOCX, lưu tên tệp mới và tên gốc vào tệp JSON.
Tóm tắt bài học
  • file_get/put_contents cho tệp nhỏ; fopen/fgets cho tệp lớn.
  • fputcsv/fgetcsv, json_encode/decode cho dữ liệu có cấu trúc.
  • Upload: kiểm tra error, size, MIME thật, đổi tên, move_uploaded_file.
  • Thư mục uploads: có quyền ghi, chặn chạy PHP.