Trong công việc và các dự án liên quan đến việc quản lý, chúng ta thường sẽ cần chức năng xuất và nhập dữ liệu từ file CSV. Đối với đa số các Framework PHP trên thị trường hiện nay thì việc này đều được hỗ trợ sẵn thông qua các thư viện được xây dựng sẵn.

Tuy nhiên trong vài trường hợp đối với các dữ liệu sử dụng ngôn ngữ tiếng Nhật hay tiếng Trung, đôi khi sẽ xảy ra trường hợp file được export ra sẽ không thể import vào đúng như ban đầu. Ví dụ dưới đây sẽ minh họa cho điều đó (Lưu ý: Framework sử dụng trong bài viết là FuelPHP).

I. Trường hợp lỗi.

Hàm Export Data ra file CSV

function get_export($shop_id)
{
   $shifts = \Util_Csv_Shift_Export::getShiftCsvData(); // Lay du lieu de export
   $csv_header = array('日付', 'パターン', '勤務開始時刻', '勤務終了時刻', '休憩開始時刻', '休憩終了時刻', 'グループ', 'スタッフ社員番号', 'スタッフ氏名');
   //download
   $response = new \Response();
   $response->set_header('Content-Type', 'application/csv');
   $response->set_header('Content-Disposition', 'attachment; filename="export.csv"');
   echo \Format::forge($shifts)->to_csv(null, null, false, $csv_header);
   return $response->send(true);
}

Hàm Import đọc dữ liệu từ file CSV.

public static function getDataTrimFile($file_path) {
    $row = 1;
    $data_file_trim = array();
    if (($handle = fopen($file_path, "r")) !== FALSE && \Util::check_file_encode($file_path)) {
        while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
            $new_data = array();
            foreach ($data as $value) {
                $new_data[] = mb_convert_encoding($value, "UTF-8", "cp932");
            }
            \Log::debug("Data: " + \Format::forge())
            $data_file_trim[$row] = $new_data;
            $row++;
        }
    }
    fclose($handle);
    return $data_file_trim;
}

Với hàm export như trên, chúng ta sẽ có được file dữ liệu như sau.

CSV 1

Chúng ta có thể thấy dữ liệu được export ra sẽ có 9 cột. Tiếp theo, chúng ta sẽ dùng chính file vừa rồi để import trở lại

CSV 2

Khi check file log của hệ thống, ta sẽ thấy

CSV 3

Có thể thấy là dữ liệu đọc vào từ file CSV này chỉ đọc được 8 cột ( Trong khi lúc Export chúng ta thấy có 9 cột). Nếu nhìn kỹ hơn trong vùng khoanh tròn thì có thể thấy 2 cột đã bị dính liền nhau khi đọc dữ liệu vào, ký tự “\u0022” là biểu tượng dấu ngoặc kép đã bị dính liền vào cột trước, do đó khi đọc file thì cột sau bị dính liền vào cột trước, làm dữ liệu  đọc vào bị thiếu mất 1 cột.

Khi xem kỹ hơn file được xuất ra, ta sẽ thấy.

CSV 4

Cột bên phải là file gốc, cột bên trái là file đã được mở bằng Microsoft Excel và được định dạng lại. Có thể dễ dàng nhận thấy là file bên trái khi được excel định dạng lại đã bỏ hết các dấu ngoặc kép phân cách giữa các từ, chỉ còn dấu phẩy ngăn cách. Vì vậy khi đọc file gốc, chương trình đã đọc luôn dấu ngoặc kép gây nên tình trạng đọc thiếu cột.

Khi sử dụng file đã được sửa bằng excel, chương trình sẽ chạy bình thường.

CSV 5

Có thể thấy là dữ liệu sau khi được chỉnh sửa định dạng bằng file Excel đã trở về đúng với tình trạng ban đầu. Để giải quyết tình trạng này, chúng ta sẽ thay đổi một chút về hàm xuất file CSV.

II. Sửa lỗi

Để xử lý lỗi trên, mình dùng cách tự convert từng dòng dữ liệu và sau đó put từng dòng vào file CSV. Khi đó dữ liệu sẽ được loại bỏ dấu ngoặc kép giữa các từ.

function get_export($shop_id)
{
    $shifts = \Util_Csv_Shift_Export::getShiftCsvData();
    $file_name = "shift_" . $shop_id . "_" . date('Ymd', strtotime($from_date)) . "_" . date('Ymd', strtotime($to_date));
    $csv_header = array('日付', 'パターン', '勤務開始時刻', '勤務終了時刻', '休憩開始時刻', '休憩終了時刻', 'グループ', 'スタッフ社員番号', 'スタッフ氏名');
    //download
    $response = new \Response();
    $response->set_header('Content-Type', 'octet-stream; charset:SJIS-win');
    $response->set_header('Content-Disposition', 'attachment; filename="test.csv"');
    $stream = fopen('php://output', 'w');
    $convert_header = self::convertToCSVData($csv_header);
    fputs($stream, $convert_header . "\n");
    $temp_array = array();
    foreach ($shifts as $shift) {         
        $converted_data = self::convertToCSVData($shift);
        fputs($stream, $converted_data . "\n");
    }
    return $response->send(true);
}
private static function convertToCSVData($data) {
    $csv_data = implode(',', $data);
    return mb_convert_encoding($csv_data, 'CP932', 'ASCII,JIS,UTF-8,eucJP-win,SJIS-win');    
}
  • Hàm **convertToCSVData **sẽ thay đổi dịnh dạng của dữ liệu từ các định dạng như ASCII, JIS, UTF-8, eucJP-win, SJIS-win về định dạng “CP932”.
  • Dữ liệu sau khi convert sẽ được put từng dòng vào file bằng hàm fputs

Đây là dữ liệu so sánh sau khi đã thay đổi hàm xuất file.

CSV 6

Có thể thấy bây h dữ liệu 2 file đã tương đồng nhau, do đó khi thực hiện việc import cũng sẽ ko còn gặp trường hợp lỗi như trên.