28

I'm using PHPExcel to export some data to user in an excel file. I would like the script to send the excel file to the user immediately after it's creation. Here is my test code:

try{

  /* Some test data */
  $data = array(
    array(1, 10   , 2             ,),
    array(3, 'qqq', 'some string' ,),
  );

  $objPHPExcel = new PHPExcel();
  $objPHPExcel->setActiveSheetIndex(0);

  /* Fill the excel sheet with the data */
  $rowI = 0;
  foreach($data as $row){
    $colI = 0;
    foreach($row as $v){
      $colChar = PHPExcel_Cell::stringFromColumnIndex($colI++);
      $cellId = $colChar.($rowI+1);
      $objPHPExcel->getActiveSheet()->SetCellValue($cellId, $v);
    }
    $rowI++;
  }

  header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
  header('Content-Disposition: attachment;filename="export.xlsx"');
  header('Cache-Control: max-age=0');

  $objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
  $objWriter->save('php://output');

}catch(Exception $e){
  echo $e->__toString();
}

On my local server (Windows 7 x64, Php 5.3.8, Apache 2.2.21) I get a valid xlsx file. There are no errors. But there is problem on the live server (Linux 2.6.32-5-amd64, PHP 5.3.3-7+squeeze13, Apache 2.2.16). The script lets the browser to download the "export.xlsx" file with such content:

exception 'PHPExcel_Writer_Exception' with message 'Could not close zip file php://output.' in /var/www/someuser/data/www/somedomain.com/libs/PHPExcel/Writer/Excel2007.php:348
Stack trace:
#0 /var/www/someuser/data/www/somedomain.com/classes/Report/Leads/Export.php(339): PHPExcel_Writer_Excel2007->save('php://output')
#1 /var/www/someuser/data/www/somedomain.com/application/pages/account/controllers/TestController.php(13): Report_Leads_Export->Test()
#2 /var/www/someuser/data/www/somedomain.com/libs/Zend/Controller/Action.php(516): Account_TestController->indexAction()
#3 /var/www/someuser/data/www/somedomain.com/libs/Zend/Controller/Dispatcher/Standard.php(295): Zend_Controller_Action->dispatch('indexAction')
#4 /var/www/someuser/data/www/somedomain.com/libs/Zend/Controller/Front.php(954): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Controller_Request_Http), Object(Zend_Controller_Response_Http))
#5 /var/www/someuser/data/www/somedomain.com/index.php(511): Zend_Controller_Front->dispatch()
#6 {main}

PHP is NOT running in Safe Mode. The "open_basedir" option is empty (it's commented out).

I have found such code in the PHPExcel files:

if ($objZip->close() === false) {
    throw new PHPExcel_Writer_Exception("Could not close zip file $pFilename.");
}

So the reason of the problem is that $objZip->close() === false where $objZip is an instance of ZipArchive class.

What is the reason of the problem and how can I solve it? Thank you.

pnuts
  • 58,317
  • 11
  • 87
  • 139
Pavel L
  • 921
  • 2
  • 9
  • 16

13 Answers13

44

The most common cause of this error when saving to php://output is an open_basedir restriction that doesn't include a valid system's temp folder (e.g. /tmp), or permissions for the system's temp folder... suhosin can also affect this, even when the obvious permissions appear to be set correctly.

A possible workround is to write the file to the filesystem in a directory that you know you do have full privileges to write, and then use readfile() to stream that file to php://output before deleting the file

Mark Baker
  • 209,507
  • 32
  • 346
  • 385
  • Suhosin is installed on the live server, but it's not on the local one. So suhosin may cause the problem. But I wouldn't like to see into it. I would like to solve the problem simply using a temp file, as you advice. Thank you! – Pavel L Jan 30 '14 at 09:29
  • In my case, I have a typo .. :< so make sure you does not have any – Rangi Lin Dec 06 '15 at 09:06
  • 2
    Hey. Im goin into the same error. I tried to give /tmp and /var/tmp 777 and it does not work. How are you doin that? – muuvmuuv Feb 09 '17 at 23:06
  • Is there a solution not saving a temporary file to the filesystem? – Anders Lindén Oct 29 '17 at 08:39
  • I get that the file path is `./tml/export_123.xlsx` why the dot? – rexxar Jul 19 '18 at 10:47
  • 1
    The first dot (`./`) ensures that the path is relative to the current working folder; the second dot (`export_123.xlsx`) is the separator between the filename and the file extension – Mark Baker Jul 19 '18 at 11:38
  • Take care if you want to save to file, you must create a full parent path with `mkdir()` function, otherwise, you get `ZipArchive::close(): No error in ...`, because `Excel2007` doesn't create a path if it doesn't exist – Serhii Popov Dec 20 '19 at 13:41
28

Thanks to Mark Baker. His answer has solved the problem. I have written a simple helper method using his approach.

static function SaveViaTempFile($objWriter){
    $filePath = sys_get_temp_dir() . "/" . rand(0, getrandmax()) . rand(0, getrandmax()) . ".tmp";
    $objWriter->save($filePath);
    readfile($filePath);
    unlink($filePath);
}

And I have just replaced $objWriter->save('php://output') with SaveViaTempFile($objWriter)

fiorebat
  • 3,431
  • 2
  • 18
  • 18
Pavel L
  • 921
  • 2
  • 9
  • 16
6

Hi i tried the following: in a server linux Centos 7.0 do not specified the route of directory tmp, input:

function SaveViaTempFile($objWriter){
    $filePath = '' . rand(0, getrandmax()) . rand(0, getrandmax()) . ".tmp";
    $objWriter->save($filePath);
    readfile($filePath);
    unlink($filePath);
    exit;
}

and work !!

2

For some people who may have this same ERROR message : it may very simply be because the filename you're trying to save to is actually already open in your Excel.. Was my case

Custam
  • 75
  • 7
2

Excelent Friend Work for me in php 7.1.2 and work in PhpSpreadsheet, fix the same file.

PhpSpreadsheet/Writer/Excel2007.php

the solution is in de function save in Excel2007.php

if (strtolower($pFilename) == 'php://output' || strtolower($pFilename) == 'php://stdout') {
    $pFilename = @tempnam(PHPExcel_Shared_File::sys_get_temp_dir(), 'phpxltmp');

Replace the second line with this:

$pFilename = dirname(__FILE__).'/'. rand(0, getrandmax()) . rand(0, getrandmax()) . ".phpxltmp";

thanks.

1

I´ve had the same error when I try run my php file, just change the in next line:

$objWriter->save("/dir1"."/".$file.".xlsx");

for this:

$objWriter->save(dirname(__FILE__)."/dir1"."/".$file.".xlsx");

add the the dir path and it worked!!!.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
0

And my answer is: Filename had symbols such as ":", ",", '"' Had to replace them to different ones. All worked

Velaro
  • 461
  • 1
  • 3
  • 20
0

This error happens also when trying to save a file into a folder that doesn't exist. Make sure the whole path exists.

Joey Quint
  • 45
  • 6
0

In my case I just tried to save file to folder, that wasn't exist. Created folder and problem was solved.

Mick
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Aug 04 '23 at 13:43
-1

The following works for the Excel2007 format. It would need to be adapted for different formats.


Open this file:

\Classes\PHPExcel\Writer\Excel2007.php

Look near Line 196 for:

if (strtolower($pFilename) == 'php://output' || strtolower($pFilename) == 'php://stdout') {
    $pFilename = @tempnam(PHPExcel_Shared_File::sys_get_temp_dir(), 'phpxltmp');

Replace the second line with this:

    $pFilename = dirname(\__FILE__).'/'. rand(0, getrandmax()) . rand(0, getrandmax()) . ".phpxltmp";

Then you can use the export-function as described in the developer guide.

Zipfel
  • 1
-1

In my case, I modify the permissions of the target folder to rwxrwxrwx (0777) and now works!

  • You should never configure your server directories to be readable and writable by the entire world. – ZeWaren Dec 20 '19 at 10:28
-2

It's caused by dir permission. Try to enter the final folder, then chmod -R 777 [folder_name]. It should work :)

Jax Liu
  • 11
-3

set chmod 777 -R public/results