Working with temporary files in Magento 2

Controller, Magento 2

Recently i had to create an export in Magento 2 using PhpSpreadsheet. I created the sheet and added my data, but them i did hit a bump in the road: how do i download this file? PhpSpreadsheet offers two ways out of the box:

  • Writing to a file.

  • Echo the data.

The second method is anti Magento patterns where you return the response from your controller. You can fetch the output by using ob_flush(); functions, but that feels nasty too.

MageTested.com (ad)

Do you want your Magento store to be more reliable? Tired of things that suddenly break the checkout process without anyone noticing? You can hire my services to kickstart End-2-End testing for your Magento store. This way you know for sure that your store is behaving as expected before releasing that new feature or that update.

View MageTested.com for more information.

Creating temporary files

As option #2 is off the table, we investigate option 1. The first thing i tried is creating a temporary file as advised by PHP (Example from php.net):

$tmpfname = tempnam("/tmp", "FOO");

$handle = fopen($tmpfname, "w");
fwrite($handle, "writing to tempfile");
fclose($handle);

Now i have a pathe where i can let PhpSpreadsheet output its result and pass that to our controller. Sound easy, right? Well it turns out that Magento refuses to download this file as it is outside of the Magento directory.

Then i took another way, which makes more sense: asking Magento for the path to the tmp folder and write my file over there. This looks like this:

use Magento\Framework\Filesystem;
use Magento\Framework\App\Filesystem\DirectoryList;

class Export {
    public function __construct(
      \Magento\Framework\Filesystem $filesystem
    ) {
      $this->filesystem = $filesystem;
    }

    public function save()
    {
        $directory = $this->filesystem->getDirectoryWrite(DirectoryList::TMP);

        $path = $directory->getAbsolutePath(sprintf('export-%s.xlsx', date('Ymd-His')));

        // Output your result to the file in $path
        $writer = new Xlsx;
        $writer->save($path);

        return $path;
    }
}

Now in your controller you need to use the path that is returned from the ->save() method. It look like this:

public function execute()
{
    $filename = $this->export->save();

    return $this->fileFactory->create('export-' . date('Ymd-His') . '.xls', [
        'type' => 'filename',
        'value' => $filename,
        'rm' => true,
    ]);
}

The 'rm' => true should make sure the the file is deleted after the download. You can read more about downloading files from controllers in this article.

Want to respond?