diff --git a/README.md b/README.md index 9d37261..13565e8 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,89 @@ # CacheLayer CacheLayer is a standalone cache toolkit for modern PHP applications. +It provides a unified API over PSR-6 and PSR-16 with local, distributed, +and cloud adapters. -It provides: +## Features -- PSR-6 and PSR-16 compatible `Cache` facade -- Adapters for `APCu`, `File`, `Memcached`, `Redis`, `Redis Cluster`, `PDO (default SQLite; also MySQL/MariaDB/PostgreSQL/etc.)` -- In-process adapters: `memory` (array), `weakMap`, `nullStore`, `chain` -- Filesystem/Opcode adapter: `phpFiles` -- Shared-memory adapter: `sharedMemory` (sysvshm) -- Cloud adapters: `mongodb`, `dynamoDb`, `s3` (SDK/client injected or auto-created when SDK is installed) -- Tag-version invalidation (`setTagged()`, `invalidateTag()`, `invalidateTags()`) without full key scans -- Stampede-safe `remember()` with pluggable lock providers (file/redis/memcached) -- Per-adapter metrics counters with export hooks -- Optional payload compression via `configurePayloadCompression()` -- Serializer helpers for closures/resources used by cache payloads -- Memoization primitives: `Memoizer`, `MemoizeTrait`, and helpers `memoize()`, `remember()`, `once()` +- Unified `Cache` facade implementing PSR-6, PSR-16, `ArrayAccess`, and `Countable` +- Adapter support for APCu, File, PHP Files, Memcached, Redis, Redis Cluster, PDO (SQLite default), Shared Memory, MongoDB, DynamoDB, and S3 +- Tagged invalidation with versioned tags: `setTagged()`, `invalidateTag()`, `invalidateTags()` +- Stampede-safe `remember()` with pluggable lock providers +- Per-adapter metrics counters and export hooks +- Payload compression controls +- Value serializer helpers for closures/resources +- Memoization helpers: `memoize()`, `remember()`, `once()` -Quick usage: +## Requirements + +- PHP 8.3+ +- Composer + +Optional extensions/packages depend on adapter choice: + +- `ext-apcu` +- `ext-redis` +- `ext-memcached` +- `ext-pdo` + driver (`pdo_sqlite`, `pdo_pgsql`, `pdo_mysql`, ...) +- `ext-sysvshm` +- `mongodb/mongodb` +- `aws/aws-sdk-php` + +## Installation + +```bash +composer require infocyph/cachelayer +``` + +## Usage ```php use Infocyph\CacheLayer\Cache\Cache; -$cache = Cache::memory('app'); -$cache->setTagged('user:1', ['name' => 'A'], ['users'], 300); +$cache = Cache::pdo('app'); // defaults to sqlite file in sys temp dir + +$cache->setTagged('user:1', ['name' => 'Ada'], ['users'], 300); -$cache->invalidateTag('users'); // all entries tagged with "users" become stale +$user = $cache->remember('user:1', function ($item) { + $item->expiresAfter(300); + return ['name' => 'Ada']; +}, tags: ['users']); + +$cache->invalidateTag('users'); -$value = $cache->remember('expensive', fn () => compute(), 60); $metrics = $cache->exportMetrics(); ``` -Factory overview: +## Documentation -```php -Cache::apcu('ns'); -Cache::file('ns', __DIR__ . '/storage/cache'); -Cache::phpFiles('ns', __DIR__ . '/storage/cache'); -Cache::memcache('ns'); -Cache::redis('ns'); -Cache::redisCluster('ns', ['127.0.0.1:6379']); -Cache::pdo('ns'); // defaults to sqlite file in sys temp dir -Cache::sqlite('ns'); -Cache::pdo('ns', 'mysql:host=127.0.0.1;port=3306;dbname=app', 'user', 'pass'); -Cache::memory('ns'); -Cache::weakMap('ns'); -Cache::sharedMemory('ns'); -Cache::nullStore(); -Cache::chain([ - new Infocyph\CacheLayer\Cache\Adapter\ArrayCacheAdapter('l1'), - new Infocyph\CacheLayer\Cache\Adapter\RedisCacheAdapter('l2'), -]); +- User docs are in `docs/` +- Build docs locally with Sphinx (if installed): + +```bash +make -C docs html +``` + +## Testing + +```bash +composer test:code +``` + +Or run the full test pipeline: + +```bash +composer test:all ``` -Namespace: +## Contributing + +Contributions are welcome. + +- Open an issue for bug reports or feature discussions +- Open a pull request with focused changes and tests +- Keep coding style and static checks passing before submitting + +## License -- `Infocyph\CacheLayer\Cache\...` -- `Infocyph\CacheLayer\Cache\Lock\...` -- `Infocyph\CacheLayer\Cache\Metrics\...` -- `Infocyph\CacheLayer\Serializer\...` -- `Infocyph\CacheLayer\Exceptions\...` +MIT License. See [LICENSE](LICENSE). diff --git a/composer.json b/composer.json index 65db77d..df5b4a8 100644 --- a/composer.json +++ b/composer.json @@ -64,7 +64,7 @@ "require-dev": { "captainhook/captainhook": "^5.29.2", "laravel/pint": "^1.29", - "pestphp/pest": "^4.4.5", + "pestphp/pest": "^4.5", "pestphp/pest-plugin-drift": "^4.1", "phpbench/phpbench": "^1.6", "phpstan/phpstan": "^2.1", diff --git a/docs/_static/theme.css b/docs/_static/theme.css index ae81ea7..c49ed37 100644 --- a/docs/_static/theme.css +++ b/docs/_static/theme.css @@ -1,3 +1,52 @@ -.highlight-php .k { - color: #0077aa; /* Example: make PHP keywords a different color */ +/* Make inline code and fenced blocks visually distinct in both light/dark themes. */ +code.docutils.literal, +code.literal { + border: 1px solid var(--pst-color-border); + border-radius: 0.35rem; + padding: 0.1rem 0.35rem; + background: var(--pst-color-surface); +} + +.highlight pre { + border: 1px solid var(--pst-color-border); + border-radius: 0.6rem; + padding: 0.9rem 1rem; +} + +.highlight .k, +.highlight .kn, +.highlight .kd, +.highlight .kc { + color: #0b7285; + font-weight: 600; +} + +.highlight .s, +.highlight .s1, +.highlight .s2 { + color: #2b8a3e; +} + +.highlight .c, +.highlight .c1 { + color: #7d8597; + font-style: italic; +} + +[data-theme="dark"] .highlight .k, +[data-theme="dark"] .highlight .kn, +[data-theme="dark"] .highlight .kd, +[data-theme="dark"] .highlight .kc { + color: #4dabf7; +} + +[data-theme="dark"] .highlight .s, +[data-theme="dark"] .highlight .s1, +[data-theme="dark"] .highlight .s2 { + color: #8ce99a; +} + +[data-theme="dark"] .highlight .c, +[data-theme="dark"] .highlight .c1 { + color: #9aa6b2; } diff --git a/docs/adapters/apcu.rst b/docs/adapters/apcu.rst index b4d370e..227aa10 100644 --- a/docs/adapters/apcu.rst +++ b/docs/adapters/apcu.rst @@ -1,21 +1,21 @@ .. _adapters.apcu: ======================= -APCu Adapter (`apcu`) +APCu Adapter (``apcu``) ======================= -Factory: `Cache::apcu(string $namespace = 'default')` +Factory: ``Cache::apcu(string $namespace = 'default')`` Requirements: -* `ext-apcu` -* APCu enabled (`apcu_enabled()`) -* for CLI usage/tests: `apcu.enable_cli=1` +* ``ext-apcu`` +* APCu enabled (``apcu_enabled()``) +* for CLI usage/tests: ``apcu.enable_cli=1`` Highlights: * in-memory shared cache in the PHP runtime environment -* namespace-prefixed keys (`:`) +* namespace-prefixed keys (``:``) * efficient bulk fetch through APCu array fetch path -`Cache::local()` will choose APCu automatically when available. +``Cache::local()`` will choose APCu automatically when available. diff --git a/docs/adapters/array-memory.rst b/docs/adapters/array-memory.rst index bc76f00..19d4b2b 100644 --- a/docs/adapters/array-memory.rst +++ b/docs/adapters/array-memory.rst @@ -1,10 +1,10 @@ .. _adapters.array_memory: ============================ -Array Adapter (`memory`) +Array Adapter (``memory``) ============================ -Factory: `Cache::memory(string $namespace = 'default')` +Factory: ``Cache::memory(string $namespace = 'default')`` In-process array-backed adapter for fast ephemeral caching. diff --git a/docs/adapters/chain.rst b/docs/adapters/chain.rst index 2a04ace..e4f8afd 100644 --- a/docs/adapters/chain.rst +++ b/docs/adapters/chain.rst @@ -1,10 +1,10 @@ .. _adapters.chain: ========================= -Chain Adapter (`chain`) +Chain Adapter (``chain``) ========================= -Factory: `Cache::chain(array $pools)` +Factory: ``Cache::chain(array $pools)`` Composes multiple PSR-6 pools into a tiered cache. @@ -16,8 +16,8 @@ Behavior: Typical layout: -* L1: in-memory (`ArrayCacheAdapter`) -* L2: network cache (`RedisCacheAdapter`) +* L1: in-memory (``ArrayCacheAdapter``) +* L2: network cache (``RedisCacheAdapter``) Example: diff --git a/docs/adapters/dynamodb.rst b/docs/adapters/dynamodb.rst index 25a9084..e74ccab 100644 --- a/docs/adapters/dynamodb.rst +++ b/docs/adapters/dynamodb.rst @@ -1,28 +1,28 @@ .. _adapters.dynamodb: ================================ -DynamoDB Adapter (`dynamoDb`) +DynamoDB Adapter (``dynamoDb``) ================================ Factory: -`Cache::dynamoDb(string $namespace = 'default', string $table = 'cachelayer_entries', ?object $client = null, array $config = [])` +``Cache::dynamoDb(string $namespace = 'default', string $table = 'cachelayer_entries', ?object $client = null, array $config = [])`` Requirements: -* `aws/aws-sdk-php` for default client path, or +* ``aws/aws-sdk-php`` for default client path, or * injected client implementing required DynamoDB methods Highlights: * namespace-scoped row storage -* clear via scan + chunked `batchWriteItem` delete requests -* TTL stored as absolute timestamp in `expires` +* clear via scan + chunked ``batchWriteItem`` delete requests +* TTL stored as absolute timestamp in ``expires`` Supported injected client methods: -* `getItem` -* `putItem` -* `deleteItem` -* `scan` -* `batchWriteItem` +* ``getItem`` +* ``putItem`` +* ``deleteItem`` +* ``scan`` +* ``batchWriteItem`` diff --git a/docs/adapters/file.rst b/docs/adapters/file.rst index 59b4ec3..8b5ef85 100644 --- a/docs/adapters/file.rst +++ b/docs/adapters/file.rst @@ -1,24 +1,24 @@ .. _adapters.file: ======================= -File Adapter (`file`) +File Adapter (``file``) ======================= -Factory: `Cache::file(string $namespace = 'default', ?string $dir = null)` +Factory: ``Cache::file(string $namespace = 'default', ?string $dir = null)`` Stores one cache payload per file under a namespace directory. Path layout: -* base dir: provided `$dir` or `sys_get_temp_dir()` -* namespace dir: `cache_` -* file name: `hash('xxh128', $key) . '.cache'` +* base dir: provided ``$dir`` or ``sys_get_temp_dir()`` +* namespace dir: ``cache_`` +* file name: ``hash('xxh128', $key) . '.cache'`` Highlights: * zero service dependencies * persists across process restarts -* atomic write flow (`tempnam` + `rename`) -* `setNamespaceAndDirectory()` supported +* atomic write flow (``tempnam`` + ``rename``) +* ``setNamespaceAndDirectory()`` supported Best for local/single-host environments. diff --git a/docs/adapters/memcached.rst b/docs/adapters/memcached.rst index 277517d..11bdc63 100644 --- a/docs/adapters/memcached.rst +++ b/docs/adapters/memcached.rst @@ -1,22 +1,22 @@ .. _adapters.memcached: ================================= -Memcached Adapter (`memcache`) +Memcached Adapter (``memcache``) ================================= Factory: -`Cache::memcache(string $namespace = 'default', array $servers = [['127.0.0.1', 11211, 0]], ?Memcached $client = null)` +``Cache::memcache(string $namespace = 'default', array $servers = [['127.0.0.1', 11211, 0]], ?Memcached $client = null)`` Requirements: -* `ext-memcached` +* ``ext-memcached`` * reachable Memcached server(s) Highlights: * distributed in-memory cache -* `getMulti` based batch reads -* factory auto-configures `MemcachedLockProvider` for `remember()` when using this adapter +* ``getMulti`` based batch reads +* factory auto-configures ``MemcachedLockProvider`` for ``remember()`` when using this adapter -You may pass your own preconfigured `Memcached` client. +You may pass your own preconfigured ``Memcached`` client. diff --git a/docs/adapters/mongodb.rst b/docs/adapters/mongodb.rst index 6807a2b..229d8f7 100644 --- a/docs/adapters/mongodb.rst +++ b/docs/adapters/mongodb.rst @@ -1,17 +1,17 @@ .. _adapters.mongodb: ============================= -MongoDB Adapter (`mongodb`) +MongoDB Adapter (``mongodb``) ============================= Factories: -* `Cache::mongodb(...)` -* `MongoDbCacheAdapter::fromClient(...)` (adapter-level) +* ``Cache::mongodb(...)`` +* ``MongoDbCacheAdapter::fromClient(...)`` (adapter-level) Requirements: -* `mongodb/mongodb` package for default client path, or +* ``mongodb/mongodb`` package for default client path, or * injected collection/client compatible with expected methods Highlights: @@ -22,8 +22,8 @@ Highlights: Supported injected collection methods: -* `findOne` -* `updateOne` -* `deleteOne` -* `deleteMany` -* `countDocuments` +* ``findOne`` +* ``updateOne`` +* ``deleteOne`` +* ``deleteMany`` +* ``countDocuments`` diff --git a/docs/adapters/null-store.rst b/docs/adapters/null-store.rst index ba55de8..5384849 100644 --- a/docs/adapters/null-store.rst +++ b/docs/adapters/null-store.rst @@ -1,17 +1,17 @@ .. _adapters.null_store: ============================ -Null Adapter (`nullStore`) +Null Adapter (``nullStore``) ============================ -Factory: `Cache::nullStore()` +Factory: ``Cache::nullStore()`` No-op adapter that never persists values. Behavior: -* `set()` returns true -* `get()` always misses unless default/callable path is used -* `remember()` recomputes every call +* ``set()`` returns true +* ``get()`` always misses unless default/callable path is used +* ``remember()`` recomputes every call Useful for disabling caching without changing calling code. diff --git a/docs/adapters/pdo.rst b/docs/adapters/pdo.rst index 757ddc5..eaffe07 100644 --- a/docs/adapters/pdo.rst +++ b/docs/adapters/pdo.rst @@ -1,29 +1,29 @@ .. _adapters.pdo: ========================= -PDO Adapter (`pdo`) +PDO Adapter (``pdo``) ========================= Factory: -`Cache::pdo(string $namespace = 'default', ?string $dsn = null, ?string $username = null, ?string $password = null, ?PDO $pdo = null, string $table = 'cachelayer_entries')` +``Cache::pdo(string $namespace = 'default', ?string $dsn = null, ?string $username = null, ?string $password = null, ?PDO $pdo = null, string $table = 'cachelayer_entries')`` Requirements: -* `ext-pdo` -* the target PDO driver for your DSN (`pdo_mysql`, `pdo_pgsql`, etc.) +* ``ext-pdo`` +* the target PDO driver for your DSN (``pdo_mysql``, ``pdo_pgsql``, etc.) Highlights: * unified SQL adapter for MySQL, MariaDB, PostgreSQL, and other PDO drivers * defaults to SQLite when no DSN/PDO is provided -* namespace-prefixed row keys (`:`) +* namespace-prefixed row keys (``:``) * automatic table/index initialization * driver-aware upsert strategy: - - PostgreSQL/SQLite: native `ON CONFLICT` - - MySQL/MariaDB: native `ON DUPLICATE KEY UPDATE` + - PostgreSQL/SQLite: native ``ON CONFLICT`` + - MySQL/MariaDB: native ``ON DUPLICATE KEY UPDATE`` - fallback path for other PDO drivers -* batched `multiFetch()` via single `IN (...)` query +* batched ``multiFetch()`` via single ``IN (...)`` query Examples: diff --git a/docs/adapters/php-files.rst b/docs/adapters/php-files.rst index adb9eff..d0239f2 100644 --- a/docs/adapters/php-files.rst +++ b/docs/adapters/php-files.rst @@ -1,23 +1,23 @@ .. _adapters.php_files: ============================== -PHP Files Adapter (`phpFiles`) +PHP Files Adapter (``phpFiles``) ============================== -Factory: `Cache::phpFiles(string $namespace = 'default', ?string $dir = null)` +Factory: ``Cache::phpFiles(string $namespace = 'default', ?string $dir = null)`` Persists cache records as PHP files that return payload arrays. Path layout: -* base dir: provided `$dir` or `sys_get_temp_dir()` -* namespace dir: `phpcache_` -* file name: `hash('xxh128', $key) . '.php'` +* base dir: provided ``$dir`` or ``sys_get_temp_dir()`` +* namespace dir: ``phpcache_`` +* file name: ``hash('xxh128', $key) . '.php'`` Highlights: * persistent local cache -* opcode-cache aware (`opcache_invalidate` on writes/deletes when available) -* `setNamespaceAndDirectory()` supported +* opcode-cache aware (``opcache_invalidate`` on writes/deletes when available) +* ``setNamespaceAndDirectory()`` supported Good for environments where opcode cache integration is desired. diff --git a/docs/adapters/redis-cluster.rst b/docs/adapters/redis-cluster.rst index 17297dd..cfe4711 100644 --- a/docs/adapters/redis-cluster.rst +++ b/docs/adapters/redis-cluster.rst @@ -1,21 +1,21 @@ .. _adapters.redis_cluster: ======================================== -Redis Cluster Adapter (`redisCluster`) +Redis Cluster Adapter (``redisCluster``) ======================================== Factory: -`Cache::redisCluster(string $namespace = 'default', array $seeds = ['127.0.0.1:6379'], float $timeout = 1.0, float $readTimeout = 1.0, bool $persistent = false, ?object $client = null)` +``Cache::redisCluster(string $namespace = 'default', array $seeds = ['127.0.0.1:6379'], float $timeout = 1.0, float $readTimeout = 1.0, bool $persistent = false, ?object $client = null)`` Requirements: -* RedisCluster support via `ext-redis`, or -* injected client exposing expected methods (`get`, `set`, `setex`, `del`, `exists`, `sAdd`, `sRem`, `sCard`, `sMembers`) +* RedisCluster support via ``ext-redis``, or +* injected client exposing expected methods (``get``, ``set``, ``setex``, ``del``, ``exists``, ``sAdd``, ``sRem``, ``sCard``, ``sMembers``) Highlights: * cluster-aware storage -* tracks namespace key membership through an index set (`:__keys`) for clear/count operations +* tracks namespace key membership through an index set (``:__keys``) for clear/count operations Useful when using Redis Cluster topology. diff --git a/docs/adapters/redis.rst b/docs/adapters/redis.rst index 4808fce..eb2b51a 100644 --- a/docs/adapters/redis.rst +++ b/docs/adapters/redis.rst @@ -1,26 +1,26 @@ .. _adapters.redis: ========================= -Redis Adapter (`redis`) +Redis Adapter (``redis``) ========================= Factory: -`Cache::redis(string $namespace = 'default', string $dsn = 'redis://127.0.0.1:6379', ?Redis $client = null)` +``Cache::redis(string $namespace = 'default', string $dsn = 'redis://127.0.0.1:6379', ?Redis $client = null)`` Requirements: -* `ext-redis` (phpredis) +* ``ext-redis`` (phpredis) * reachable Redis server Highlights: * distributed cache with namespace key prefixing -* `MGET` batch retrieval -* TTL via `SETEX` when expiration is set -* factory auto-configures `RedisLockProvider` for `remember()` when using this adapter +* ``MGET`` batch retrieval +* TTL via ``SETEX`` when expiration is set +* factory auto-configures ``RedisLockProvider`` for ``remember()`` when using this adapter DSN notes: * host/port parsed from DSN -* optional password and DB selection (`/db-index`) are supported +* optional password and DB selection (``/db-index``) are supported diff --git a/docs/adapters/s3.rst b/docs/adapters/s3.rst index 40f1038..8cbb0f0 100644 --- a/docs/adapters/s3.rst +++ b/docs/adapters/s3.rst @@ -1,16 +1,16 @@ .. _adapters.s3: ===================== -S3 Adapter (`s3`) +S3 Adapter (``s3``) ===================== Factory: -`Cache::s3(string $namespace = 'default', string $bucket = 'cachelayer', ?object $client = null, array $config = [], string $prefix = 'cachelayer')` +``Cache::s3(string $namespace = 'default', string $bucket = 'cachelayer', ?object $client = null, array $config = [], string $prefix = 'cachelayer')`` Requirements: -* `aws/aws-sdk-php` for default client path, or +* ``aws/aws-sdk-php`` for default client path, or * injected S3-compatible client implementing required methods Highlights: @@ -21,8 +21,8 @@ Highlights: Supported injected client methods: -* `putObject` -* `getObject` -* `deleteObject` -* `listObjectsV2` -* `deleteObjects` +* ``putObject`` +* ``getObject`` +* ``deleteObject`` +* ``listObjectsV2`` +* ``deleteObjects`` diff --git a/docs/adapters/serialization.rst b/docs/adapters/serialization.rst index 2bf723a..f7efbe2 100644 --- a/docs/adapters/serialization.rst +++ b/docs/adapters/serialization.rst @@ -4,7 +4,7 @@ Serialization in Adapters =================================== -All adapters rely on `CachePayloadCodec` and `ValueSerializer` to persist +All adapters rely on ``CachePayloadCodec`` and ``ValueSerializer`` to persist arbitrary values consistently. The payload format stores: diff --git a/docs/adapters/shared-memory.rst b/docs/adapters/shared-memory.rst index 7b11134..9769dd6 100644 --- a/docs/adapters/shared-memory.rst +++ b/docs/adapters/shared-memory.rst @@ -1,14 +1,14 @@ .. _adapters.shared_memory: ======================================== -Shared Memory Adapter (`sharedMemory`) +Shared Memory Adapter (``sharedMemory``) ======================================== -Factory: `Cache::sharedMemory(string $namespace = 'default', int $segmentSize = 16777216)` +Factory: ``Cache::sharedMemory(string $namespace = 'default', int $segmentSize = 16777216)`` Requirements: -* `ext-sysvshm` +* ``ext-sysvshm`` Highlights: diff --git a/docs/adapters/sqlite.rst b/docs/adapters/sqlite.rst index eb0a95b..fe20671 100644 --- a/docs/adapters/sqlite.rst +++ b/docs/adapters/sqlite.rst @@ -1,17 +1,17 @@ .. _adapters.sqlite: =========================== -SQLite Adapter (`sqlite`) +SQLite Adapter (``sqlite``) =========================== -Factory: `Cache::sqlite(string $namespace = 'default', ?string $file = null)` +Factory: ``Cache::sqlite(string $namespace = 'default', ?string $file = null)`` -`sqlite` is a convenience wrapper over the PDO adapter. +``sqlite`` is a convenience wrapper over the PDO adapter. Equivalent behavior: -* `Cache::sqlite($namespace, $file)` forwards to `Cache::pdo($namespace, 'sqlite:' . $file)` -* when `$file` is `null`, default path is `sys_get_temp_dir() . "/cache_.sqlite"` +* ``Cache::sqlite($namespace, $file)`` forwards to ``Cache::pdo($namespace, 'sqlite:' . $file)`` +* when ``$file`` is ``null``, default path is ``sys_get_temp_dir() . "/cache_.sqlite"`` -Use `Cache::pdo(...)` directly if you want to switch to MySQL/MariaDB/PostgreSQL +Use ``Cache::pdo(...)`` directly if you want to switch to MySQL/MariaDB/PostgreSQL without changing the rest of your cache usage pattern. diff --git a/docs/adapters/weak-map.rst b/docs/adapters/weak-map.rst index 2d243a9..2ff3c62 100644 --- a/docs/adapters/weak-map.rst +++ b/docs/adapters/weak-map.rst @@ -1,15 +1,15 @@ .. _adapters.weak_map: =============================== -WeakMap Adapter (`weakMap`) +WeakMap Adapter (``weakMap``) =============================== -Factory: `Cache::weakMap(string $namespace = 'default')` +Factory: ``Cache::weakMap(string $namespace = 'default')`` Hybrid in-process adapter: * scalar/array values stored as encoded blobs -* object values stored via `WeakReference`/`WeakMap` +* object values stored via ``WeakReference``/``WeakMap`` Object entries remain available while strongly referenced elsewhere. When an object is collected, its cache entry can disappear naturally. diff --git a/docs/cache.rst b/docs/cache.rst index 264019e..aa63338 100644 --- a/docs/cache.rst +++ b/docs/cache.rst @@ -1,18 +1,18 @@ .. _cache: ============================ -Cache Facade (`Cache`) +Cache Facade (``Cache``) ============================ -`Infocyph\CacheLayer\Cache\Cache` is the unified facade for CacheLayer. +``Infocyph\CacheLayer\Cache\Cache`` is the unified facade for CacheLayer. It implements: -* PSR-6 (`CacheItemPoolInterface`) -* PSR-16 (`Psr\SimpleCache\CacheInterface`) -* `ArrayAccess` -* `Countable` +* PSR-6 (``CacheItemPoolInterface``) +* PSR-16 (``Psr\SimpleCache\CacheInterface``) +* ``ArrayAccess`` +* ``Countable`` -It also adds tagged invalidation, stampede-safe `remember()`, lock provider +It also adds tagged invalidation, stampede-safe ``remember()``, lock provider selection, metrics hooks, and payload compression controls. Installation @@ -43,62 +43,62 @@ Factory Methods The facade exposes factory methods for all bundled adapters: -* `Cache::local(string $namespace = 'default', ?string $dir = null)` -* `Cache::file(string $namespace = 'default', ?string $dir = null)` -* `Cache::phpFiles(string $namespace = 'default', ?string $dir = null)` -* `Cache::apcu(string $namespace = 'default')` -* `Cache::memcache(string $namespace = 'default', array $servers = [['127.0.0.1', 11211, 0]], ?Memcached $client = null)` -* `Cache::redis(string $namespace = 'default', string $dsn = 'redis://127.0.0.1:6379', ?Redis $client = null)` -* `Cache::redisCluster(string $namespace = 'default', array $seeds = ['127.0.0.1:6379'], float $timeout = 1.0, float $readTimeout = 1.0, bool $persistent = false, ?object $client = null)` -* `Cache::sqlite(string $namespace = 'default', ?string $file = null)` -* `Cache::pdo(string $namespace = 'default', ?string $dsn = null, ?string $username = null, ?string $password = null, ?PDO $pdo = null, string $table = 'cachelayer_entries')` -* `Cache::memory(string $namespace = 'default')` -* `Cache::weakMap(string $namespace = 'default')` -* `Cache::sharedMemory(string $namespace = 'default', int $segmentSize = 16777216)` -* `Cache::nullStore()` -* `Cache::chain(array $pools)` -* `Cache::mongodb(string $namespace = 'default', ?object $collection = null, ?object $client = null, string $database = 'cachelayer', string $collectionName = 'entries', string $uri = 'mongodb://127.0.0.1:27017')` -* `Cache::dynamoDb(string $namespace = 'default', string $table = 'cachelayer_entries', ?object $client = null, array $config = [])` -* `Cache::s3(string $namespace = 'default', string $bucket = 'cachelayer', ?object $client = null, array $config = [], string $prefix = 'cachelayer')` - -`local()` chooses APCu when available (`extension_loaded('apcu')` and `apcu_enabled()`), otherwise File cache. - -`pdo()` defaults to SQLite (temp-file database per namespace) when DSN/PDO is not provided. -`sqlite()` is a convenience wrapper over `pdo()` for explicit SQLite file selection. +* ``Cache::local(string $namespace = 'default', ?string $dir = null)`` +* ``Cache::file(string $namespace = 'default', ?string $dir = null)`` +* ``Cache::phpFiles(string $namespace = 'default', ?string $dir = null)`` +* ``Cache::apcu(string $namespace = 'default')`` +* ``Cache::memcache(string $namespace = 'default', array $servers = [['127.0.0.1', 11211, 0]], ?Memcached $client = null)`` +* ``Cache::redis(string $namespace = 'default', string $dsn = 'redis://127.0.0.1:6379', ?Redis $client = null)`` +* ``Cache::redisCluster(string $namespace = 'default', array $seeds = ['127.0.0.1:6379'], float $timeout = 1.0, float $readTimeout = 1.0, bool $persistent = false, ?object $client = null)`` +* ``Cache::sqlite(string $namespace = 'default', ?string $file = null)`` +* ``Cache::pdo(string $namespace = 'default', ?string $dsn = null, ?string $username = null, ?string $password = null, ?PDO $pdo = null, string $table = 'cachelayer_entries')`` +* ``Cache::memory(string $namespace = 'default')`` +* ``Cache::weakMap(string $namespace = 'default')`` +* ``Cache::sharedMemory(string $namespace = 'default', int $segmentSize = 16777216)`` +* ``Cache::nullStore()`` +* ``Cache::chain(array $pools)`` +* ``Cache::mongodb(string $namespace = 'default', ?object $collection = null, ?object $client = null, string $database = 'cachelayer', string $collectionName = 'entries', string $uri = 'mongodb://127.0.0.1:27017')`` +* ``Cache::dynamoDb(string $namespace = 'default', string $table = 'cachelayer_entries', ?object $client = null, array $config = [])`` +* ``Cache::s3(string $namespace = 'default', string $bucket = 'cachelayer', ?object $client = null, array $config = [], string $prefix = 'cachelayer')`` + +``local()`` chooses APCu when available (``extension_loaded('apcu')`` and ``apcu_enabled()``), otherwise File cache. + +``pdo()`` defaults to SQLite (temp-file database per namespace) when DSN/PDO is not provided. +``sqlite()`` is a convenience wrapper over ``pdo()`` for explicit SQLite file selection. Key and TTL Rules ----------------- Key validation is strict and shared across PSR-6/PSR-16 calls: -* Allowed characters: `A-Z`, `a-z`, `0-9`, `_`, `.`, `-` +* Allowed characters: ``A-Z``, ``a-z``, ``0-9``, ``_``, ``.``, ``-`` * Empty keys or keys with spaces are rejected -* Invalid keys throw `Infocyph\CacheLayer\Exceptions\CacheInvalidArgumentException` +* Invalid keys throw ``Infocyph\CacheLayer\Exceptions\CacheInvalidArgumentException`` TTL handling: -* Supported types: `null`, `int`, `DateInterval` +* Supported types: ``null``, ``int``, ``DateInterval`` * Negative TTL is rejected -* TTL `0` behaves as immediate expiry (adapters treat it as delete/expired) +* TTL ``0`` behaves as immediate expiry (adapters treat it as delete/expired) PSR-16 Methods -------------- Common helpers: -* `get(string $key, mixed $default = null): mixed` -* `set(string $key, mixed $value, int|DateInterval|null $ttl = null): bool` -* `delete(string $key): bool` -* `clear(): bool` -* `getMultiple(iterable $keys, mixed $default = null): iterable` -* `setMultiple(iterable $values, int|DateInterval|null $ttl = null): bool` -* `deleteMultiple(iterable $keys): bool` -* `has(string $key): bool` +* ``get(string $key, mixed $default = null): mixed`` +* ``set(string $key, mixed $value, int|DateInterval|null $ttl = null): bool`` +* ``delete(string $key): bool`` +* ``clear(): bool`` +* ``getMultiple(iterable $keys, mixed $default = null): iterable`` +* ``setMultiple(iterable $values, int|DateInterval|null $ttl = null): bool`` +* ``deleteMultiple(iterable $keys): bool`` +* ``has(string $key): bool`` -`get()` callable default +``get()`` callable default ~~~~~~~~~~~~~~~~~~~~~~~~ -If `$default` is callable, `get()` internally uses `remember()` semantics. +If ``$default`` is callable, ``get()`` internally uses ``remember()`` semantics. On miss, the callable is executed and the result is persisted. .. code-block:: php @@ -113,17 +113,17 @@ PSR-6 Methods Standard pool methods are available and delegated to the underlying adapter: -* `getItem()` -* `getItems()` -* `hasItem()` -* `save()` -* `saveDeferred()` -* `commit()` -* `deleteItem()` -* `deleteItems()` -* `clear()` - -For adapters that implement `multiFetch(array $keys)`, `getItems()` uses it +* ``getItem()`` +* ``getItems()`` +* ``hasItem()`` +* ``save()`` +* ``saveDeferred()`` +* ``commit()`` +* ``deleteItem()`` +* ``deleteItems()`` +* ``clear()`` + +For adapters that implement ``multiFetch(array $keys)``, ``getItems()`` uses it for efficient batch retrieval. Tagged Caching @@ -131,9 +131,9 @@ Tagged Caching CacheLayer uses tag-version invalidation (no full key scans required): -* `setTagged(string $key, mixed $value, array $tags, mixed $ttl = null): bool` -* `invalidateTag(string $tag): bool` -* `invalidateTags(array $tags): bool` +* ``setTagged(string $key, mixed $value, array $tags, mixed $ttl = null): bool`` +* ``invalidateTag(string $tag): bool`` +* ``invalidateTags(array $tags): bool`` When a tag is invalidated, its internal version is incremented. Entries tagged with older versions become stale and are treated as misses on read. @@ -145,10 +145,10 @@ with older versions become stale and are treated as misses on read. $cache->invalidateTag('feed'); $cache->get('home:feed'); // null (stale) -Stampede-Safe `remember()` +Stampede-Safe ``remember()`` -------------------------- -`remember()` protects expensive recomputation with a lock provider: +``remember()`` protects expensive recomputation with a lock provider: .. code-block:: php @@ -160,7 +160,7 @@ Stampede-Safe `remember()` Behavior: 1. Read existing value. -2. On miss, acquire lock (`FileLockProvider` by default). +2. On miss, acquire lock (``FileLockProvider`` by default). 3. Re-check value under lock. 4. Compute and save value. 5. Apply jitter to TTL to reduce herd effects. @@ -168,27 +168,27 @@ Behavior: Lock provider selection: -* `setLockProvider(LockProviderInterface $provider): self` -* `useRedisLock(?Redis $client = null, string $prefix = 'cachelayer:lock:'): self` -* `useMemcachedLock(?Memcached $client = null, string $prefix = 'cachelayer:lock:'): self` +* ``setLockProvider(LockProviderInterface $provider): self`` +* ``useRedisLock(?Redis $client = null, string $prefix = 'cachelayer:lock:'): self`` +* ``useMemcachedLock(?Memcached $client = null, string $prefix = 'cachelayer:lock:'): self`` Factory defaults: -* `Cache::redis(...)` auto-configures `RedisLockProvider` -* `Cache::memcache(...)` auto-configures `MemcachedLockProvider` -* `Cache::pdo(...)` / `Cache::sqlite(...)` auto-configure `PdoLockProvider` -* other adapters default to `FileLockProvider` +* ``Cache::redis(...)`` auto-configures ``RedisLockProvider`` +* ``Cache::memcache(...)`` auto-configures ``MemcachedLockProvider`` +* ``Cache::pdo(...)`` / ``Cache::sqlite(...)`` auto-configure ``PdoLockProvider`` +* other adapters default to ``FileLockProvider`` Metrics and Export Hooks ------------------------ Methods: -* `setMetricsCollector(CacheMetricsCollectorInterface $metrics): self` -* `exportMetrics(): array` -* `setMetricsExportHook(?callable $hook): self` +* ``setMetricsCollector(CacheMetricsCollectorInterface $metrics): self`` +* ``exportMetrics(): array`` +* ``setMetricsExportHook(?callable $hook): self`` -Default collector is `InMemoryCacheMetricsCollector`. +Default collector is ``InMemoryCacheMetricsCollector``. Metrics are grouped by adapter class and metric name, for example: @@ -205,33 +205,33 @@ Metrics are grouped by adapter class and metric name, for example: Payload Compression ------------------- -Use `configurePayloadCompression(?int $thresholdBytes = null, int $level = 6)` +Use ``configurePayloadCompression(?int $thresholdBytes = null, int $level = 6)`` to enable compression for encoded payloads. Notes: * Compression is applied when payload size meets/exceeds threshold. -* Requires `gzencode`/`gzdecode` functions. -* Compression configuration is global (`CachePayloadCodec` static state). +* Requires ``gzencode``/``gzdecode`` functions. +* Compression configuration is global (``CachePayloadCodec`` static state). Convenience Features -------------------- Array and magic access: -* `$cache['key'] = 'value';` -* `$cache['key'];` -* `$cache->key = 'value';` -* `$cache->key;` +* ``$cache['key'] = 'value';`` +* ``$cache['key'];`` +* ``$cache->key = 'value';`` +* ``$cache->key;`` Counting: -* `count($cache)` delegates to adapter `Countable` support when available. +* ``count($cache)`` delegates to adapter ``Countable`` support when available. Namespace/Directory Mutation ---------------------------- -`setNamespaceAndDirectory(string $namespace, ?string $dir = null): void` +``setNamespaceAndDirectory(string $namespace, ?string $dir = null): void`` forwards to adapters that support runtime namespace/directory changes. Supported by: @@ -239,4 +239,4 @@ Supported by: * File cache adapter * PHP files cache adapter -Unsupported adapters throw `BadMethodCallException`. +Unsupported adapters throw ``BadMethodCallException``. diff --git a/docs/conf.py b/docs/conf.py index 9c84786..baa1320 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -43,6 +43,8 @@ def get_version() -> str: ] source_suffix = ".rst" +pygments_style = "sphinx" +pygments_dark_style = "native" autodoc_default_options = { "members": True, diff --git a/docs/functions.rst b/docs/functions.rst index 82cc87c..537af0d 100644 --- a/docs/functions.rst +++ b/docs/functions.rst @@ -4,7 +4,7 @@ Global Helper Functions ========================== -CacheLayer autoloads helper functions from `src/functions.php`. +CacheLayer autoloads helper functions from ``src/functions.php``. sanitize_cache_ns() ------------------- @@ -15,7 +15,7 @@ Normalizes namespaces into safe key prefixes. Behavior: -* Replaces any character outside `[A-Za-z0-9_-]` with `_` +* Replaces any character outside ``[A-Za-z0-9_-]`` with ``_`` * Uses an internal static memoization map for repeated inputs Example: @@ -32,8 +32,8 @@ memoize() Two modes: -* `memoize()` returns the singleton `Infocyph\CacheLayer\Memoize\Memoizer` -* `memoize($callable, $params)` executes memoized call lookup for global/static scope +* ``memoize()`` returns the singleton ``Infocyph\CacheLayer\Memoize\Memoizer`` +* ``memoize($callable, $params)`` executes memoized call lookup for global/static scope Example: @@ -55,10 +55,10 @@ Object-scoped memoization helper. Two modes: -* `remember()` returns the singleton `Memoizer` -* `remember($object, $callable, $params)` caches value per object instance +* ``remember()`` returns the singleton ``Memoizer`` +* ``remember($object, $callable, $params)`` caches value per object instance -If object is provided but callable is missing, it throws `InvalidArgumentException`. +If object is provided but callable is missing, it throws ``InvalidArgumentException``. once() ------ @@ -66,7 +66,7 @@ once() .. php:function:: once(callable $callback): mixed Executes callback once per call site context using -`Infocyph\CacheLayer\Memoize\OnceMemoizer`. +``Infocyph\CacheLayer\Memoize\OnceMemoizer``. Useful for one-time initialization inside request/process scope. diff --git a/docs/index.rst b/docs/index.rst index 9773e8d..48f9abb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,14 +4,14 @@ CacheLayer Manual CacheLayer is a standalone caching toolkit for PHP 8.3+ with: -* PSR-6 and PSR-16 support behind one facade (`Cache`) +* PSR-6 and PSR-16 support behind one facade (``Cache``) * local, distributed, and cloud cache adapters -* tag-version invalidation (`setTagged`, `invalidateTag`, `invalidateTags`) -* stampede-safe `remember()` with pluggable lock providers +* tag-version invalidation (``setTagged``, ``invalidateTag``, ``invalidateTags``) +* stampede-safe ``remember()`` with pluggable lock providers * adapter-level metrics export hooks * payload compression controls * value serialization for closures and resources -* process-local memoization helpers (`memoize`, `remember`, `once`) +* process-local memoization helpers (``memoize``, ``remember``, ``once``) Quick Start ----------- diff --git a/docs/memoize.rst b/docs/memoize.rst index d9fc6a3..3c83895 100644 --- a/docs/memoize.rst +++ b/docs/memoize.rst @@ -9,10 +9,10 @@ in-process calls. Available components: -* `Infocyph\CacheLayer\Memoize\Memoizer` -* `Infocyph\CacheLayer\Memoize\OnceMemoizer` -* `Infocyph\CacheLayer\Memoize\MemoizeTrait` -* global helpers `memoize()`, `remember()`, and `once()` +* ``Infocyph\CacheLayer\Memoize\Memoizer`` +* ``Infocyph\CacheLayer\Memoize\OnceMemoizer`` +* ``Infocyph\CacheLayer\Memoize\MemoizeTrait`` +* global helpers ``memoize()``, ``remember()``, and ``once()`` .. toctree:: :maxdepth: 1 diff --git a/docs/memoize/functions.rst b/docs/memoize/functions.rst index 41da329..b7feb28 100644 --- a/docs/memoize/functions.rst +++ b/docs/memoize/functions.rst @@ -7,12 +7,12 @@ Memoize Function Helpers memoize(callable, params) ------------------------- -`memoize($callable, $params)` caches return values by: +``memoize($callable, $params)`` caches return values by: * callable signature * normalized parameters hash -Internally this uses `Memoizer::get()`. +Internally this uses ``Memoizer::get()``. .. code-block:: php @@ -21,8 +21,8 @@ Internally this uses `Memoizer::get()`. remember(object, callable, params) ---------------------------------- -`remember($object, $callable, $params)` caches values per object instance -(using `WeakMap` inside `Memoizer`). +``remember($object, $callable, $params)`` caches values per object instance +(using ``WeakMap`` inside ``Memoizer``). When the object is garbage-collected, its memoized bucket is removable. @@ -35,7 +35,7 @@ When the object is garbage-collected, its memoized bucket is removable. once(callback) -------------- -`once($callback)` is call-site based memoization via `OnceMemoizer`. +``once($callback)`` is call-site based memoization via ``OnceMemoizer``. Key details: diff --git a/docs/memoize/trait.rst b/docs/memoize/trait.rst index 902d99c..c822d01 100644 --- a/docs/memoize/trait.rst +++ b/docs/memoize/trait.rst @@ -1,17 +1,17 @@ .. _memoize.trait: ===================== -`MemoizeTrait` +``MemoizeTrait`` ===================== -`Infocyph\CacheLayer\Memoize\MemoizeTrait` provides lightweight per-object +``Infocyph\CacheLayer\Memoize\MemoizeTrait`` provides lightweight per-object memoization for class internals. API --- -* `memoize(string $key, callable $producer): mixed` -* `memoizeClear(?string $key = null): void` +* ``memoize(string $key, callable $producer): mixed`` +* ``memoizeClear(?string $key = null): void`` Example ------- diff --git a/docs/metrics-and-locking.rst b/docs/metrics-and-locking.rst index 58836c6..eb82ef4 100644 --- a/docs/metrics-and-locking.rst +++ b/docs/metrics-and-locking.rst @@ -9,21 +9,21 @@ Metrics Cache facade metrics API: -* `setMetricsCollector(CacheMetricsCollectorInterface $metrics): self` -* `exportMetrics(): array` -* `setMetricsExportHook(?callable $hook): self` +* ``setMetricsCollector(CacheMetricsCollectorInterface $metrics): self`` +* ``exportMetrics(): array`` +* ``setMetricsExportHook(?callable $hook): self`` -Default collector is `InMemoryCacheMetricsCollector`. +Default collector is ``InMemoryCacheMetricsCollector``. Metric counters are tracked per adapter class, for example: -* `hit` -* `miss` -* `set` -* `delete` -* `delete_batch` -* `remember_hit` -* `remember_miss` +* ``hit`` +* ``miss`` +* ``set`` +* ``delete`` +* ``delete_batch`` +* ``remember_hit`` +* ``remember_miss`` .. code-block:: php @@ -39,25 +39,25 @@ Metric counters are tracked per adapter class, for example: Locking and Stampede Protection ------------------------------- -`Cache::remember()` acquires a lock to prevent duplicate recomputation. +``Cache::remember()`` acquires a lock to prevent duplicate recomputation. Default: -* `FileLockProvider` +* ``FileLockProvider`` Optional providers: -* `RedisLockProvider` -* `MemcachedLockProvider` -* `PdoLockProvider` +* ``RedisLockProvider`` +* ``MemcachedLockProvider`` +* ``PdoLockProvider`` Facade helpers: -* `setLockProvider(LockProviderInterface $provider): self` -* `useRedisLock(?Redis $client = null, string $prefix = 'cachelayer:lock:'): self` -* `useMemcachedLock(?Memcached $client = null, string $prefix = 'cachelayer:lock:'): self` +* ``setLockProvider(LockProviderInterface $provider): self`` +* ``useRedisLock(?Redis $client = null, string $prefix = 'cachelayer:lock:'): self`` +* ``useMemcachedLock(?Memcached $client = null, string $prefix = 'cachelayer:lock:'): self`` -Custom lock providers can implement `LockProviderInterface`: +Custom lock providers can implement ``LockProviderInterface``: .. code-block:: php @@ -67,11 +67,11 @@ Custom lock providers can implement `LockProviderInterface`: public function release(?LockHandle $handle): void; } -`LockHandle` carries key/token/resource metadata used by providers to release locks safely. +``LockHandle`` carries key/token/resource metadata used by providers to release locks safely. Adapter defaults: -* Redis adapter factory sets `RedisLockProvider` -* Memcached adapter factory sets `MemcachedLockProvider` -* PDO/SQLite adapter factories set `PdoLockProvider` -* all other adapters use `FileLockProvider` by default +* Redis adapter factory sets ``RedisLockProvider`` +* Memcached adapter factory sets ``MemcachedLockProvider`` +* PDO/SQLite adapter factories set ``PdoLockProvider`` +* all other adapters use ``FileLockProvider`` by default diff --git a/docs/serializer.rst b/docs/serializer.rst index 6eca444..1f012c5 100644 --- a/docs/serializer.rst +++ b/docs/serializer.rst @@ -4,27 +4,27 @@ Value Serialization ===================== -`Infocyph\CacheLayer\Serializer\ValueSerializer` is used by adapters to encode +``Infocyph\CacheLayer\Serializer\ValueSerializer`` is used by adapters to encode and decode cached payloads. What it handles --------------- * scalar values and arrays -* closures (via `opis/closure`) +* closures (via ``opis/closure``) * registered resource types Core Methods ------------ -* `serialize(mixed $value): string` -* `unserialize(string $blob): mixed` -* `encode(mixed $value, bool $base64 = true): string` -* `decode(string $payload, bool $base64 = true): mixed` -* `wrap(mixed $value): mixed` -* `unwrap(mixed $value): mixed` -* `registerResourceHandler(string $type, callable $wrapFn, callable $restoreFn): void` -* `clearResourceHandlers(): void` +* ``serialize(mixed $value): string`` +* ``unserialize(string $blob): mixed`` +* ``encode(mixed $value, bool $base64 = true): string`` +* ``decode(string $payload, bool $base64 = true): mixed`` +* ``wrap(mixed $value): mixed`` +* ``unwrap(mixed $value): mixed`` +* ``registerResourceHandler(string $type, callable $wrapFn, callable $restoreFn): void`` +* ``clearResourceHandlers(): void`` Resource Handler Example ------------------------ @@ -56,6 +56,6 @@ Resource Handler Example Notes ----- -* Registering the same resource type twice throws `InvalidArgumentException`. -* Wrapping/serializing unregistered resources throws `InvalidArgumentException`. +* Registering the same resource type twice throws ``InvalidArgumentException``. +* Wrapping/serializing unregistered resources throws ``InvalidArgumentException``. * Closure detection has an internal bounded memo cache.