Skip to main content

Streaming a ZIP File of Files

This chapter explains how to stream-download a ZIP file containing several files to the client's web browser.

Preparation

You need to install the package rekalogika/file-zip to use this feature:

composer require rekalogika/file-zip

Concepts

A DirectoryInterface represents a tree of files. It contains entries of FileInterface, FilePointerInterface, or other DirectoryInterface. The FileZip service turns a DirectoryInterface into a ZIP file.

The ZIP file is created on the fly and streamed to the client's web browser, and does not involve a temporary file. Therefore, the operation is relatively inexpensive.

note

We are using the ZipStream-PHP package under the hood.

Basic Usage

The FileZip::streamZip() method takes a DirectoryInterface and streams a ZIP file to the client's web browser.

use Rekalogika\File\Zip\FileZip;
use Rekalogika\Contracts\File\DirectoryInterface;
use Rekalogika\File\Directory;
use Rekalogika\Contracts\File\FileInterface;

/** @var FileZip $fileZip */
/** @var FileInterface $file1 */
/** @var FileInterface $file2 */
/** @var FileInterface $file3 */

$directory = new Directory('my_zip_file', [$file1, $file2, $file3]);
$fileZip->streamZip($directory);

In Controllers

To get an HTTP Foundation Response object, use FileZip::createZipResponse():

use Rekalogika\File\Zip\FileZip;
use Rekalogika\File\Directory;
use Rekalogika\Contracts\File\FileInterface;

class SomeController {
public function download(FileZip $fileZip): Response
{
/** @var FileInterface $file1 */
/** @var FileInterface $file2 */
/** @var FileInterface $file3 */

$directory = new Directory('my_zip_file', [$file1, $file2, $file3]);

return $fileZip->createZipResponse($directory);
}
}

Dealing With Doctrine Collections Containing Files

To convert a Doctrine collection of files into a DirectoryInterface, you can use FileCollection.

use Doctrine\Common\Collections\Collection;
use Rekalogika\Domain\File\Association\Entity\FileCollection;
use Rekalogika\Contracts\File\DirectoryInterface;
use Rekalogika\Contracts\File\FileInterface;
use Rekalogika\File\Directory;
use Rekalogika\File\Zip\FileZip;

class SomeController {
public function download(Product $product, FileZip $fileZip): Response
{
/** @var Collection<int,Images> */
$images = $product->getImages();

/** @var FileCollection */
$directoryOfImages = new FileCollection($images, 'product-image');

return $fileZip->createZipResponse($directoryOfImages);
}
}

Returning a DirectoryInterface in the Getter Itself

FileCollection implements both DirectoryInterface and Collection. So, it is safe to return a FileCollection in the getter because it won't change the existing behavior of your getter.

use Rekalogika\Domain\File\Association\Entity\FileCollection;

class Product
{
// ...

/**
* @return FileCollection<int,Image>
*/
public function getImages(): FileCollection
{
return new FileCollection(
$this->images,
sprintf('product %s images', $this->getName())
);
}

// ...
}

Then, you can do something like this in the controller:

use Rekalogika\File\Zip\FileZip;

class SomeController {
public function download(Product $product, FileZip $fileZip): Response
{
return $fileZip->createZipResponse($product->getImages());
}
}

The framework registers a temporary URL handler. So, you can simply use TemporaryUrlGeneratorInterface::generateUrl() to generate a temporary URL to the ZIP file.

use Rekalogika\TemporaryUrl\TemporaryUrlGeneratorInterface;

/** @var TemporaryUrlGeneratorInterface $temporaryUrlGenerator */

/** @var FileCollection */
$images = $product->getImages();

$url = $temporaryUrlGenerator->generateUrl($images);

In Twig Templates

In Twig templates, you can easily generate URLs to a ZIP file by using the temporary_url filter with a DirectoryInterface as the input.

<a href="{{ product.images|temporary_url }}" {{ temporary_url_autoexpire() }}>
Download Product Images
</a>