Split file & merge file upload cùng với PHP

Hôm nay nhận được yêu cầu phải làm chức năng này qua đó tiện thể mình sẽ viết một bài viết chia sẻ với các bạn. Để có thể hoàn thành được chức năng split & merge file này thì các bạn cần lắm được cơ bản các kiến thức về hàm xử lý file trong PHP e.g hàm mở file fopen(), hàm đọc file ...

Hôm nay nhận được yêu cầu phải làm chức năng này qua đó tiện thể mình sẽ viết một bài viết chia sẻ với các bạn. 

Để có thể hoàn thành được chức năng split & merge file này thì các bạn cần lắm được cơ bản các kiến thức về hàm xử lý file trong PHP e.g hàm mở file fopen(), hàm đọc file fgets() và hàm ghi file fwrite(). Hoặc nếu bạn chưa lắm được kiến thức này thì các bạn có thể xem lại bài viết các hàm xử lý file trong php.

Trong bài viết này mình sẽ không nói lại cách upload một file từ client nên server-side nữa. Nếu bạn chưa lắm được phương pháp để upload một file từ client nên server-side thì bạn có thể xem lại các bài viết của mình.

  • Upload file trong PHP - Xem
  • Upload ảnh bằng ajax và PHP - Xem
  • Upload file sử dụng Uploadifive - Xem

Trong ba vài viết này mình đã trình bày rất kỹ rồi. Trong bài viết này mình sẽ không nói lại nữa.

Trong bài viết này mình sẽ tập chung vào nội dung chính là split & merge file. Mình sẽ đưa da một giả thiết là mình có một file như sau:

$filepath = './uploads/example.doc'; // filesize = 500KB 

Bây giờ mình muốn chia nó thành khoảng 5 phần nhỏ hơn thì mỗi phần sẽ có dung lượng là 500KB / 5 = 100KB.

$partnum = 5; // số phần sẽ chia
$filezise = filesize($filepath) // Size file đích.
$partsize = floor($filezise/partnum)// Size mỗi phần sẽ chia nhỏ.
//Return $partsize = 100KB

Hoặc khi bạn cần download file này thì 5 phần kia sẽ được gộp lại thành một file ban đầu.

P/s: Trong nội dung bài viết này mình sẽ không tạo ra CSDL để lưu chữ bất cứ thông tin gì. Nhưng nếu bạn có ý đinh viết chương trình quản lý upload và download file có sử dụng phương pháp split & merge file thì bạn nên sử dụng CSDL. Lưu ý rằng mình chỉ nói về ý tưởng thôi nhé.

Mình sẽ tạo ra một class có tên là SplitMergeFile. Mình sẽ xây dựng một số hàm sau.

  • __constructor($dir, filepath, $partnum):Hàm khởi tạo
  • _basename(): Hàm này sẽ trả về tên đây đủ của file
  • rmkdir(): Hàm tạo danh mục đệ quy
  • _mkdir(): Hàm tạo một danh mục
  • split_file(): Hàm cắt file
  • merge_file(): Hàm merge file

Chi tiết nội dung SplitMergeFile:

class SplitMergeFile{
	/**/
	private $dir = null;
	private $filepath = null;
	private $partnum = 1;
	/*
	|--------------------------------------------------------------------------
	| Constructor
	|--------------------------------------------------------------------------
	*/
	function __construct($dir, $filepath, $partnum){
		$this->dir = $dir;
		$this->filepath = $filepath;
		$this->partnum = $partnum;
	}
	/*
	|--------------------------------------------------------------------------
	| Return basename of file
	|--------------------------------------------------------------------------
	*/
	function _basename(){
		$path_parts = @pathinfo($this->filepath);
		return $path_parts['basename'];
	}
	/*
	|--------------------------------------------------------------------------
	| Creates directories recursively
	|--------------------------------------------------------------------------
	*/
	function rmkdir($path, $mode = 0777) {
		return is_dir($path) || ( $this->rmkdir(dirname($path), $mode) && $this->_mkdir($path, $mode) );
	}
	/*
	|--------------------------------------------------------------------------
	| Creates directory
	|--------------------------------------------------------------------------
	*/
	function _mkdir($path, $mode = 0777) {
		$old = umask(0);
		$res = @mkdir($path, $mode);
		umask($old);
		return $res;
	}
	/*
	|--------------------------------------------------------------------------
	| Function Split File
	|--------------------------------------------------------------------------
	*/
	function split_file(){
		$handle = @fopen($this->filepath, 'rb') or die("Lỗi mở file {$this->filepath}");
		// Tổng dung lượng File đích
		$filesize = @filesize($this->filepath);
		// Tính dung lượng mỗi phần
		$partsize = floor($filesize/$this->partnum);
		$excess = $filesize % $this->partnum; // Phần dư thừa
		$parts = array();
		for($i=0; $i<$this->partnum; $i++){
			if($i==$this->partnum-1 && $excess > 0){
				$parts[$i] = fread($handle, $partsize + $excess); 
			}else{
				$parts[$i] = fread($handle, $partsize); 
			}
		}
		// Đóng mở file đích
		@fclose($handle) or die("Lỗi đóng File");
		
		// Ghi dữ liệu vào từng file đã được chia
		$dirname = $this->dir.'/'.$this->_basename();
		if(!is_dir($dirname)) $this->rmkdir($dirname, 0777);
		for($i=0; $i<$this->partnum;$i++) { 
			$handle = fopen($dirname.'/splited_'.$i, 'wb') or die("Lỗi mở File"); 
			@fwrite($handle,$parts[$i]) or die("Lỗi ghi File Splitter"); 
		} 
		// Close file handle 
		@fclose($handle) or die("Lỗi đóng File Splitter"); 
	}
	/*
	|--------------------------------------------------------------------------
	| Function Merge File
	|--------------------------------------------------------------------------
	*/
	function merge_file() { 
		$content='; 
		// Đọc từng file đã chia và merge 
		for($i=0; $i<$this->partnum; $i++) { 
			$file_size = @filesize($this->dir.'/'.$this->_basename().'/splited_'.$i); 
			$handle = @fopen($this->dir.'/'.$this->_basename().'/splited_'.$i, 'rb') or die("Lỗi mở File"); 
			$content .= @fread($handle, $file_size) or die("Lỗi đọc File"); 
		} 
		// Ghi tới merge File
		$handle=fopen($this->dir.'/merge/'.$this->_basename(), 'wb') or die("Lỗi không thể tạo Merge File"); 
		@fwrite($handle, $content) or die("Lỗi ghi merge File"); 
		// Đóng mở File ghi
		@fclose($handle) or die("Lỗi đóng File"); 
	}
}

P/s: Có lẽ các bạn đọc qua cũng sẽ hiểu tuy nhiên mình lưu ý các bạn phần này.

$excess = $filesize % $this->partnum; // Phần dư thừa
for($i=0; $i<$this->partnum; $i++){
	if($i==$this->partnum-1 && $excess > 0){
		$parts[$i] = fread($handle, $partsize + $excess); 
	}else{
		$parts[$i] = fread($handle, $partsize); 
	}
}

Tại sao lại vậy? Mình sẽ giải thích cho các bạn hiểu nhé.

// Giả sử bạn có một file dung lượng
$filesize = 508KB; 

// Mình sẽ chia nhỏ làm 5 phần
$partnum = 5;

// Tính dung lượng mỗi phần
$partsize = floor($filesize/$partnum); 

// Như các bạn đã biết thì hàm floor() làm tròn giá trị xuống mức sàn.
// Khi đó kết quả của mối phần: $partsize = 101;

// Nếu lấy $partsize(101) * $partnum(5) = 505(KB)
// Sau khi tính dung lượng bị thiếu so với ban đầu $filesize = 508KB
// 508/5 ~= 101.6 => Mỗi phần đã bị hàm floor() làm thiếu đi một ít.

// Xác định phần thiếu
$excess = $filesize % $this->partnum;

// Phần thiếu này sẽ được gán vào phần cuối cùng.
if($i==$this->partnum-1 && $excess > 0){
	$parts[$i] = fread($handle, $partsize + $excess); 
}

Bây giờ thì các bạn đã hiểu rồi đúng không nào???

Ở đây mỗi khi chia nhỏ một file mình sẽ tạo mới một folder trùng với tên của file.

$filepath = './uploads/example.doc';
$folder= example.doc

Thực ra mình làm vậy để mình khôi phục lại file ban đầu cho đỡ dài dòng thôi. Còn khi vào thực tế thì có nhiều cách khác.

Bây giờ chúng ta chạy thử và xem thành quả nhé.

$dir = './uploads/splitmerge/';
$filepath = './uploads/content/cau-lenh-sql.jpg';
$smf = new SplitMergeFile($dir, $filepath, 5);

// Split file
$smf ->split_file();

// Merge file
$smf->merge_file();

Kết quả:

Kết luận.

Như vậy là mình đã giới thiệu tới các bạn cách chia nhỏ một file lớn và gộp lại các file nhỏ đã chia thành file ban đầu. Tuy bài viết còn khá lộn xộn như mình đã nói ban đầu đây chỉ là giải pháp và mình đã ứng dụng nó vào sản phẩm khách hàng. Có lẽ nó sẽ phải tối ưu hơn nữa nhưng ở thời điểm bây giờ thời gian nó chưa cho phép nên đành để sau vậy.

Lưu ý: Các thư mục bạn cần thiết lập permission là 0777

0