Please wait

Downloading Files

One common practice is to allow users to download their uploaded files. You can allow users to download files by directly linking them. But what if you need to programmatically do so? Programmatically allowing users to download files with PHP instead of linking directly offers several advantages.

  1. By serving files through PHP, you can control who has access to the files. For example, you can require users to be logged in or have specific permissions.
  2. You can log who downloads what and when. This data might be valuable for understanding user behavior or compliance with regulations.
  3. If you need to generate files on the fly (like personalized PDFs), PHP can create the content dynamically before serving the download.
  4. If files are sensitive, serving them through PHP can keep them outside the public web directory, making it harder for unauthorized users to access them.
  5. You can implement mechanisms to control the download speed, benefiting the server performance, especially for large or popular files.

By using PHP to manage file downloads, you have much more control and flexibility over the process compared to simply linking to the file directly. It enables a more secure, trackable, and potentially dynamic way to serve files to your users.

The readfile() Function

We can force users to download files by using the readfile() function. The readfile() function in PHP is used to read a file and write it to the output buffer. It's commonly used to serve files for download or simply to output the contents of a file to the browser.

The readfile() function has the following definition:

readfile(string $filename, bool $use_include_path = false, ?resource $context = null): int|false

Parameters

  • $filename: The path to the file that you want to read. It can be a relative or absolute path or a URL.
  • $use_include_path: (Optional) This is a boolean parameter. If set to true, the function will search for the file in the include path as well. The default value is false.
  • $context: (Optional) A valid context resource created with the stream_context_create() function. It can be used to specify various options like HTTP headers, timeout settings, etc.

The readfile() function returns the number of bytes read from the file. If an error occurs, false is returned.

Downloading a File

Let's say you wanted to download an image. You could do so with the following code:

$filename = 'image.jpg';
 
if (file_exists($filename)) {
  header('Content-Description: File Transfer');
  header('Content-Type: image/jpeg');
  header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
  header('Expires: 0');
  header('Cache-Control: must-revalidate');
  header('Pragma: public');
  header('Content-Length: ' . filesize($filename));
  readfile($filename);
  exit;
}

Firstly, we have a variable called $filename. This variable holds the path to the image file you want to download. Next, we're checking whether the file exists on the server before attempting to download it.

Afterward, we're adding a few headers. These headers are important so that the browser understands the contents of the file. We're using the header() function to add these headers. The following headers are added:

  • Content-Description: A brief description of the file.
  • Content-Type: Set to application/octet-stream, meaning it's treated as a binary file, prompting a download.
  • Content-Disposition: Specifies that the file should be downloaded and provides the filename.
  • Expires
  • Cache-Control
  • Pragma: These control caching of the file.
  • Content-Length: Specifies the size of the file.

Lastly, we're sending the file with the readfile(). This function reads the file and outputs it to the browser, initiating the download. As an extra assurance, adding an exit statement ensures that no additional output is sent after the file.

Limiting the Download Rate

Rate limiting the download of a file means controlling the speed at which the file is sent to the user. This can be useful in scenarios where you want to avoid overwhelming your server's bandwidth or provide fair access to multiple users downloading simultaneously.

In PHP, you can achieve download rate limiting by reading and sending the file in small chunks, adding a delay between each chunk. We can't use the readfile() function because this function sends the file to the client immediately. Instead, we have to use a different set of functions.

 
$filename = 'image.jpg';
$downloadRate = 100; // In KB per second
 
if (file_exists($filename)) {
  header('Content-Description: File Transfer');
  header('Content-Type: image/jpeg');
  header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
  header('Expires: 0');
  header('Cache-Control: must-revalidate');
  header('Pragma: public');
  header('Content-Length: ' . filesize($filename));
 
  $file = fopen($filename, 'rb');
  while (!feof($file)) {
    print fread($file, $downloadRate * 1024);
    flush();
    sleep(1); // 1-second delay between chunks
  }
  fclose($file);
  exit;
}

Breakdown

Firstly, we're defining a variable called $downloadRate to set the download rate in KB per second. You can adjust this value to control the speed of the download.

Next, we open the file with the fopen() function. This function does not immediately send the file to the browser. We're just accessing it.

Afterward, we begin a loop where the condition is a function called feof(). Files are opened from the beginning. We only want to continue downloading a file as long as there's additional data left to be downloaded. This function checks if the end of the file has been reached.

The next step is to begin sending the file to the browser with the fread() function. This reads a chunk of the file equal to the download rate multiplied by 1024 (to convert KB to bytes). In the example, it reads 100 KB per iteration. The flush() flushes the output buffer to send the chunk to the browser. The sleep(1) introduces a 1-second delay between sending each chunk, effectively limiting the download rate to the specified KB per second. Lastly, we're closing the file with the fclose() function.

By breaking the file into chunks and introducing a delay between each chunk, you can control the download speed and limit the rate at which the file is downloaded by the user.

Key Takeaways

  • Ensure the file path is correct and the file is readable by the PHP process.
  • Always check if the file exists on the server using file_exists() before attempting to download.
  • Utilize appropriate HTTP headers for controlling download behavior, like Content-Type, Content-Disposition, and Content-Length.
  • Control the download speed by reading and sending the file in chunks and introducing delays if needed.

Comments

Please read this before commenting