rasuvaeff / yii3-idempotency
Idempotency key middleware for Yii3 APIs
v1.0.0
2026-06-12 09:48 UTC
Requires
- php: 8.3 - 8.5
- psr/clock: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
Requires (Dev)
- ergebnis/composer-normalize: ^2.51
- friendsofphp/php-cs-fixer: ^3.95
- infection/infection: ^0.29
- maglnet/composer-require-checker: ^4.17
- phpunit/phpunit: ^11.5
- rector/rector: ^2.4
- roave/backward-compatibility-check: ^8.0
- vimeo/psalm: ^6.16
This package is auto-updated.
Last update: 2026-06-12 09:49:57 UTC
README
Idempotency key middleware for Yii3 APIs. Prevents duplicate processing of POST/PUT/PATCH requests.
Using an AI coding assistant? llms.txt contains a compact API reference you can feed to the LLM.
Requirements
- PHP 8.3+
psr/clock^1.0psr/http-message^2.0psr/http-server-middleware^1.0
Installation
composer require rasuvaeff/yii3-idempotency
Usage
Basic setup
use Rasuvaeff\Yii3Idempotency\HeaderIdempotencyKeyExtractor; use Rasuvaeff\Yii3Idempotency\IdempotencyMiddleware; use Rasuvaeff\Yii3Idempotency\InMemoryIdempotencyStorage; $middleware = new IdempotencyMiddleware( keyExtractor: new HeaderIdempotencyKeyExtractor(), storage: new InMemoryIdempotencyStorage($clock), responseFactory: $responseFactory, clock: $clock, ttlSeconds: 3600, );
How it works
| Scenario | Result |
|---|---|
No idempotency key, PassThrough policy |
Request passes through |
No idempotency key, Reject policy |
400 Bad Request |
| First request with key | Handler processes, response stored |
| Same key + same payload | Stored response replayed (handler not called) |
| Same key + different payload | 422 Unprocessable Content |
| Same key while first request is still processing | 409 Conflict |
| Non-2xx handler response (3xx/4xx/5xx) | Response NOT stored — claim released, client may retry with the same key |
Non-configured method (e.g. GET, DELETE) |
Passes through untouched — idempotency applies only to methods (default POST/PUT/PATCH) |
| Expired record | Request processed as new |
Configuration
// config/params.php return [ 'rasuvaeff/yii3-idempotency' => [ 'headerName' => 'Idempotency-Key', 'policy' => 'pass_through', // or 'reject' 'ttlSeconds' => 3600, 'methods' => ['POST', 'PUT', 'PATCH'], // methods idempotency applies to ], ];
Public API
| Class | Description |
|---|---|
IdempotencyMiddleware |
PSR-15 middleware |
IdempotencyKey |
Validated key value object (1-255 chars, [A-Za-z0-9._-]+) |
IdempotencyFingerprint |
Request fingerprint (method + path + query + body hash) |
IdempotencyRecord |
Stored record with TTL |
IdempotencyResponse |
Captured response (status, headers, body) |
IdempotencyStorage |
Interface: load, claim, store, release |
IdempotencyKeyExtractor |
Interface for key extraction strategies |
InMemoryIdempotencyStorage |
In-memory implementation (for testing) |
HeaderIdempotencyKeyExtractor |
Extracts key from request header |
IdempotencyPolicy |
Enum: PassThrough, Reject |
Security
- Fingerprint includes method, path, query string, and body — prevents payload substitution
- Request body stream is rewound after fingerprinting — handlers can re-read it
- Only 2xx responses are cached; non-2xx (incl. retryable 409/423/429 and any 5xx) release the claim, so a transient failure cannot be replayed for the whole TTL
- Idempotency applies only to the configured
methods(default POST/PUT/PATCH) — safe methods pass through - Atomic claim prevents race conditions (in persistent storage adapters)
- TTL prevents indefinite storage
Examples
See examples/ for runnable scripts.
Development
make install
make build
make cs-fix
make test
make test-coverage
make mutation
make release-check
make test-coverage and make mutation bootstrap pcov inside the
composer:2 container because the base image has no coverage driver.
License
BSD-3-Clause. See LICENSE.md.