symfony/remote-event
Symfony RemoteEvent component helps you receive and handle remote events from third-party services. It provides a structured way to parse payloads, validate signatures, and dispatch events within your application for consistent, secure integrations.
Installation:
composer require symfony/remote-event
For Laravel, also install Symfony’s HTTP components for request handling:
composer require symfony/http-foundation symfony/http-client
Define a Remote Event Class:
Create a DTO class annotated with #[RemoteEvent] to represent your webhook payload. Example for Stripe:
use Symfony\Component\RemoteEvent\Attribute\RemoteEvent;
#[RemoteEvent]
class StripePaymentSucceededEvent
{
public function __construct(
public string $id,
public string $amount,
public string $currency,
) {}
}
Create a Consumer:
Implement Symfony\Component\RemoteEvent\Consumer\ConsumerInterface to handle the event:
use Symfony\Component\RemoteEvent\Consumer\ConsumerInterface;
class StripePaymentConsumer implements ConsumerInterface
{
public function __invoke(StripePaymentSucceededEvent $event): void
{
// Handle payment logic (e.g., update database, send notification)
}
}
Set Up a Dispatcher:
Configure the RemoteEventDispatcher with a consumer resolver (e.g., Symfony\Component\RemoteEvent\Consumer\ConsumerResolver):
use Symfony\Component\RemoteEvent\RemoteEventDispatcher;
use Symfony\Component\RemoteEvent\Consumer\ConsumerResolver;
$dispatcher = new RemoteEventDispatcher(
new ConsumerResolver([StripePaymentSucceededEvent::class => StripePaymentConsumer::class])
);
Handle Incoming Requests: Parse the incoming request and dispatch the event:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\RemoteEvent\RemoteEventParser;
$request = Request::createFromGlobals();
$parser = new RemoteEventParser();
$event = $parser->parse($request, StripePaymentSucceededEvent::class);
$dispatcher->dispatch($event);
Add Laravel Integration (Optional): Create a middleware to handle remote events in Laravel’s request pipeline:
namespace App\Http\Middleware;
use Closure;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\RemoteEvent\RemoteEventParser;
use Symfony\Component\RemoteEvent\RemoteEventDispatcher;
class RemoteEventMiddleware
{
public function __construct(
private RemoteEventParser $parser,
private RemoteEventDispatcher $dispatcher
) {}
public function handle(Request $request, Closure $next)
{
if ($request->isMethod('post') && $request->headers->has('X-Signature')) {
$event = $parser->parse($request, StripePaymentSucceededEvent::class);
$this->dispatcher->dispatch($event);
}
return $next($request);
}
}
Stripe Webhook Handling:
StripePaymentSucceededEvent DTO and consumer.Event-Driven Workflows:
Messenger (if integrated) or Laravel’s Bus/Queue to process remote events asynchronously.StripePaymentSucceededEvent to a queue for delayed processing.Provider-Specific Consumers:
StripeConsumer, GitHubConsumer) and register them in a ConsumerResolver.$resolver = new ConsumerResolver([
StripePaymentSucceededEvent::class => StripePaymentConsumer::class,
GitHubPushEvent::class => GitHubPushConsumer::class,
]);
Validation and Security:
RemoteEventParser.$parser = new RemoteEventParser(
new Symfony\Component\RemoteEvent\Validator\SignatureValidator(
'your-secret-key',
'sha256'
)
);
Middleware Pipeline:
$dispatcher = new RemoteEventDispatcher($resolver, [
new LogMiddleware(),
new RateLimitMiddleware(),
]);
Laravel Integration:
RemoteEventDispatcher in a Laravel service provider:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Symfony\Component\RemoteEvent\RemoteEventDispatcher;
use Symfony\Component\RemoteEvent\Consumer\ConsumerResolver;
class RemoteEventServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(RemoteEventDispatcher::class, function ($app) {
return new RemoteEventDispatcher(
new ConsumerResolver([
StripePaymentSucceededEvent::class => $app->make(StripePaymentConsumer::class),
])
);
});
}
}
Webhook Endpoint:
RemoteEventParser and RemoteEventDispatcher.Async Processing:
$dispatcher->dispatch($event); // Uses Symfony Messenger under the hood
// OR for Laravel:
dispatch(new HandleRemoteEvent($event));
Testing:
RemoteEventParser and RemoteEventDispatcher in unit tests.$parser = $this->createMock(RemoteEventParser::class);
$parser->method('parse')->willReturn(new StripePaymentSucceededEvent(...));
$dispatcher->dispatch($event); // Test consumer logic
Symfony-Laravel Bridge:
Illuminate\Contracts\Http\Kernel to integrate Symfony’s HttpFoundation request handling.use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
use Illuminate\Http\Request as LaravelRequest;
$symfonyRequest = SymfonyRequest::createFromGlobals();
$laravelRequest = new LaravelRequest($symfonyRequest->query->all(), $symfonyRequest->request->all(), [], [], [], $symfonyRequest->server->all());
Queue Adapters:
RemoteEvent:
namespace App\Jobs;
use Symfony\Component\RemoteEvent\RemoteEventInterface;
class HandleRemoteEvent implements ShouldQueue
{
public function __construct(public RemoteEventInterface $event) {}
public function handle()
{
// Process event (e.g., update database, send email)
}
}
Event Schema Validation:
Validator component to validate event payloads against JSON Schema:
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints\Json;
$validator = Validation::createValidator();
$constraints = new Json(['schema' => file_get_contents('stripe-schema.json')]);
$errors = $validator->validate($event, $constraints);
Observability:
use Psr\Log\LoggerInterface;
class StripePaymentConsumer implements ConsumerInterface
{
public function __construct(private LoggerInterface $logger) {}
public function __invoke(StripePaymentSucceededEvent $event): void
{
$this->logger->info('Stripe payment succeeded', [
'event_id' => $event->id,
'amount' => $event->amount,
'currency' => $event->currency,
]);
}
}
Symfony Dependency Overhead:
HttpFoundation, Messenger), which may conflict with Laravel’s ecosystem.symfony/http-foundation and symfony/messenger explicitly to avoid version conflicts.Request Parsing Differences:
HttpFoundation\Request and Laravel’s Illuminate\Http\Request have different APIs.use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
use Illuminate\Http\Request as LaravelRequest;
class RequestConverter
{
public static function toSymfony(LaravelRequest $laravelRequest): SymfonyRequest
{
return SymfonyRequest::create(
$laravelRequest->getUri(),
$laravelRequest->getMethod(),
$laravelRequest->query->all(),
$laravelRequest->request->all(),
[],
['HTTP_' => array_map('strtoupper', $laravelRequest->header->all())]
);
}
How can I help you explore Laravel packages today?