Installation
composer require conejerock/idempotency-bundle
Register the bundle in config/bundles.php:
return [
// ...
ConejeRock\IdempotencyBundle\IdempotencyBundle::class => ['all' => true],
];
Basic Configuration Publish the default config:
php bin/console idempotency:install
Update config/packages/idempotency.yaml to define:
idempotency_key_header (e.g., X-Idempotency-Key)idempotency_key_query_param (e.g., idempotency_key)storage (e.g., database, redis, or memory)First Use Case
Enable idempotency for a controller action by adding the #[Idempotency] attribute:
use ConejeRock\IdempotencyBundle\Attribute\Idempotency;
#[Idempotency]
public function createOrder(Request $request): JsonResponse
{
// Your logic here
}
The bundle will automatically:
Request-Level Idempotency
Use the #[Idempotency] attribute on controller methods to enforce idempotency for specific endpoints:
#[Idempotency]
public function processPayment(Request $request): Response
{
// Idempotent logic (e.g., charge a payment)
}
Custom Key Extraction
Override the default key extraction logic by implementing IdempotencyKeyExtractorInterface:
use ConejeRock\IdempotencyBundle\Extractor\IdempotencyKeyExtractorInterface;
class CustomKeyExtractor implements IdempotencyKeyExtractorInterface
{
public function extract(Request $request): ?string
{
return $request->get('custom_param');
}
}
Register it in config/packages/idempotency.yaml:
idempotency:
extractor: App\Service\CustomKeyExtractor
Conditional Idempotency
Use the #[Idempotency] attribute with options:
#[Idempotency(
ttl: 3600, // 1-hour cache
storage: 'redis',
ignoreHeaders: ['Authorization']
)]
public function updateProfile(Request $request): Response
{
// ...
}
Event-Based Extensions
Listen to idempotency.key.generated and idempotency.response.served events to log or modify behavior:
use ConejeRock\IdempotencyBundle\Event\IdempotencyEvents;
$eventDispatcher->addListener(IdempotencyEvents::KEY_GENERATED, function ($event) {
// Log the generated key
});
Laravel-Specific Setup
Since this is a Symfony bundle, use spatie/laravel-symfony-bundle to bridge compatibility:
composer require spatie/laravel-symfony-bundle
Register the bundle in config/app.php under Symfony\Bridge\Laravel\ServiceProvider.
Database Storage
For Laravel, configure the database storage in idempotency.yaml:
idempotency:
storage: database
database:
table: idempotency_keys
connection: mysql
Run migrations:
php artisan vendor:publish --provider="ConejeRock\IdempotencyBundle\Database\IdempotencyDatabaseProvider"
php artisan migrate
API Gateway Use Case
Combine with Laravel’s throttle middleware to limit rate + idempotency:
Route::middleware(['throttle:60,1', 'idempotency'])->post('/api/orders', ...);
Key Collisions
idempotency:
key_generator: ConejeRock\IdempotencyBundle\Generator\CompositeKeyGenerator
Storage Locking
memory storage.redis or database storage for production:
idempotency:
storage: redis
redis:
client: predis
Attribute Overrides
#[Idempotency] attributes override global config.Request Body Parsing
idempotency:
ignore_body: true
Log Generated Keys
Enable debug mode in idempotency.yaml:
idempotency:
debug: true
Check logs for idempotency.key.generated and idempotency.response.served events.
Clear Cache Manually purge idempotency keys:
php bin/console idempotency:clear
For Laravel, use:
php artisan idempotency:clear
Custom Storage
Implement IdempotencyStorageInterface:
use ConejeRock\IdempotencyBundle\Storage\IdempotencyStorageInterface;
class LaravelCacheStorage implements IdempotencyStorageInterface
{
public function store(string $key, array $data, int $ttl): bool
{
Cache::put($key, $data, $ttl);
return true;
}
public function retrieve(string $key): ?array
{
return Cache::get($key);
}
public function delete(string $key): bool
{
return Cache::forget($key);
}
}
Register it in config:
idempotency:
storage: App\Service\LaravelCacheStorage
Response Transformation
Override the default response caching logic by extending IdempotencyListener:
use ConejeRock\IdempotencyBundle\EventListener\IdempotencyListener;
class CustomIdempotencyListener extends IdempotencyListener
{
protected function transformResponse(Response $response): array
{
// Custom serialization logic
return ['data' => $response->getContent()];
}
}
Bind it in services.yaml:
services:
ConejeRock\IdempotencyBundle\EventListener\IdempotencyListener:
class: App\EventListener\CustomIdempotencyListener
Key Validation
Add validation logic via IdempotencyKeyValidatorInterface:
use ConejeRock\IdempotencyBundle\Validator\IdempotencyKeyValidatorInterface;
class RegexKeyValidator implements IdempotencyKeyValidatorInterface
{
public function isValid(string $key): bool
{
return preg_match('/^[a-f0-9]{32}$/', $key);
}
}
Configure in idempotency.yaml:
idempotency:
validator: App\Validator\RegexKeyValidator
How can I help you explore Laravel packages today?