PHP Large File Download in Chunks

Introduction

Here in this tutorial example, I will show you how to download large file in PHP program. Generally, when you download a file, your file is stored into file system or you load it into memory as a byte array. This is not a problem when you deal with a small file but when you download a large file (MB or GB) then it may lead to out of memory error.

The file which I am going to download in this program is an external file and will be downloaded from a URL.

Related Posts:

php large file download

Prerequisites

PHP 7.4.23

Project Directory

It’s assumed that you have setup PHP in your system.

Now I will create a project root directory called php-download-large-file anywhere in your system. I may not mention the project root directory in subsequent sections and I will assume that I am talking with respect to the project’s root directory.

File Download Script

I will show you how to do streaming a large file in PHP or in other words how to read file in chunks instead of reading file at once. Reading file in chunks will not hit your memory or you won’t face any out of memory error while you are reading a large file.

I will explain how to read large file using a defined chunk size and using the built-in function fpassthru which is available in PHP. fpassthru doesn’t read the entire file into memory prior to sending it, rather it outputs straight to the client.

While you read the file in chunks you will see that browser is downloading file in the defined chunk size. You will also not face maximum execution time exceeded error.

Let’s look at the following code how to download the file chunk by chunk:

function download_file_chunked($path) {
	$file_name = basename($path);

	// get the file's mime type to send the correct content type header
	//$finfo = finfo_open(FILEINFO_MIME_TYPE); //For remote file, it may not work
	//$mime_type = finfo_file($finfo, $path); //For remote file, it may not work
	$mime_type = mime_type($file_name);

	$attachment = (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE")) ? "" : " attachment"; // IE 5.5 fix.

	// send the headers	
	header("Content-Type: $mime_type");
	header('Content-Transfer-Encoding: binary');
	//header('Content-Length: ' . filesize($path)); //PHP Warning: filesize(): stat failed for remote file
	//header("Content-Disposition: attachment; filename=$file_name;");
	header("Content-Disposition: $attachment; filename=$file_name;");

	$options = array(
		"ssl"=>array(
			"verify_peer"=>false,
			"verify_peer_name"=>false,
		),
	);
	
	$context  = stream_context_create($options);

    //$handle = fopen($path, 'rb');	
	$handle = fopen($path, 'rb', false, $context);
	
	ob_end_clean();//output buffering is disabled, so you won't hit your memory limit
	
	$buffer = '';
	$chunkSize = 1024 * 1024;
	
	//$newfname = basename($path);
	//$newf = fopen ($newfname, "wb");

	ob_start();
    while (!feof($handle)) {
        $buffer = fread($handle, $chunkSize);		
        echo $buffer;
        ob_flush();
        flush();
		
		//fwrite($newf, $buffer, $chunkSize);
    }
	
    fclose($handle);
	
	//fclose($newf);
	
	exit;
}

Functions finfo_open(), finfo_file() is available from the PHP module php_fileinfo.dll or php_fileinfo.so. So, make sure that this module is enabled in the php.ini file otherwise you will see an error.

In windows system you can enable it by the following line of code in php.ini file:

extension=<php installation dir>/ext/php_fileinfo.dll

If you are unable to enable this module or extension, then you can create your own function that will check the file’s MIME type from the file name.

$mime_type = mime_type($file_name);

The partial source code for mime_type() function is given below:

function mime_type($filename) {

	$mime_types = array(

		'txt' => 'text/plain',
		'htm' => 'text/html',
		'html' => 'text/html',
		'php' => 'text/html',
		'css' => 'text/css',
		'js' => 'application/javascript',
		'json' => 'application/json',
		'xml' => 'application/xml',
		'swf' => 'application/x-shockwave-flash',
		'flv' => 'video/x-flv',
                
                //more code ...

        }
}

Next you need to send appropriate headers for file downloading:

header("Content-Type: $mime_type");
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($path));
header("Content-Disposition: $attachment; filename=$file_name;");

Then I am reading the file. I am creating stream content with SSL verification false because I am calling from localhost over HTTP protocol.

$options = array(
	"ssl"=>array(
		"verify_peer"=>false,
		"verify_peer_name"=>false,
	),
);

$context  = stream_context_create($options);

//$handle = fopen($path, 'rb');	//for SSL
$handle = fopen($path, 'rb', false, $context); //for non SSL

Finally, I am reading the file in chunk (size of 1024 * 1024 bytes) and flushing the content:

$buffer = '';
$chunkSize = 1024 * 1024;

ob_start();
while (!feof($handle)) {
	$buffer = fread($handle, $chunkSize);		
	echo $buffer;
	ob_flush();
	flush();
}

fclose($handle);

The above file will be downloaded through browser and browser will give you an option to save the file.

If you want to save the file directly through code only then you can use the following code instead of sending any header to the browser:

$options = array(
	"ssl"=>array(
		"verify_peer"=>false,
		"verify_peer_name"=>false,
	),
);

$context  = stream_context_create($options);

//$handle = fopen($path, 'rb');	
$handle = fopen($path, 'rb', false, $context);

ob_end_clean();//output buffering is disabled, so you won't hit your memory limit

$buffer = '';
$chunkSize = 1024 * 1024;

$newfname = basename($path);
$newf = fopen ($newfname, "wb");

while (!feof($handle)) {
	$buffer = fread($handle, $chunkSize);
	
	fwrite($newf, $buffer, $chunkSize);
}

fclose($handle);

fclose($newf);

So, I have done coding for downloading file using a defined chunk size.

Now I will show you the code that will download file in chunk size using fpassthru() function.

function download_large_file($path) {
	// the file name of the download, change this if needed
	$file_name = basename($path);

	// get the file's mime type to send the correct content type header
	//$finfo = finfo_open(FILEINFO_MIME_TYPE); //For remote file, it may not work
	//$mime_type = finfo_file($finfo, $path); //For remote file, it may not work
	$mime_type = mime_type($file_name);

	$attachment = (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE")) ? "" : " attachment"; // IE 5.5 fix.

	// send the headers	
	header("Content-Type: $mime_type");
	//header('Content-Length: ' . filesize($path)); //PHP Warning: filesize(): stat failed for remote file
	//header("Content-Disposition: attachment; filename=$file_name;");
	header("Content-Disposition: $attachment; filename=$file_name;");

	//Disable SSL verification
	$options=array(
		"ssl"=>array(
			"verify_peer"=>false,
			"verify_peer_name"=>false,
		),
	);
	
	$context  = stream_context_create($options);

	// stream the file
	//$fp = fopen($path, 'rb');
	$fp = fopen($path, 'rb', false, $context);
	
	ob_end_clean();//output buffering is disabled, so you won't hit your memory limit
	
	fpassthru($fp);
	
	fclose($fp);
	
	exit;
}

In the above code you won’t find that I am manually setting the chunk size anywhere.

Deploying the File Download Application

Start the development server, navigate to the root folder of your project from the command line tool and execute the command: php -S localhost:8000. You can also check how to run PHP application without external server.

Your development server will start and listen to port 8000.

Testing the File Download Application

Hit the URL http://localhost:8000/download-large-file.php in the browser, you will get an option to save the file and your file will be downloading and you will notice that the downloaded file size in increasing gradually.

php download large file

Hope you got an idea how to download large file in PHP.

Source Code

Download

Leave a Reply

Your email address will not be published. Required fields are marked *