Skip to content

Commit c5809a8

Browse files
committed
feat: add Webhook component integration
1 parent ee33766 commit c5809a8

35 files changed

+462
-54
lines changed

composer-dependency-analyser.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@
1313
->addPathToScan(__DIR__.'/src/Debug', isDev: true)
1414
->addPathToScan(__DIR__.'/tests', isDev: true)
1515

16+
->ignoreErrorsOnPackage('symfony/remote-event', [
17+
ErrorType::DEV_DEPENDENCY_IN_PROD,
18+
])
1619
->ignoreErrorsOnPackage('symfony/routing', [
1720
ErrorType::DEV_DEPENDENCY_IN_PROD,
1821
])
22+
->ignoreErrorsOnPackage('symfony/webhook', [
23+
ErrorType::DEV_DEPENDENCY_IN_PROD,
24+
])
1925
->ignoreErrorsOnPackage('twig/twig', [
2026
ErrorType::DEV_DEPENDENCY_IN_PROD,
2127
])

composer.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@
4242
"symfony/framework-bundle": "^6.4 || ^7.0",
4343
"symfony/http-client": "^6.4 || ^7.0",
4444
"symfony/monolog-bundle": "^3.10",
45+
"symfony/remote-event": "^6.4 || ^7.0",
4546
"symfony/routing": "^6.4 || ^7.0",
4647
"symfony/stopwatch": "^6.4 || ^7.0",
4748
"symfony/twig-bundle": "^6.4 || ^7.0",
4849
"symfony/var-dumper": "^6.4 || ^7.0",
50+
"symfony/webhook": "^6.4 || ^7.0",
4951
"twig/twig": "^3.14"
5052
},
5153
"config": {
@@ -56,7 +58,9 @@
5658
},
5759
"suggest": {
5860
"symfony/monolog-bundle": "Enables logging througout the generating process.",
59-
"symfony/twig-bundle": "Allows you to use Twig to render templates into PDF",
61+
"symfony/remote-event": "Allows you to use Webhook to handle asynchronous generation.",
62+
"symfony/twig-bundle": "Allows you to use Twig to render templates into PDF.",
63+
"symfony/webhook": "Allows you to use Webhook to handle asynchronous generation.",
6064
"monolog/monolog": "Enables logging througout the generating process."
6165
}
6266
}

config/builder_pdf.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
service('sensiolabs_gotenberg.asset.base_dir_formatter'),
2424
service('.sensiolabs_gotenberg.webhook_configuration_registry'),
2525
service('request_stack'),
26+
service('router')->nullOnInvalid(),
2627
service('twig')->nullOnInvalid(),
2728
])
2829
->call('setLogger', [service('logger')->nullOnInvalid()])
@@ -36,8 +37,8 @@
3637
service('sensiolabs_gotenberg.asset.base_dir_formatter'),
3738
service('.sensiolabs_gotenberg.webhook_configuration_registry'),
3839
service('request_stack'),
39-
service('twig')->nullOnInvalid(),
4040
service('router')->nullOnInvalid(),
41+
service('twig')->nullOnInvalid(),
4142
])
4243
->call('setLogger', [service('logger')->nullOnInvalid()])
4344
->call('setRequestContext', [service('.sensiolabs_gotenberg.request_context')->nullOnInvalid()])
@@ -51,6 +52,7 @@
5152
service('sensiolabs_gotenberg.asset.base_dir_formatter'),
5253
service('.sensiolabs_gotenberg.webhook_configuration_registry'),
5354
service('request_stack'),
55+
service('router')->nullOnInvalid(),
5456
service('twig')->nullOnInvalid(),
5557
])
5658
->call('setLogger', [service('logger')->nullOnInvalid()])

config/builder_screenshot.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
service('sensiolabs_gotenberg.asset.base_dir_formatter'),
2121
service('.sensiolabs_gotenberg.webhook_configuration_registry'),
2222
service('request_stack'),
23+
service('router')->nullOnInvalid(),
2324
service('twig')->nullOnInvalid(),
2425
])
2526
->call('setLogger', [service('logger')->nullOnInvalid()])
@@ -33,8 +34,8 @@
3334
service('sensiolabs_gotenberg.asset.base_dir_formatter'),
3435
service('.sensiolabs_gotenberg.webhook_configuration_registry'),
3536
service('request_stack'),
36-
service('twig')->nullOnInvalid(),
3737
service('router')->nullOnInvalid(),
38+
service('twig')->nullOnInvalid(),
3839
])
3940
->call('setLogger', [service('logger')->nullOnInvalid()])
4041
->call('setRequestContext', [service('.sensiolabs_gotenberg.request_context')->nullOnInvalid()])
@@ -48,6 +49,7 @@
4849
service('sensiolabs_gotenberg.asset.base_dir_formatter'),
4950
service('.sensiolabs_gotenberg.webhook_configuration_registry'),
5051
service('request_stack'),
52+
service('router')->nullOnInvalid(),
5153
service('twig')->nullOnInvalid(),
5254
])
5355
->call('setLogger', [service('logger')->nullOnInvalid()])

docs/async/webhook.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
## Using the Symfony Webhook component
2+
3+
Symfony provides a specific [Webhook component](https://symfony.com/doc/current/webhook.html) dedicated to this task.
4+
5+
Its role is to parse requests related to known webhooks and dispatch a corresponding remote event. Then, this event can
6+
be handled by your application through the [Messenger component](https://symfony.com/doc/current/messenger.html).
7+
8+
The GotenbergBundle offers a native integration of this component if installed.
9+
10+
### Usage
11+
12+
To connect the provider to your application, you need to configure the Webhook component routing:
13+
14+
```yaml
15+
# config/packages/webhook.yaml
16+
framework:
17+
webhook:
18+
routing:
19+
gotenberg:
20+
service: 'sensiolabs_gotenberg.webhook.request_parser'
21+
```
22+
23+
Then, create your handler to respond to the Gotenberg RemoteEvent:
24+
25+
```php
26+
use Sensiolabs\GotenbergBundle\RemoteEvent\ErrorGotenbergEvent;
27+
use Sensiolabs\GotenbergBundle\RemoteEvent\SuccessGotenbergEvent;
28+
use Symfony\Component\RemoteEvent\Consumer\ConsumerInterface;
29+
use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer;
30+
use Symfony\Component\RemoteEvent\RemoteEvent;
31+
32+
#[AsRemoteEventConsumer('gotenberg')]
33+
class WebhookListener implements ConsumerInterface
34+
{
35+
public function consume(RemoteEvent $event): void
36+
{
37+
if ($event instanceof SuccessGotenbergEvent) {
38+
// Handle the event
39+
// PDF content is available as a resource through the getFile() method
40+
} elseif ($event instanceof ErrorGotenbergEvent) {
41+
// Handle the error
42+
}
43+
}
44+
}
45+
```
46+
47+
> [!WARNING]
48+
> The webhook component **won't be used** if a [native webhook configuration](native.md) is set.

docs/webhook.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# Going async
22

33
* [Native](./async/native.md)
4+
* [Using the Symfony Webhook component](./async/webhook.md)

src/Builder/AsyncBuilderTrait.php

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
namespace Sensiolabs\GotenbergBundle\Builder;
44

5-
use Sensiolabs\GotenbergBundle\Exception\MissingRequiredFieldException;
5+
use Sensiolabs\GotenbergBundle\Exception\WebhookConfigurationException;
66
use Sensiolabs\GotenbergBundle\Webhook\WebhookConfigurationRegistryInterface;
7+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
78

89
trait AsyncBuilderTrait
910
{
@@ -30,16 +31,22 @@ trait AsyncBuilderTrait
3031

3132
private WebhookConfigurationRegistryInterface $webhookConfigurationRegistry;
3233

34+
protected UrlGeneratorInterface|null $urlGenerator;
35+
3336
public function generateAsync(): void
3437
{
35-
if (null === $this->successWebhookUrl) {
36-
throw new MissingRequiredFieldException('->webhookUrl() was never called.');
37-
}
38+
$successWebhookUrl = $this->successWebhookUrl;
39+
if (!$successWebhookUrl) {
40+
if (!$this->urlGenerator) {
41+
throw new WebhookConfigurationException(\sprintf('A webhook URL or Router is required to use "%s" method. Set the URL or try to run "composer require symfony/routing".', __METHOD__));
42+
}
3843

39-
$errorWebhookUrl = $this->errorWebhookUrl ?? $this->successWebhookUrl;
44+
$successWebhookUrl = $this->urlGenerator->generate('_webhook_controller', ['type' => 'gotenberg'], UrlGeneratorInterface::ABSOLUTE_URL);
45+
}
46+
$errorWebhookUrl = $this->errorWebhookUrl ?? $successWebhookUrl;
4047

4148
$headers = [
42-
'Gotenberg-Webhook-Url' => $this->successWebhookUrl,
49+
'Gotenberg-Webhook-Url' => $successWebhookUrl,
4350
'Gotenberg-Webhook-Error-Url' => $errorWebhookUrl,
4451
];
4552

@@ -59,6 +66,7 @@ public function generateAsync(): void
5966
// Gotenberg will add the extension to the file name (e.g. filename : "file.pdf" => generated file : "file.pdf.pdf").
6067
$headers['Gotenberg-Output-Filename'] = $this->fileName;
6168
}
69+
6270
$this->client->call($this->getEndpoint(), $this->getMultipartFormData(), $headers);
6371
}
6472

src/Builder/Pdf/AbstractChromiumPdfBuilder.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\Component\HttpFoundation\RequestStack;
2020
use Symfony\Component\Mime\Part\DataPart;
2121
use Symfony\Component\Mime\Part\File as DataPartFile;
22+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
2223
use Twig\Environment;
2324

2425
abstract class AbstractChromiumPdfBuilder extends AbstractPdfBuilder
@@ -30,9 +31,10 @@ public function __construct(
3031
AssetBaseDirFormatter $asset,
3132
WebhookConfigurationRegistryInterface $webhookConfigurationRegistry,
3233
private readonly RequestStack $requestStack,
34+
UrlGeneratorInterface|null $urlGenerator = null,
3335
private readonly Environment|null $twig = null,
3436
) {
35-
parent::__construct($gotenbergClient, $asset, $webhookConfigurationRegistry);
37+
parent::__construct($gotenbergClient, $asset, $webhookConfigurationRegistry, $urlGenerator);
3638

3739
$normalizers = [
3840
'extraHttpHeaders' => function (mixed $value): array {

src/Builder/Pdf/AbstractPdfBuilder.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Sensiolabs\GotenbergBundle\Client\GotenbergClientInterface;
1010
use Sensiolabs\GotenbergBundle\Formatter\AssetBaseDirFormatter;
1111
use Sensiolabs\GotenbergBundle\Webhook\WebhookConfigurationRegistryInterface;
12+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
1213

1314
abstract class AbstractPdfBuilder implements PdfBuilderInterface, AsyncBuilderInterface
1415
{
@@ -20,10 +21,12 @@ public function __construct(
2021
GotenbergClientInterface $gotenbergClient,
2122
AssetBaseDirFormatter $asset,
2223
WebhookConfigurationRegistryInterface $webhookConfigurationRegistry,
24+
UrlGeneratorInterface|null $urlGenerator = null,
2325
) {
2426
$this->client = $gotenbergClient;
2527
$this->asset = $asset;
2628
$this->webhookConfigurationRegistry = $webhookConfigurationRegistry;
29+
$this->urlGenerator = $urlGenerator;
2730

2831
$this->normalizers = [
2932
'metadata' => function (mixed $value): array {

src/Builder/Pdf/UrlPdfBuilder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ public function __construct(
2222
AssetBaseDirFormatter $asset,
2323
WebhookConfigurationRegistryInterface $webhookConfigurationRegistry,
2424
RequestStack $requestStack,
25+
UrlGeneratorInterface|null $urlGenerator = null,
2526
Environment|null $twig = null,
26-
private readonly UrlGeneratorInterface|null $urlGenerator = null,
2727
) {
28-
parent::__construct($gotenbergClient, $asset, $webhookConfigurationRegistry, $requestStack, $twig);
28+
parent::__construct($gotenbergClient, $asset, $webhookConfigurationRegistry, $requestStack, $urlGenerator, $twig);
2929

3030
$this->addNormalizer('route', $this->generateUrlFromRoute(...));
3131
}

0 commit comments

Comments
 (0)