PHPackages                             sobstel/metaphore - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Caching](/categories/caching)
4. /
5. sobstel/metaphore

ActiveLibrary[Caching](/categories/caching)

sobstel/metaphore
=================

PHP cache slam defense using a semaphore to prevent dogpile effect (aka clobbering updates or stampending herd).

v2.0.1(2y ago)100179.4k↓35%9[2 issues](https://github.com/sobstel/metaphore/issues)1MITPHPPHP &gt;=8.2

Since Sep 27Pushed 2y ago10 watchersCompare

[ Source](https://github.com/sobstel/metaphore)[ Packagist](https://packagist.org/packages/sobstel/metaphore)[ Docs](https://github.com/sobstel/metaphore)[ Fund](https://ko-fi.com/sobstel)[ RSS](/packages/sobstel-metaphore/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (3)Dependencies (1)Versions (16)Used By (1)

metaphore
=========

[](#metaphore)

PHP cache slam defense using a semaphore to prevent dogpile effect (aka clobbering updates, stampeding herd or Slashdot effect).

**Problem**: too many requests hit your website at the same time while it tries to regenerate same content slamming your database, eg. when cache expired.

**Solution:** first request generates new content while all the subsequent requests get (stale) content from cache until it's refreshed by the first request.

Read for more details.

[![Buy Me a Coffee at ko-fi.com](https://camo.githubusercontent.com/7f803b4c3a634f4d451c4310d00c2520aae1f15ef910420a0f27890e3c4787dd/68747470733a2f2f617a3734333730322e766f2e6d7365636e642e6e65742f63646e2f6b6f6669332e706e673f763d30)](https://ko-fi.com/sobstel)

Installation
------------

[](#installation)

In composer.json file:

```
"require": {
  "sobstel/metaphore": "2.0.*"
}

```

or just `composer require sobstel/metaphore`

Usage
-----

[](#usage)

```
use Metaphore\Cache;
use Metaphore\Store\MemcachedStore;

// initialize $memcached object (new Memcached())

$cache = new Cache(new MemcachedStore($memcached));
$cache->cache('key', function() {
    // generate content
}, 30);
```

Public API (methods)
--------------------

[](#public-api-methods)

- `__construct(ValueStoreInterface $valueStore, LockManager $lockManager = null)`
- `cache($key, callable $callable, [$ttl, [$onNoStaleCacheCallable]])` - returns result
- `delete($key)`
- `getValue($key)` - returns Value object
- `setResult($key, $result, Ttl $ttl)` - sets result (without anti-dogpile-effect mechanism)
- `onNoStaleCache($callable)`
- `getValueStore()`
- `getLockManager()`

Value store vs lock store
-------------------------

[](#value-store-vs-lock-store)

Cache values and locks can be handled by different stores.

```
$valueStore = new Metaphore\MemcachedStore($memcached);

$lockStore = new Your\Custom\MySQLLockStore($connection);
$lockManager = new Metaphore\LockManager($lockStore);

$cache = new Metaphore\Cache($valueStore, $lockManager);
```

By default - if no 2nd argument passed to Cache constructor - value store is used as a lock store.

Sample use case might be to have custom MySQL GET\_LOCK/RELEASE\_LOCK for locks and still use in-built Memcached store for storing values.

Time-to-live
------------

[](#time-to-live)

You can pass simple integer value...

```
$cache->cache('key', callback, 30); // cache for 30 secs
```

.. or use more advanced `Metaphore\TTl` object, which gives you control over grace period and lock ttl.

```
// $ttl, $grace_ttl, $lock_ttl
$ttl = new Ttl(30, 60, 15);

$cache->cache('key', callback, $ttl);
```

- `$ttl` - regular cache time (in seconds)
- `$grace_ttl` - grace period, how long to allow to serve stale content while new one is being generated (in seconds), similar to HTTP's stale-while-revalidate, default is 60s
- `$lock_ttl` - lock time, how long to prevent other request(s) from generating same content, default is 5s

Ttl value is added to current timestamp (`time() + $ttl`).

No stale cache
--------------

[](#no-stale-cache)

In rare situations, when cache gets expired and there's no stale (generated earlier) content available, all requests will start generating new content.

You can add listener to catch this:

```
$cache->onNoStaleCache(function (NoStaleCacheEvent $event) {
    Logger::log(sprintf('no stale cache detected for key %s', $event->getKey()));
});
```

You can also affect value that is returned:

```
$cache->onNoStaleCache(function (NoStaleCacheEvent $event) {
    $event->setResult('new custom result');
});
```

Tests
-----

[](#tests)

Run all tests: `phpunit`.

If no memcached or/and redis installed: `phpunit --exclude-group=notisolated` or `phpunit --exclude-group=memcached,redis`.

###  Health Score

47

—

FairBetter than 94% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity46

Moderate usage in the ecosystem

Community22

Small or concentrated contributor base

Maturity83

Battle-tested with a long release history

 Bus Factor1

Top contributor holds 90.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~264 days

Recently: every ~758 days

Total

14

Last Release

814d ago

Major Versions

v1.2.6 → v2.0.02024-02-24

PHP version history (3 changes)v1.0.0-BETA1PHP &gt;=5.4.0

v2.0.0PHP &gt;=7.4.0

v2.0.1PHP &gt;=8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/7457bb91566e5572c2d429636acc1ceada24a28335f662357c10a34c3693427a?d=identicon)[sobstel](/maintainers/sobstel)

---

Top Contributors

[![sobstel](https://avatars.githubusercontent.com/u/117428?v=4)](https://github.com/sobstel "sobstel (125 commits)")[![wojluk](https://avatars.githubusercontent.com/u/10763237?v=4)](https://github.com/wojluk "wojluk (6 commits)")[![lchenay](https://avatars.githubusercontent.com/u/804687?v=4)](https://github.com/lchenay "lchenay (3 commits)")[![adambro](https://avatars.githubusercontent.com/u/649233?v=4)](https://github.com/adambro "adambro (2 commits)")[![aykutfarsak](https://avatars.githubusercontent.com/u/311125?v=4)](https://github.com/aykutfarsak "aykutfarsak (1 commits)")[![jarkt](https://avatars.githubusercontent.com/u/2691295?v=4)](https://github.com/jarkt "jarkt (1 commits)")

---

Tags

cachingphpcache

### Embed Badge

![Health badge](/badges/sobstel-metaphore/health.svg)

```
[![Health](https://phpackages.com/badges/sobstel-metaphore/health.svg)](https://phpackages.com/packages/sobstel-metaphore)
```

###  Alternatives

[cache/predis-adapter

A PSR-6 cache implementation using Redis (Predis). This implementation supports tags

272.6M13](/packages/cache-predis-adapter)[contributte/redis

Redis client integration into Nette framework

181.6M2](/packages/contributte-redis)[pstaender/silverstripe-redis-cache

Enables Redis cache for SilverStripe

1199.1k](/packages/pstaender-silverstripe-redis-cache)[abouvier/slim-redis-cache

Redis cache middleware for Slim framework

172.0k](/packages/abouvier-slim-redis-cache)[quick/cache

This is a cache system that uses Redis for rapid caching.

122.7k](/packages/quick-cache)[bnomei/kirby3-redis-cachedriver

Advanced Redis cache-driver with in-memory store, transactions and preloading

101.7k](/packages/bnomei-kirby3-redis-cachedriver)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
