Today I had to debug something I hadn’t run into before.
I had some PHPExcel code that had been running fine, outputting Excel files without a problem. This code had been successfully working for some time. If you’ve used the PHPExcel library before, you’ve probably written code that looks like this:
header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename=' . $filename . ".xlsx"); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); $objWriter->save("php://output");
Well, this stopped working recently. No error messages, no idea what was going on.
Turns out the problem was open_basedir. I’d recently done a litte tightening of the security on the server, and had implemented the php.ini directive open_basedir. If you’re not familiar, this is a directive that keeps php scripts from playing outside their own sandbox. For example if you’re running from /home/account, you can’t read or write to any directory outside of /home/account.
Well, the problem came from that php://output in the last line. I couldn’t find any documentation on the innards of how it’s implemented, but it must be operating outside of the /home/account directory. Makes sense, since it’s a global php facility. So this had my code hosed up so I couldn’t output an Excel file.
The solution? Simple. Write the file out to the filesystem, then read it back in. With a little bit of a delay to give it time to make sure the whole thing is written before reading it back:
$temp_filename = "/home/" . $account. "/temp/" . $filename . "-" . microtime() . ".xlsx"; header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename=' . $filename . ".xlsx"); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); $objWriter->save($temp_filename); sleep(2); // Give the system time to write out the file if(file_exists($temp_filename)) { $data = file_get_contents($temp_filename); unlink($temp_filename); echo $data; }
This bypasses the php://output facility, and makes sure that the code is operating fully within the allowed path(s) defined by open_basedir.