Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GotenbergAssetRuntime to avoid passing Builder in context #128

Merged
merged 3 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Sensiolabs\GotenbergBundle\GotenbergScreenshot;
use Sensiolabs\GotenbergBundle\GotenbergScreenshotInterface;
use Sensiolabs\GotenbergBundle\Twig\GotenbergAssetExtension;
use Sensiolabs\GotenbergBundle\Twig\GotenbergAssetRuntime;
use Sensiolabs\GotenbergBundle\Webhook\WebhookConfigurationRegistry;
use Sensiolabs\GotenbergBundle\Webhook\WebhookConfigurationRegistryInterface;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
Expand Down Expand Up @@ -42,6 +43,9 @@
$services->set('sensiolabs_gotenberg.twig.asset_extension', GotenbergAssetExtension::class)
->tag('twig.extension')
;
$services->set('sensiolabs_gotenberg.twig.asset_runtime', GotenbergAssetRuntime::class)
->tag('twig.runtime')
;

$services->set('sensiolabs_gotenberg.pdf', GotenbergPdf::class)
->args([
Expand Down
7 changes: 6 additions & 1 deletion src/Builder/Pdf/AbstractChromiumPdfBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Sensiolabs\GotenbergBundle\Exception\InvalidBuilderConfiguration;
use Sensiolabs\GotenbergBundle\Exception\PdfPartRenderingException;
use Sensiolabs\GotenbergBundle\Formatter\AssetBaseDirFormatter;
use Sensiolabs\GotenbergBundle\Twig\GotenbergAssetRuntime;
use Sensiolabs\GotenbergBundle\Webhook\WebhookConfigurationRegistryInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\RequestStack;
Expand Down Expand Up @@ -591,10 +592,14 @@ protected function withRenderedPart(Part $pdfPart, string $template, array $cont
throw new \LogicException(\sprintf('Twig is required to use "%s" method. Try to run "composer require symfony/twig-bundle".', __METHOD__));
}

$this->twig->getRuntime(GotenbergAssetRuntime::class)->setBuilder($this);

try {
$html = $this->twig->render($template, array_merge($context, ['_builder' => $this]));
$html = $this->twig->render($template, $context);
} catch (\Throwable $error) {
throw new PdfPartRenderingException(\sprintf('Could not render template "%s" into PDF part "%s". %s', $template, $pdfPart->value, $error->getMessage()), previous: $error);
} finally {
$this->twig->getRuntime(GotenbergAssetRuntime::class)->setBuilder(null);
}

$this->formFields[$pdfPart->value] = new DataPart($html, $pdfPart->value, 'text/html');
Expand Down
7 changes: 6 additions & 1 deletion src/Builder/Screenshot/AbstractChromiumScreenshotBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Sensiolabs\GotenbergBundle\Exception\InvalidBuilderConfiguration;
use Sensiolabs\GotenbergBundle\Exception\ScreenshotPartRenderingException;
use Sensiolabs\GotenbergBundle\Formatter\AssetBaseDirFormatter;
use Sensiolabs\GotenbergBundle\Twig\GotenbergAssetRuntime;
use Sensiolabs\GotenbergBundle\Webhook\WebhookConfigurationRegistryInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\RequestStack;
Expand Down Expand Up @@ -386,10 +387,14 @@ protected function withRenderedPart(Part $screenshotPart, string $template, arra
throw new \LogicException(\sprintf('Twig is required to use "%s" method. Try to run "composer require symfony/twig-bundle".', __METHOD__));
}

$this->twig->getRuntime(GotenbergAssetRuntime::class)->setBuilder($this);

try {
$html = $this->twig->render($template, array_merge($context, ['_builder' => $this]));
$html = $this->twig->render($template, $context);
} catch (\Throwable $error) {
throw new ScreenshotPartRenderingException(\sprintf('Could not render template "%s" into Screenshot part "%s". %s', $template, $screenshotPart->value, $error->getMessage()), previous: $error);
} finally {
$this->twig->getRuntime(GotenbergAssetRuntime::class)->setBuilder(null);
}

$this->formFields[$screenshotPart->value] = new DataPart($html, $screenshotPart->value, 'text/html');
Expand Down
20 changes: 1 addition & 19 deletions src/Twig/GotenbergAssetExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace Sensiolabs\GotenbergBundle\Twig;

use Sensiolabs\GotenbergBundle\Builder\Pdf\AbstractChromiumPdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Screenshot\AbstractChromiumScreenshotBuilder;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

Expand All @@ -12,23 +10,7 @@ final class GotenbergAssetExtension extends AbstractExtension
public function getFunctions(): array
{
return [
new TwigFunction('gotenberg_asset', $this->getAssetUrl(...), ['needs_context' => true]),
new TwigFunction('gotenberg_asset', [GotenbergAssetRuntime::class, 'getAssetUrl']),
];
}

/**
* @param array<string, mixed> $context
*/
public function getAssetUrl(array $context, string $path): string
{
$builder = $context['_builder'];

if (!$builder instanceof AbstractChromiumPdfBuilder && !$builder instanceof AbstractChromiumScreenshotBuilder) {
throw new \LogicException('You need to extend from AbstractChromiumPdfBuilder to use gotenberg_asset function.');
}

$builder->addAsset($path);

return basename($path);
}
}
37 changes: 37 additions & 0 deletions src/Twig/GotenbergAssetRuntime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Sensiolabs\GotenbergBundle\Twig;

use Sensiolabs\GotenbergBundle\Builder\Pdf\AbstractChromiumPdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Screenshot\AbstractChromiumScreenshotBuilder;

/**
* @internal
*/
final class GotenbergAssetRuntime
{
private AbstractChromiumPdfBuilder|AbstractChromiumScreenshotBuilder|null $builder = null;

public function setBuilder(AbstractChromiumPdfBuilder|AbstractChromiumScreenshotBuilder|null $builder): void
{
$this->builder = $builder;
}

/**
* This function is used to get the URL of an asset during the rendering
* of a PDF or a screenshot with the Gotenberg client.
*
* It only works if the builder is an instance of AbstractChromiumPdfBuilder
* or AbstractChromiumScreenshotBuilder.
*/
public function getAssetUrl(string $path): string
{
if (null === $this->builder) {
throw new \LogicException('The gotenberg_asset function must be used in a Gotenberg context.');
}

$this->builder->addAsset($path);

return basename($path);
}
}
11 changes: 11 additions & 0 deletions tests/Builder/AbstractBuilderTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@

namespace Sensiolabs\GotenbergBundle\Tests\Builder;

use PHPUnit\Framework\Attributes\UsesClass;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Sensiolabs\GotenbergBundle\Client\GotenbergClientInterface;
use Sensiolabs\GotenbergBundle\Formatter\AssetBaseDirFormatter;
use Sensiolabs\GotenbergBundle\Twig\GotenbergAssetExtension;
use Sensiolabs\GotenbergBundle\Twig\GotenbergAssetRuntime;
use Sensiolabs\GotenbergBundle\Webhook\WebhookConfigurationRegistryInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Mime\Part\DataPart;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\RuntimeLoader\RuntimeLoaderInterface;

#[UsesClass(GotenbergAssetExtension::class)]
#[UsesClass(GotenbergAssetRuntime::class)]
abstract class AbstractBuilderTestCase extends TestCase
{
protected const FIXTURE_DIR = __DIR__.'/../Fixtures';
Expand All @@ -37,6 +42,12 @@ public static function setUpBeforeClass(): void
'strict_variables' => true,
]);
self::$twig->addExtension(new GotenbergAssetExtension());
self::$twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {
public function load(string $class): object|null
{
return GotenbergAssetRuntime::class === $class ? new GotenbergAssetRuntime() : null;
}
});
self::$assetBaseDirFormatter = new AssetBaseDirFormatter(new Filesystem(), self::FIXTURE_DIR, self::FIXTURE_DIR);
}

Expand Down
56 changes: 56 additions & 0 deletions tests/Twig/GotenbergAssetRuntimeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace Sensiolabs\GotenbergBundle\Tests\Twig;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Sensiolabs\GotenbergBundle\Builder\Pdf\AbstractChromiumPdfBuilder;
use Sensiolabs\GotenbergBundle\Builder\Screenshot\AbstractChromiumScreenshotBuilder;
use Sensiolabs\GotenbergBundle\Twig\GotenbergAssetRuntime;

#[CoversClass(GotenbergAssetRuntime::class)]
class GotenbergAssetRuntimeTest extends TestCase
{
public function testGetAssetThrowsPerDefault(): void
{
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('The gotenberg_asset function must be used in a Gotenberg context.');
$runtime = new GotenbergAssetRuntime();
$runtime->getAssetUrl('foo');
}

public function testGetAssetThrowsWhenBuilderIsNotSet(): void
{
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('The gotenberg_asset function must be used in a Gotenberg context.');
$runtime = new GotenbergAssetRuntime();
$runtime->setBuilder(null);
$runtime->getAssetUrl('foo');
}

public function testGetAssetCallChromiumPdfBuilder(): void
{
$runtime = new GotenbergAssetRuntime();
$builder = $this->createMock(AbstractChromiumPdfBuilder::class);
$builder
->expects($this->once())
->method('addAsset')
->with('foo')
;
$runtime->setBuilder($builder);
$this->assertSame('foo', $runtime->getAssetUrl('foo'));
}

public function testGetAssetCallChromiumScreenshotBuilder(): void
{
$runtime = new GotenbergAssetRuntime();
$builder = $this->createMock(AbstractChromiumScreenshotBuilder::class);
$builder
->expects($this->once())
->method('addAsset')
->with('foo')
;
$runtime->setBuilder($builder);
$this->assertSame('foo', $runtime->getAssetUrl('foo'));
}
}
Loading