fyennyi / async-cache-php
Asynchronous caching library with rate limiting and stale-while-revalidate support.
Fund package maintenance!
Patreon
Open Collective
Installs: 303
Dependents: 4
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/fyennyi/async-cache-php
Requires
- php: >=8.1
- psr/clock: ^1.0
- psr/event-dispatcher: ^1.0
- psr/log: ^1.0 || ^2.0 || ^3.0
- psr/simple-cache: ^1.0 || ^2.0 || ^3.0
- react/async: ^4.3
- react/cache: ^1.2
- react/promise-timer: ^1.11
- symfony/clock: ^6.3
- symfony/lock: ^6.0 || ^7.0 || ^8.0
- symfony/rate-limiter: ^6.0 || ^7.0 || ^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.93
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^10.0 || ^11.0
- react/http: ^1.11
- react/socket: ^1.17
README
An asynchronous caching abstraction layer for PHP with built-in rate limiting and stale-while-revalidate support. This library is designed to wrap promise-based operations (like ReactPHP Promises) to provide robust caching strategies suitable for high-load or rate-limited API clients.
Features
- Asynchronous Caching: Wraps
PromiseInterfaceor any callable returning a value/promise to handle caching transparently without blocking execution. - Stale-While-Revalidate: Supports background revalidation and stale-on-error patterns.
- X-Fetch (Probabilistic Early Recomputation): Implements the X-Fetch algorithm to prevent cache stampedes (dog-pile effect).
- Atomic Operations: Support for atomic
incrementanddecrementoperations using Symfony Lock. - Logical vs. Physical TTL: Separates the "freshness" of data from its "existence" in the cache, enabling soft expiration patterns.
- Rate Limiting Integration: Supports Symfony Rate Limiter for request throttling.
- PSR-16 & ReactPHP Compatible: Works with any PSR-16 Simple Cache adapter or ReactPHP Cache implementation.
Installation
To install the Async Cache PHP library, run the following command in your terminal:
composer require fyennyi/async-cache-php
Usage
Basic Setup
The easiest way to create a manager is using the fluent configuration API.
use Fyennyi\AsyncCache\AsyncCacheManager; use React\Cache\ArrayCache; // 1. Setup Cache (using ReactPHP ArrayCache as an example) $cache = new ArrayCache(); // 2. Create the Manager using fluent configuration $manager = new AsyncCacheManager( AsyncCacheManager::configure($cache) ->build() );
Wrapping an Async Operation
Use the wrap method to cache a promise-based operation.
use Fyennyi\AsyncCache\CacheOptions; use Fyennyi\AsyncCache\Enum\CacheStrategy; use React\Http\Browser; $browser = new \React\Http\Browser(); $options = new CacheOptions( ttl: 60, // Data is fresh for 60 seconds strategy: CacheStrategy::Strict // Default strategy ); $promise = $manager->wrap( 'cache_key_user_1', fn() => $browser->get('https://api.example.com/users/1')->then( fn($response) => (string)$response->getBody() ), $options ); // Handle the result asynchronously $promise->then(function ($data) { echo "User data: " . $data; });
Advanced Configuration Options
The CacheOptions DTO allows you to configure behavior per request:
use Fyennyi\AsyncCache\Enum\CacheStrategy; new CacheOptions( ttl: 300, // Time in seconds data is considered fresh stale_grace_period: 86400, // Keep stale data physically in cache for 24h strategy: CacheStrategy::Strict, // Strict, Background, or ForceRefresh rate_limit_key: 'nominatim', // Key for rate limiting (if limiter is configured) serve_stale_if_limited: true, // Return stale data if rate limited tags: ['geo', 'kyiv'], // Cache tags (if adapter supports them) compression: false, // Enable data compression compression_threshold: 1024, // Minimum size in bytes to trigger compression fail_safe: true, // Catch cache exceptions and treat as misses x_fetch_beta: 1.0 // Beta coefficient for X-Fetch (0 to disable) );
Atomic Increments
$manager->increment('page_views', 1)->then(function($newValue) { echo "New value: " . $newValue; });
How It Works
- Cache Hit: If data is found in the cache and is fresh (within
ttl), the promise resolves immediately with the cached value. The factory function is not called. - Cache Miss: If data is not found, the factory function is executed, and the result is stored in the cache.
- Stale Data:
- If data is in the cache but expired (older than
ttl), the manager behavior depends on the chosenstrategy. - Strict: Fetches fresh data while the request waits.
- Background: Returns stale data immediately and triggers an asynchronous refresh in the background.
- If data is in the cache but expired (older than
- X-Fetch: Helps avoid simultaneous cache misses for the same key by probabilistic early recomputation.
Contributing
Contributions are welcome! Please follow these steps:
- Fork the project.
- Create your feature branch (
git checkout -b feature/AmazingFeature). - Commit your changes (
git commit -m 'Add some AmazingFeature'). - Push to the branch (
git push origin feature/AmazingFeature). - Open a Pull Request.
License
This library is licensed under the CSSM Unlimited License v2.0 (CSSM-ULv2). See the LICENSE file for details.