1. Go to this page and download the library: Download spiral/idempotency library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
spiral / idempotency example snippets
// app/src/Application/Kernel.php
public function defineBootloaders(): array
{
return [
// ...
\Spiral\Idempotency\Bootloader\IdempotencyBootloader::class,
// opt-in: HTTP wiring for the #[Idempotent] attribute
\Spiral\Idempotency\Bootloader\HttpIdempotencyBootloader::class,
// opt-in: idempotency tables in the ORM schema (cycle:sync / cycle:migrate)
\Spiral\Idempotency\Bootloader\CycleSchemaBootloader::class,
];
}
// app/config/idempotency.php
use Spiral\Idempotency\Driver\Cycle\CycleInboxConfig;
use Spiral\Idempotency\Driver\Cycle\CycleLeaseConfig;
use Spiral\Idempotency\Http\HttpKeyMiddleware;
use Spiral\Idempotency\Http\HttpOutcomeMiddleware;
return [
'default' => 'payments',
// Resolution middleware, outer → inner. The outcome middleware sits OUTERMOST: it marshals
// the response (replay headers, Locked → 409, missing key → 400); the key middleware inside
// it extracts and normalizes the key.
'transports' => [
'http' => [
HttpOutcomeMiddleware::class,
HttpKeyMiddleware::class,
],
],
// Semantic aliases: the handler code names an alias, the driver and the declared guarantee
// live here. The declared guarantee is verified against the driver capability at bootstrap.
'storages' => [
'payments' => new CycleLeaseConfig(
table: 'idempotency_lease',
lockTtl: 30, // seconds the PROCESSING lock is held
retentionTtl: 3600, // seconds the cached result is kept
),
'orders' => new CycleInboxConfig(
table: 'idempotency_inbox',
),
],
];
use Spiral\Idempotency\Interceptor\IdempotencyInterceptor;
final class AppBootloader extends DomainBootloader
{
protected const INTERCEPTORS = [
// ...your existing interceptors (Cycle, Guard, ...) — innermost after auth:
IdempotencyInterceptor::class,
];
}
use Spiral\Idempotency\Attribute\Idempotent;
final class PaymentController
{
#[Route(route: '/payments/charge', methods: 'POST')]
#[Idempotent(storage: 'payments')]
public function charge(): ResponseInterface
{
// charge the gateway, return the response — it will be cached and replayed
}
}
// app/config/idempotency.php
use Spiral\Idempotency\Queue\QueueKeyMiddleware;
use Spiral\Idempotency\Queue\QueueRetryMiddleware;
'transports' => [
'queue' => [
QueueRetryMiddleware::class, // outer: maps failure modes back to the transport
QueueKeyMiddleware::class, // inner: extracts the key from the job header
],
],
use Spiral\Idempotency\Interceptor\IdempotencyInterceptor;
$queue->addConsumeInterceptor(IdempotencyInterceptor::class);
#[Idempotent(storage: 'orders', key: 'orderId')] // dot-path over the job payload
public function invoke(string $orderId, array $payload): void
{
// runs once per orderId; a redelivered job replays / is skipped
}
// a bootloader of the app — the config stack lists class names, so bind the configured instance
QueueKeyMiddleware::class => static fn(KeyResolver $resolver): QueueKeyMiddleware
=> new QueueKeyMiddleware($resolver, fallbackToJobId: true),
#[Idempotent(storage: 'orders')] // key from the job header
final class DispatchEvent extends JobHandler {} // handle() is inherited from JobHandler
use Spiral\Idempotency\Events\HasIdempotencyKey;
final class OrderPlaced implements HasIdempotencyKey
{
public function __construct(public readonly string $id) {} // outbox message_id, not a fresh uuid
public function idempotencyKey(): string
{
return $this->id;
}
}
final class PaymentService
{
#[Listener]
#[Idempotent(storage: 'events')]
public function charge(OrderPlaced $event): void {}
#[Listener]
#[Idempotent(storage: 'events')]
public function notify(OrderPlaced $event): void {}
}
interface DomainEvent extends HasIdempotencyKey {}
trait WithEventId
{
public readonly Uid $id; // the outbox message_id, preserved across re-publications
public function idempotencyKey(): string
{
return $this->id->rawValue();
}
}
use Spiral\Idempotency\Driver\Cycle\CycleContext;
use Spiral\Idempotency\IdempotencyContext;
#[Idempotent(storage: 'orders', key: 'command.orderId')]
public function place(PlaceOrder $command, IdempotencyContext $ctx): array
{
\assert($ctx instanceof CycleContext);
// ORM entities: a scoped Unit of Work flushed inside the transaction
$ctx->entityManager()->persist(new Order(...));
// ...or raw DBAL through the transactional connection
$ctx->database()->insert('order_events')->values([...])->run();
return ['status' => 'placed'];
}
use Spiral\Idempotency\ExecuteOptions;
use Spiral\Idempotency\FailurePolicy;
use Spiral\Idempotency\IdempotencyContext;
use Spiral\Idempotency\IdempotencyRegistry;
public function __construct(
private readonly IdempotencyRegistry $registry,
) {}
public function handle(string $transactionId): Receipt
{
return $this->registry->get('payments')->execute(
$transactionId,
static fn(IdempotencyContext $ctx): Receipt => /* the operation */,
new ExecuteOptions(lockTtl: 60, ttl: 86400, failurePolicy: FailurePolicy::Cache),
);
}
final class PaymentDeclined extends \DomainException implements ReplayableFailure
{
public function toReplayPayload(): array
{
return ['code' => $this->code, 'reason' => $this->reason];
}
public static function fromReplayPayload(array $payload): static
{
return new self($payload['code'], $payload['reason']);
}
}
use Spiral\Idempotency\FailurePolicy;
#[Idempotent(storage: 'jobs', key: 'event.id', failurePolicy: FailurePolicy::Release)]
final class DeclineRenderer implements DomainFailureRenderer
{
public function __construct(private ResponseFactoryInterface $responses) {}
public function render(\Throwable $failure): ?ResponseInterface
{
// Return null for failures this renderer does not own — they are rethrown untouched.
return $failure instanceof PaymentDeclined
? $this->responses->createResponse(402)
: null;
}
}
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.