Skip to content

Commit

Permalink
Add gotenberg output filename
Browse files Browse the repository at this point in the history
  • Loading branch information
StevenRenaux committed Jan 10, 2025
1 parent 9a5c76a commit 3944a70
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 35 deletions.
1 change: 0 additions & 1 deletion src/Builder/AsyncBuilderTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ public function generateAsync(): void
}

if (null !== $this->fileName) {
// Gotenberg will add the extension to the file name (e.g. filename : "file.pdf" => generated file : "file.pdf.pdf").
$headers['Gotenberg-Output-Filename'] = $this->fileName;
}
$this->client->call($this->getEndpoint(), $this->getMultipartFormData(), $headers);
Expand Down
15 changes: 11 additions & 4 deletions src/Builder/DefaultBuilderTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Psr\Log\LoggerInterface;
use Sensiolabs\GotenbergBundle\Client\GotenbergClientInterface;
use Sensiolabs\GotenbergBundle\Exception\InvalidBuilderConfiguration;
use Sensiolabs\GotenbergBundle\Exception\JsonEncodingException;
use Sensiolabs\GotenbergBundle\Formatter\AssetBaseDirFormatter;
use Sensiolabs\GotenbergBundle\Processor\NullProcessor;
Expand Down Expand Up @@ -69,6 +70,10 @@ protected function encodeData(string $key, mixed $value): array
*/
public function fileName(string $fileName, string $headerDisposition = HeaderUtils::DISPOSITION_INLINE): static
{
if (!preg_match('/\.[^.]+$/', $fileName)) {
throw new InvalidBuilderConfiguration(\sprintf('File name "%s" needs to get extension as ".pdf", ".png" or any other valid extension.', $fileName));
}

$this->fileName = $fileName;
$this->headerDisposition = $headerDisposition;

Expand Down Expand Up @@ -214,13 +219,15 @@ public function generate(): GotenbergFileResult
'sensiolabs_gotenberg.builder' => $this::class,
]);

$processor = $this->processor ?? new NullProcessor();
$headers = [];
if (null !== $this->fileName) {
$headers['Gotenberg-Output-Filename'] = $this->fileName;
}

return new GotenbergFileResult(
$this->client->call($this->getEndpoint(), $this->getMultipartFormData()),
$processor($this->fileName),
$this->client->call($this->getEndpoint(), $this->getMultipartFormData(), $headers),
$this->processor ?? new NullProcessor(),
$this->headerDisposition,
$this->fileName,
);
}
}
22 changes: 13 additions & 9 deletions src/Builder/GotenbergFileResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@

use Sensiolabs\GotenbergBundle\Client\GotenbergResponse;
use Sensiolabs\GotenbergBundle\Exception\ProcessorException;
use Sensiolabs\GotenbergBundle\Processor\ProcessorInterface;
use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Contracts\HttpClient\ChunkInterface;

class GotenbergFileResult
{
/**
* @param \Generator<int, void, ChunkInterface, mixed> $processorGenerator
* @param ProcessorInterface<mixed> $processor
*/
public function __construct(
protected readonly GotenbergResponse $response,
protected readonly \Generator $processorGenerator,
protected readonly ProcessorInterface $processor,
protected readonly string $disposition,
protected readonly string|null $fileName = null,
) {
}

Expand Down Expand Up @@ -51,19 +50,22 @@ public function process(): mixed
throw new ProcessorException('Already processed query.');
}

$processorGenerator = ($this->processor)($this->getFileName());

foreach ($this->response->getStream() as $chunk) {
$this->processorGenerator->send($chunk);
$processorGenerator->send($chunk);
}

return $this->processorGenerator->getReturn();
return $processorGenerator->getReturn();
}

public function stream(): StreamedResponse
{
$headers = $this->getHeaders();
$headers->set('X-Accel-Buffering', 'no'); // See https://symfony.com/doc/current/components/http_foundation.html#streaming-a-json-response
if (null !== $this->fileName) {
$headers->set('Content-Disposition', HeaderUtils::makeDisposition($this->disposition, $this->fileName));

if (null !== $this->getFileName()) {
$headers->set('Content-Disposition', HeaderUtils::makeDisposition($this->disposition, $this->getFileName()));
}

return new StreamedResponse(
Expand All @@ -72,8 +74,10 @@ function (): void {
throw new ProcessorException('Already processed query.');
}

$processorGenerator = ($this->processor)($this->getFileName());

foreach ($this->response->getStream() as $chunk) {
$this->processorGenerator->send($chunk);
$processorGenerator->send($chunk);
echo $chunk->getContent();
flush();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Processor/FileProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function __construct(
public function __invoke(string|null $fileName): \Generator
{
if (null === $fileName) {
$fileName = uniqid('gotenberg_', true).'.pdf';
$fileName = uniqid('gotenberg_', true);
$this->logger?->debug('{processor}: no filename given. Content will be dumped to "{file}".', ['processor' => self::class, 'file' => $fileName]);
}

Expand Down
43 changes: 27 additions & 16 deletions tests/Builder/GotenbergFileResultTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PHPUnit\Framework\TestCase;
use Sensiolabs\GotenbergBundle\Builder\GotenbergFileResult;
use Sensiolabs\GotenbergBundle\Client\GotenbergResponse;
use Sensiolabs\GotenbergBundle\Processor\ProcessorInterface;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
Expand All @@ -17,43 +18,37 @@
final class GotenbergFileResultTest extends TestCase
{
private GotenbergResponse $response;
/** \Generator<int, void, ChunkInterface, string> */
private \Generator $processorGenerator;

/** @var ProcessorInterface<mixed> */
private ProcessorInterface $processor;

protected function setUp(): void
{
$client = new MockHttpClient(new MockResponse(['a', 'b', 'c']));
$this->response = new GotenbergResponse(
$client->stream($client->request('GET', '/')),
200,
new ResponseHeaderBag(),
new ResponseHeaderBag([
'Content-Disposition' => 'inline; filename="file.pdf"',
]),
);

$this->processorGenerator = (function () {
$content = '';
do {
$chunk = yield;
$content .= $chunk->getContent();
} while (!$chunk->isLast());

return $content;
})();
$this->processor = new TestProcessor();
}

#[TestDox('Response is processed')]
public function testProcess(): void
{
$result = new GotenbergFileResult($this->response, $this->processorGenerator, 'inline', 'file.pdf');
$result = new GotenbergFileResult($this->response, $this->processor, 'inline');
$process = $result->process();

self::assertSame('abc', $process);
self::assertSame('abc', $this->processorGenerator->getReturn());
}

#[TestDox('Response is streamed')]
public function testStream(): void
{
$result = new GotenbergFileResult($this->response, $this->processorGenerator, 'inline', 'file.pdf');
$result = new GotenbergFileResult($this->response, $this->processor, 'inline');
$stream = $result->stream();

ob_start();
Expand All @@ -63,6 +58,22 @@ public function testStream(): void

self::assertSame('inline; filename=file.pdf', $stream->headers->get('Content-Disposition'));
self::assertSame('no', $stream->headers->get('X-Accel-Buffering'));
self::assertSame('abc', $this->processorGenerator->getReturn());
}
}

/**
* @implements ProcessorInterface<string>
*/
class TestProcessor implements ProcessorInterface
{
public function __invoke(string|null $fileName): \Generator
{
$content = '';
do {
$chunk = yield;
$content .= $chunk->getContent();
} while (!$chunk->isLast());

return $content;
}
}
10 changes: 7 additions & 3 deletions tests/Builder/Pdf/AbstractPdfBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,20 @@ public function testFilenameIsCorrectlySetOnResponse(): void
{
// @phpstan-ignore-next-line
$this->gotenbergClient = new GotenbergClient(new MockHttpClient([
new MockResponse(),
new MockResponse(info: [
'response_headers' => [
'Content-Disposition' => 'attachment; filename="some_file.pdf"',
],
]),
]));

$response = $this->getPdfBuilder()
->fileName('some_file.png', HeaderUtils::DISPOSITION_ATTACHMENT)
->fileName('some_file.pdf', HeaderUtils::DISPOSITION_ATTACHMENT)
->generate()
->stream()
;

self::assertSame('attachment; filename=some_file.png', $response->headers->get('Content-Disposition'));
self::assertSame('attachment; filename=some_file.pdf', $response->headers->get('Content-Disposition'));
}

public static function nativeNormalizersProvider(): \Generator
Expand Down
6 changes: 5 additions & 1 deletion tests/Builder/Screenshot/AbstractScreenshotBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ public function testFilenameIsCorrectlySetOnResponse(): void
{
// @phpstan-ignore-next-line
$this->gotenbergClient = new GotenbergClient(new MockHttpClient([
new MockResponse(),
new MockResponse(info: [
'response_headers' => [
'Content-Disposition' => 'attachment; filename="some_file.png"',
],
]),
]));

$response = $this->getScreenshotBuilder()
Expand Down

0 comments on commit 3944a70

Please sign in to comment.