Skip to content

Commit

Permalink
rework
Browse files Browse the repository at this point in the history
  • Loading branch information
smnandre committed Jan 9, 2025
1 parent 32cd824 commit 456c0e2
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@
class LiveComponentSubscriber implements EventSubscriberInterface, ServiceSubscriberInterface
{
private const HTML_CONTENT_TYPE = 'application/vnd.live-component+html';

private const REDIRECT_HEADER = 'X-Live-Redirect';
private const DOWNLOAD_HEADER = 'X-Live-Download';

public function __construct(
private ContainerInterface $container,
Expand Down Expand Up @@ -258,13 +256,6 @@ public function onKernelView(ViewEvent $event): void
return;
}

if ($event->getControllerResult() instanceof BinaryFileResponse) {
if (!$event->getControllerResult()->headers->has(self::DOWNLOAD_HEADER)) {

}
$event->setResponse(new Response());
}

$event->setResponse($this->createResponse($request->attributes->get('_mounted_component')));
}

Expand Down
38 changes: 0 additions & 38 deletions src/LiveComponent/src/LiveDownloadResponse.php

This file was deleted.

51 changes: 51 additions & 0 deletions src/LiveComponent/src/LiveResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Symfony\UX\LiveComponent;

use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\StreamedResponse;

/**
* @author Simon André <[email protected]>
* @author Kevin Bond <[email protected]>
*/
final class LiveResponse
{
/**
* @param string|\SplFileInfo $file The file to send as a response
* @param string|null $filename The name of the file to send (defaults to the basename of the file)
* @param string|null $contentType The content type of the file (defaults to `application/octet-stream`)
*/
public static function file(string|\SplFileInfo $file, ?string $filename = null, ?string $contentType = null, ?int $size = null): BinaryFileResponse
{
return new BinaryFileResponse($file, 200, [
'Content-Disposition' => HeaderUtils::makeDisposition(HeaderUtils::DISPOSITION_ATTACHMENT, $filename ?? basename($file)),
'Content-Type' => $contentType ?? 'application/octet-stream',
'Content-Length' => $size ?? ($file instanceof \SplFileInfo ? $file->getSize() : null),
]);
}

/**
* @param resource|Closure $file The file to stream as a response
* @param string $filename The name of the file to send (defaults to the basename of the file)
* @param string|null $contentType The content type of the file (defaults to `application/octet-stream`)
* @param int|null $size The size of the file
*/
public static function streamFile(mixed $file, string $filename, ?string $contentType = null, ?int $size = null): StreamedResponse
{
if (!is_resource($file) && !$file instanceof \Closure) {
throw new \InvalidArgumentException(sprintf('The file must be a resource or a closure, "%s" given.', get_debug_type($file)));
}

return new StreamedResponse($file instanceof \Closure ? $file(...) : function () use ($file) {
while (!feof($file)) {
echo fread($file, 1024);
}
}, 200, [
'Content-Disposition' => HeaderUtils::makeDisposition(HeaderUtils::DISPOSITION_ATTACHMENT, $filename),
'Content-Type' => $contentType ?? 'application/octet-stream',
'Content-Length' => $size,
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\UX\LiveComponent\LiveDownloadResponse;
use Symfony\UX\LiveComponent\LiveResponse;

/**
* @author Simon André <[email protected]>
Expand All @@ -25,32 +25,32 @@
class DownloadFileComponent
{
use DefaultActionTrait;
private const FILE_DIRECTORY = __DIR__.'/../files/';

private const FILE_DIRECTORY = __DIR__.'/../files/';

#[LiveAction]
public function download(): BinaryFileResponse
{
$file = new \SplFileInfo(self::FILE_DIRECTORY.'/foo.json');
return new LiveDownloadResponse($file);

return LiveResponse::file($file);
}

#[LiveAction]
public function generate(): BinaryFileResponse
{
$file = new \SplTempFileObject();
$file->fwrite(file_get_contents(self::FILE_DIRECTORY.'/foo.json'));
return new LiveDownloadResponse($file, 'foo.json');

return LiveResponse::file($file, 'foo.json', size: 1000);
}

#[LiveAction]
public function heavyFile(#[LiveArg] int $size): BinaryFileResponse
{
$file = new \SplFileInfo(self::FILE_DIRECTORY.'heavy.txt');
$response = new BinaryFileResponse($file);

$response = LiveResponse::file($file);
$response->headers->set('Content-Length', 10000000); // 10MB
}
}
1 change: 1 addition & 0 deletions src/LiveComponent/tests/Fixtures/files/test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
text
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,6 @@ public function testCanDownloadFileFromComponentAction(): void
'body' => ['data' => json_encode(['props' => $dehydrated->getProps()])],
])
->assertStatus(200)
->assertHeaderContains('X-Live-Download', '1')
->assertHeaderContains('Content-Type', 'application/octet-stream')
->assertHeaderContains('Content-Disposition', 'attachment')
->assertHeaderEquals('Content-Length', '21')
Expand Down Expand Up @@ -379,7 +378,6 @@ public function testCanDownloadGeneratedFileFromComponentAction(): void
],
])
->assertStatus(200)
->assertHeaderContains('X-Live-Download', '1')
->assertHeaderContains('Content-Type', 'application/octet-stream')
->assertHeaderContains('Content-Disposition', 'attachment')
->assertHeaderEquals('Content-Length', '21')
Expand Down
75 changes: 75 additions & 0 deletions src/LiveComponent/tests/Unit/LiveResponseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace Symfony\UX\LiveComponent\Tests\Unit;

use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\UX\LiveComponent\LiveResponse;
use PHPUnit\Framework\TestCase;

class LiveResponseTest extends TestCase
{
public function testSendFileWithStringPath(): void
{
$filePath = __DIR__.'/../fixtures/files/test.txt';
$response = LiveResponse::file($filePath);

$this->assertInstanceOf(BinaryFileResponse::class, $response);
$this->assertEquals('attachment; filename=test.txt', $response->headers->get('Content-Disposition'));
$this->assertEquals('application/octet-stream', $response->headers->get('Content-Type'));
}

public function testSendFileWithSplFileInfo(): void
{
$file = new File(__DIR__.'/../fixtures/files/test.txt');
$response = LiveResponse::file($file, 'custom-name.txt', 'text/plain');

$this->assertInstanceOf(BinaryFileResponse::class, $response);
$this->assertEquals('attachment; filename=custom-name.txt', $response->headers->get('Content-Disposition'));
$this->assertEquals('text/plain', $response->headers->get('Content-Type'));
}

public function testSendFileWithSplTempFileObject(): void
{
$tempFile = new \SplTempFileObject();
$tempFile->fwrite('Temporary content');
$response = LiveResponse::file($tempFile, size: 17);

$this->assertInstanceOf(BinaryFileResponse::class, $response);
$this->assertEquals('application/octet-stream', $response->headers->get('Content-Type'));
$this->assertEquals(17, $response->headers->get('Content-Length'));
}

public function testStreamFileWithResource(): void
{
$file = fopen(__DIR__.'/../fixtures/files/test.txt', 'rb');
$response = LiveResponse::streamFile($file, 'streamed-file.txt');

$this->assertInstanceOf(StreamedResponse::class, $response);
$this->assertEquals('attachment; filename=streamed-file.txt', $response->headers->get('Content-Disposition'));
$this->assertEquals('application/octet-stream', $response->headers->get('Content-Type'));
fclose($file);
}

public function testStreamFileWithClosure(): void
{
$closure = function () {
echo 'Streaming content';
};

$response = LiveResponse::streamFile($closure, 'streamed-closure.txt', 'text/plain');

$this->assertInstanceOf(StreamedResponse::class, $response);
$this->assertEquals('attachment; filename=streamed-closure.txt', $response->headers->get('Content-Disposition'));
$this->assertEquals('text/plain', $response->headers->get('Content-Type'));
}

public function testStreamFileWithInvalidType(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('The file must be a resource or a closure, "string" given.');

LiveResponse::streamFile('invalid-type', 'invalid.txt');
}
}

0 comments on commit 456c0e2

Please sign in to comment.