declare (strict_types=1);
namespace ApiClients\Client\Example\Internal\WebHook;
enum Event
{
case HealthCheck;
case InventoryUpdate;
case AdoptedPet;
case SurrenderedPet;
case WebhookStorePolicyCreated;
case WebhookStorePolicyDeleted;
}
declare (strict_types=1);
namespace ApiClients\Client\Example\Internal\WebHook;
final class WebHooks
{
public function __construct(private readonly \League\OpenAPIValidation\Schema\SchemaValidator $requestSchemaValidator, private readonly \ApiClients\Client\Example\Internal\WebHook\Hydrator $hydrator)
{
}
public function resolve(array $headers, array $data): \ApiClients\Client\Example\Schema\HealthCheck|\ApiClients\Client\Example\Schema\InventoryUpdate|\ApiClients\Client\Example\Schema\AdoptedPet|\ApiClients\Client\Example\Schema\SurrenderedPet|\ApiClients\Client\Example\Schema\WebhookStorePolicyCreated|\ApiClients\Client\Example\Schema\WebhookStorePolicyDeleted
{
$headers = (static function (array $headers): array {
$loweredHeaders = [];
foreach ($headers as $key => $value) {
$loweredHeaders[strtolower($key)] = $value;
}
return $loweredHeaders;
})($headers);
return match ($this->detectDiscriminatedEvent($data)) {
Event::AdoptedPet => $this->validatedHydrate(\ApiClients\Client\Example\Schema\AdoptedPet::class, $data),
Event::SurrenderedPet => $this->validatedHydrate(\ApiClients\Client\Example\Schema\SurrenderedPet::class, $data),
Event::WebhookStorePolicyCreated => $this->validatedHydrate(\ApiClients\Client\Example\Schema\WebhookStorePolicyCreated::class, $data),
Event::WebhookStorePolicyDeleted => $this->validatedHydrate(\ApiClients\Client\Example\Schema\WebhookStorePolicyDeleted::class, $data),
default => $this->resolveByHeaders($headers, $data),
};
}
private function detectDiscriminatedEvent(array $data): ?Event
{
if (array_key_exists('eventType', $data)) {
$event = match ($data['eventType']) {
'adopted' => Event::AdoptedPet,
'surrendered' => Event::SurrenderedPet,
default => null,
};
if ($event instanceof Event) {
return $event;
}
}
if (array_key_exists('action', $data)) {
$event = match ($data['action']) {
'created' => Event::WebhookStorePolicyCreated,
'deleted' => Event::WebhookStorePolicyDeleted,
default => null,
};
if ($event instanceof Event) {
return $event;
}
}
return null;
}
private function resolveByHeaders(array $headers, array $data): \ApiClients\Client\Example\Schema\HealthCheck|\ApiClients\Client\Example\Schema\InventoryUpdate
{
$error = new \RuntimeException('No webhook matching given headers and data');
try {
if ($headers['x-petstore-event'] === 'healthCheck' && array_key_exists('message', $data)) {
return $this->validatedHydrate(\ApiClients\Client\Example\Schema\HealthCheck::class, $data);
}
} catch (\Throwable $throwable) {
$error = $throwable;
}
try {
if ($headers['x-petstore-event'] === 'inventoryUpdate' && array_key_exists('sku', $data) && array_key_exists('quantity', $data)) {
return $this->validatedHydrate(\ApiClients\Client\Example\Schema\InventoryUpdate::class, $data);
}
} catch (\Throwable $throwable) {
$error = $throwable;
}
throw $error;
}
private function validatedHydrate(string $className, array $data): object
{
$this->requestSchemaValidator->validate($data, \cebe\openapi\Reader::readFromJson($className::SCHEMA_JSON, \cebe\openapi\spec\Schema::class));
return $this->hydrator->hydrate(className::class, $data);
}
}
declare (strict_types=1);
namespace ApiClients\Client\Example;
use OpenAPITools\Contract\WebHookHandlerInterface;
use \ApiClients\Client\Example\Internal\WebHook\InvalidWebHookRequestException;
use \ApiClients\Client\Example\Internal\WebHook\WebHooks;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
final readonly class WebHookMiddleware implements Psr\Http\Server\MiddlewareInterface
{
public function __construct(private readonly \ApiClients\Client\Example\Internal\WebHook\WebHooks $webHooks, private readonly \OpenAPITools\Contract\WebHookHandlerInterface $handler, private readonly array $paths = ['/webhook'])
{
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if ($this->paths !== [] && !in_array($request->getUri()->getPath(), $this->paths, true)) {
return $handler->handle($request);
}
$body = (string) $request->getBody();
if ($body === '') {
throw new InvalidWebHookRequestException('Missing webhook request body');
}
$data = json_decode($body, true);
if (is_array($data) !== true) {
throw new InvalidWebHookRequestException('Invalid webhook request body');
}
$headers = [];
foreach ($request->getHeaders() as $name => $values) {
$headers[strtolower($name)] = $values[0];
}
try {
$payload = $this->webHooks->resolve($headers, $data);
} catch (\Throwable $throwable) {
throw new InvalidWebHookRequestException('Failed to resolve webhook', 0, $throwable);
}
return $this->handler->handle($payload);
}
}
final readonly class MyWebHookHandler implements WebHookHandlerInterface
{
public function handle(object $payload): ResponseInterface
{
return match ($payload::class) {
Ping::class => $this->ping($payload),
default => new EmptyResponse(404),
};
}
}
$middleware = new WebHookMiddleware(
new Internal\WebHook\WebHooks($requestSchemaValidator, new Internal\WebHook\Hydrator()),
new MyWebHookHandler(),
paths: ['/webhook'],
);
use OpenAPITools\Generator\PSR15\WebHook\WebHookMiddleware;
use PhpParser\BuilderFactory;
$generator = new WebHookMiddleware(new BuilderFactory(), defaultPaths: ['/webhook']);
foreach ($generator->generate($package, $representation->namespace($package->namespace)) as $file) {
// $file->pathPrefix — e.g. "src"
// $file->fqcn — e.g. "WebHookMiddleware"
// $file->contents — PhpParser Node
}
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.