diff --git a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php index 6a466f5711d7c..e49b1c9842539 100644 --- a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php +++ b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php @@ -28,6 +28,7 @@ class BinaryFileResponse extends Response protected static bool $trustXSendfileTypeHeader = false; protected File $file; + protected ?\SplTempFileObject $tempFileObject = null; protected int $offset = 0; protected int $maxlen = -1; protected bool $deleteFileAfterSend = false; @@ -62,15 +63,18 @@ public function __construct(\SplFileInfo|string $file, int $status = 200, array */ public function setFile(\SplFileInfo|string $file, ?string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true): static { + $isTemporaryFile = $file instanceof \SplTempFileObject; + $this->tempFileObject = $isTemporaryFile ? $file : null; + if (!$file instanceof File) { if ($file instanceof \SplFileInfo) { - $file = new File($file->getPathname()); + $file = new File($file->getPathname(), !$isTemporaryFile); } else { $file = new File((string) $file); } } - if (!$file->isReadable()) { + if (!$file->isReadable() && !$isTemporaryFile) { throw new FileException('File must be readable.'); } @@ -80,7 +84,7 @@ public function setFile(\SplFileInfo|string $file, ?string $contentDisposition = $this->setAutoEtag(); } - if ($autoLastModified) { + if ($autoLastModified && !$isTemporaryFile) { $this->setAutoLastModified(); } @@ -298,19 +302,25 @@ public function sendContent(): static } $out = fopen('php://output', 'w'); - $file = fopen($this->file->getPathname(), 'r'); + + if ($this->tempFileObject) { + $file = $this->tempFileObject; + $file->rewind(); + } else { + $file = new \SplFileObject($this->file->getPathname(), 'r'); + } ignore_user_abort(true); if (0 !== $this->offset) { - fseek($file, $this->offset); + $file->fseek($this->offset); } $length = $this->maxlen; - while ($length && !feof($file)) { + while ($length && !$file->eof()) { $read = $length > $this->chunkSize || 0 > $length ? $this->chunkSize : $length; - if (false === $data = fread($file, $read)) { + if (false === $data = $file->fread($read)) { break; } while ('' !== $data) { @@ -326,9 +336,8 @@ public function sendContent(): static } fclose($out); - fclose($file); } finally { - if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) { + if (null === $this->tempFileObject && $this->deleteFileAfterSend && is_file($this->file->getPathname())) { unlink($this->file->getPathname()); } } diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index a26edc4626a77..3ab25638c30df 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add `UploadedFile::getClientOriginalPath()` * Add `QueryParameterRequestMatcher` * Add `HeaderRequestMatcher` + * Add support for `\SplTempFileObject` in `BinaryFileResponse` 7.0 --- diff --git a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php index e8a194959d879..beff3511b1129 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php @@ -434,4 +434,25 @@ public static function tearDownAfterClass(): void @unlink($path); } } + + public function testCreateFromTemporaryFile() + { + $file = new \SplTempFileObject(); + $file->fwrite('foo,bar'); + + $response = new BinaryFileResponse($file, 201, [ + 'Content-Type' => 'text/csv', + ]); + + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT); + + $this->assertSame(201, $response->getStatusCode()); + $this->assertSame('text/csv', $response->headers->get('Content-Type')); + $this->assertEquals('attachment; filename=temp', $response->headers->get('Content-Disposition')); + + ob_start(); + $response->sendContent(); + $string = ob_get_clean(); + $this->assertSame('foo,bar', $string); + } }