Viết bài trên i-php.net, các vấn đề với fgetcsv


Trong quá trình xây dựng các ứng dụng, các lập trình viên thường ít quan tâm đến vấn đề import/export dữ liệu. Có lẽ là bởi đây là tính năng “giá trị gia tăng” và có thể “để mai tính”.

Tuy nhiên, với khách hàng thì khác. Họ có thể sẽ đòi hỏi rất nhiều ở tính năng import/export này, theo các tiêu chuẩn khác nhau.

Tôi sẽ không tập chung vào các yêu cầu “nâng cao” mà khách hàng có thể nghĩ ra (ví dụ như định nghĩa các giá trị thay thế Yes->1, No->0, hay lấy các giá trị mặc định…). Tôi chỉ muốn tạo một vài chú ý khi sử dụng PHP fgetcsv cho việc import dữ liệu dưới định dạng csv.

Đây là một định dạng rất đơn giản nhưng thông dụng, nó có thể được soạn thảo bởi plaintext editor hoặc ứng dụng excel like.

Và tất nhiên sử dụng fgetcsv là giải pháp đầu tiên mà ta nghĩ tới. Tuy nhiên khi sử dụng fgetcsv cần phải lưu ý:

1. Encoding của import file tốt nhất nên là utf8. Nếu không bạn có thể gặp các lỗi mất mát các ký tự đặc biệt khi đọc với fgetcsv như là các ký tự điều khiển ASCII chẳng hạn. Nếu bạn phải hỗ trợ các encoding khác, tốt nhất là nên sử dụng các hàm stream_filter_* để tự động chuẩn hóa dữ liệu nhận được về dạng utf8. Làm như sau $fp sẽ luôn được đọc dưới dạng utf8 với fgetcsv:

<?php
class utf8encode_filter extends php_user_filter
{
function filter($in, $out, &$consumed, $closing)
{
while ($bucket = stream_bucket_make_writeable($in)) {
$bucket->data = utf8_encode($bucket->data);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
$fp = @fopen($fileName, "r");
stream_filter_register("utf8encode", "utf8encode_filter")
or die("Failed to register filter");
stream_filter_prepend($fp, "utf8encode");
?>

2. Một tiện lợi và … tiềm ẩn nhiều nguy cơ đó là việc fgetcsv hỗ trợ multiple lines data, có nghĩa là dữ liệu có thể nằm trên nhiều dòng. Nó phụ thuộc vào format của file csv. Và một sơ xuất nào đó (người soạn csv không dùng tools chuyên dụng mà lại xài notepad chẳng hạn. Bạn có thể thừa hoặc thiếu chỉ một dấu ” thôi, thế là việc đọc csv sai bét cả. Tiềm tàng lớn nhất là việc ý đồ của bạn là mỗi record dữ liệu chỉ nằm trên một dòng. Nhưng sơ xuất khiến cho một record có một trường lấy toàn bộ các dòng tiếp theo trong file. Điều này dẫn đến việc chiều dài của dữ liệu nhận được lớn vượt quá giới hạn cho phép. Nếu file csv của bạn lớn, nó có thể sinh ra lỗi “trang trắng” (vì server từ chối xử lý tiếp vì một lý do nào đó liên quan đến dữ liệu của bạn).

Giải pháp toàn diện nhất cho việc này là sử dụng một csv validation cho file csv, trong đó quy định chiều dài tối đa của một trường dữ liệu cụ thể. Với php 5.3.x, chúng ta có thể đọc từng dòng của file và sử dụng str_getcsv cho dòng này (nếu ta luôn đảm bảo một record dữ liệu chỉ nằm trên một dòng csv).

— Cập nhật với php 5.2.x tôi đã có một giải pháp như sau để luôn lấy dữ liệu trên một dòng —

<?php
$records = array();
$csv = fopen('test.csv', 'r');
$stream = fopen('php://memory', 'rw');
while(($data = fgets($csv) ) !== FALSE){
fwrite($stream, $data);
fseek($stream, 0);
$records[] = fgetscv($stream, 4096);
ftruncate($stream);
}

3. Với file dữ liệu lớn, sử dụng fgetcsv khá là chậm, với MySQL ta có giải pháp thay thế là LOAD DATA INFILE để đưa dữ liệu vào một bảng tạm. LOAD DATA INFILE có thể import 5M dữ liệu vào bảng tạm chỉ trong vòng 1 giây. Sau đó sẽ bỏ qua fgetcsv mà đơn giản là chỉ thao tác với các dữ liệu đã nhận được trong bảng tạm.

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s