Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 70 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -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).
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
53 changes: 51 additions & 2 deletions docs/_static/theme.css
Original file line number Diff line number Diff line change
@@ -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;
}
14 changes: 7 additions & 7 deletions docs/adapters/apcu.rst
Original file line number Diff line number Diff line change
@@ -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 (`<ns>:<key>`)
* namespace-prefixed keys (``<ns>:<key>``)
* efficient bulk fetch through APCu array fetch path

`Cache::local()` will choose APCu automatically when available.
``Cache::local()`` will choose APCu automatically when available.
4 changes: 2 additions & 2 deletions docs/adapters/array-memory.rst
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
8 changes: 4 additions & 4 deletions docs/adapters/chain.rst
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -16,8 +16,8 @@ Behavior:

Typical layout:

* L1: in-memory (`ArrayCacheAdapter`)
* L2: network cache (`RedisCacheAdapter`)
* L1: in-memory (``ArrayCacheAdapter``)
* L2: network cache (``RedisCacheAdapter``)

Example:

Expand Down
20 changes: 10 additions & 10 deletions docs/adapters/dynamodb.rst
Original file line number Diff line number Diff line change
@@ -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``
14 changes: 7 additions & 7 deletions docs/adapters/file.rst
Original file line number Diff line number Diff line change
@@ -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_<sanitized-namespace>`
* file name: `hash('xxh128', $key) . '.cache'`
* base dir: provided ``$dir`` or ``sys_get_temp_dir()``
* namespace dir: ``cache_<sanitized-namespace>``
* 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.
12 changes: 6 additions & 6 deletions docs/adapters/memcached.rst
Original file line number Diff line number Diff line change
@@ -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.
18 changes: 9 additions & 9 deletions docs/adapters/mongodb.rst
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -22,8 +22,8 @@ Highlights:

Supported injected collection methods:

* `findOne`
* `updateOne`
* `deleteOne`
* `deleteMany`
* `countDocuments`
* ``findOne``
* ``updateOne``
* ``deleteOne``
* ``deleteMany``
* ``countDocuments``
Loading
Loading