ez-php / idempotency
Idempotency-Key middleware for safe POST retries
Requires
- php: ^8.5
- ez-php/contracts: ^2.0
- ez-php/http: ^2.0
Requires (Dev)
- ez-php/cache: ^2.0
- ez-php/docker: ^2.0
- friendsofphp/php-cs-fixer: ^3.94
- phpstan/phpstan: ^2.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^13.0
Suggests
- ez-php/cache: Provides the CacheInterface implementation that stores responses and locks
Provides
None
Conflicts
None
Replaces
None
README
Idempotency-Key middleware for safe retries of POST/PATCH/PUT/DELETE requests.
The first request with a given key runs normally and its response (status, headers, body) is stored in the cache. A retry with the same key returns the stored response with an Idempotent-Replayed: true header instead of executing the handler again.
Installation
composer require ez-php/idempotency ez-php/cache
ez-php/cache is a soft dependency: install it (or provide any CacheInterface binding).
Usage
use EzPhp\Idempotency\IdempotencyMiddleware; $router->post('/orders', [OrderController::class, 'store']) ->middleware(IdempotencyMiddleware::class);
Clients send:
POST /orders
Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9324
Behaviour
| Situation | Response |
|---|---|
No Idempotency-Key header, or GET/HEAD/OPTIONS |
request passes through |
Key is not [A-Za-z0-9_.:-]{1,255} |
400 |
| Same key, different method / URI / body | 422 |
| Same key while the first request is still running | 409 |
| Same key after completion | stored response + Idempotent-Replayed: true |
Responses with status >= 500 and streamed responses are not stored, so a failed attempt can be retried.
Configuration
new IdempotencyMiddleware( cache: $cache, ttl: 86400, // seconds a response stays replayable lockTtl: 30, // max seconds the in-flight lock is held scope: fn (RequestInterface $r): string => (string) $userId, // optional key partition );
Keys are partitioned per caller: by default a hash of the Authorization and Cookie headers, so different users cannot collide. Set scope to partition by something more stable (e.g. the user id) — for instance when the session cookie rotates between retries.
Development
composer install composer full
License
MIT