joefallon/phpcache

This package contains a tag-based cache (APC only).

Installs: 36

Dependents: 0

Suggesters: 0

Security: 0

Stars: 3

Watchers: 2

Forks: 0

Open Issues: 0

pkg:composer/joefallon/phpcache

v4.0.1 2025-10-12 22:51 UTC

This package is auto-updated.

Last update: 2025-10-12 22:52:26 UTC


README

A simple, dependency-free PHP library that adds tagging and namespacing on top of a small key/value cache backend (APCu by default). It provides a lightweight, easy-to-integrate TaggedCache for invalidating groups of cache entries by tag, plus a thin ApcCache adapter that uses APCu as the backing store.

Why use this library?

  • Tag-based invalidation: clear groups of related cache entries with a single call.
  • Namespaces: isolate caches within the same backing store (useful for multi-tenant apps or tests).
  • Minimal and portable: back-end is pluggable via the Cacheable interface, so you can add other adapters.

Quick status

  • Package: joefallon/phpcache
  • License: MIT
  • Requires: PHP 7.4+ (uses typed properties)
  • Optional extension: APCu (for ApcCache)

Installation

Require the package via Composer:

composer require joefallon/phpcache

(If you're developing locally, you can also clone the repo and include it with Composer using path repositories.)

Note: To use the included ApcCache you must have the APCu extension enabled (CLI and/or FPM depending on your environment). If APCu isn't available, implement the JoeFallon\PhpCache\Cacheable interface to plug in another backend (Redis, Memcached, filesystem, etc.).

Basic usage

This example shows the two main classes included in the package: ApcCache (back-end) and TaggedCache (feature layer).

Example — non-tagged value:

use JoeFallon\PhpCache\ApcCache;
use JoeFallon\PhpCache\TaggedCache;

$backing = new ApcCache();
$cache = new TaggedCache($backing); // default namespace and default expiration

$cache->store('user_45_profile', $profileData);
if ($cache->exists('user_45_profile')) {
    $profile = $cache->retrieve('user_45_profile');
}

$cache->remove('user_45_profile');

Example — tagged values and bulk invalidation:

use JoeFallon\PhpCache\ApcCache;
use JoeFallon\PhpCache\TaggedCache;

$cache = new TaggedCache(new ApcCache());

$cache->store('all_posts', $postsList, ['posts_table']);
$cache->store('post_45', $singlePost, ['posts_table']);

// Later, when posts are modified, invalidate everything tagged with 'posts_table':
$cache->removeByTag('posts_table');

Namespaces

TaggedCache supports a namespace string passed to its constructor. Namespacing is applied to all keys and tag lists, so you can safely use multiple independent caches in the same backing store:

$cacheA = new TaggedCache(new ApcCache(), 'siteA');
$cacheB = new TaggedCache(new ApcCache(), 'siteB');

Expiration (TTL)

  • TaggedCache accepts an optional defaultExpiresInSeconds in its constructor (defaults to 1 year).
  • When storing an entry you may supply a custom TTL (seconds) as the 4th argument of store(); passing 0 uses the cache's default TTL.

Example (custom TTL):

$cache = new TaggedCache(new ApcCache(), '', 3600); // default TTL = 1 hour
$cache->store('weather', $data, null, 300); // expires in 5 minutes

Storage format (implementation note)

Entries saved by TaggedCache are stored in the underlying Cacheable using a small associative structure with the following keys:

  • expires — timestamp string when the entry should be considered expired;
  • tags — null or array of tag names attached to this entry;
  • value — the original value saved by the caller.

This is internal implementation detail but useful to know if you inspect the backing store directly.

Public API (main methods)

  • store(string $key, $value, array $tags = null, int $expiresInSeconds = 0) — store a value. Tags are optional. If $expiresInSeconds is 0 the cache's default is used.
  • retrieve(string $key) — returns the stored value or null if missing or expired.
  • exists(string $key) — returns true if the key exists and is not expired.
  • remove(string $key) — removes the key and removes it from any tag lists.
  • removeByTag(string $tag) — removes all keys that were stored with the given tag.
  • removeAll() — removes every key previously stored through this TaggedCache instance (it relies on an internal ALL_KEYS list).

Best practices and gotchas

  • APCu is shared across the process: if you run PHP-FPM and also use CLI scripts, make sure APCu is enabled for the environment you expect. On some systems CLI APCu is disabled by default.
  • Storing null as a value: the library stores null values correctly; exists() will return true for a saved null (it reflects presence, not non-nullness). retrieve() will return null in that case.
  • Avoid long-lived in-memory caches for highly dynamic data; use shorter TTLs and explicit tag invalidation when appropriate.
  • When writing integration tests, pass a namespace to TaggedCache to avoid clobbering other tests or host caches.

Implementing a custom backend

If you can't or don't want to use APCu, implement the JoeFallon\PhpCache\Cacheable interface and pass your adapter into TaggedCache. The interface is intentionally small:

  • store(string $key, $value)
  • retrieve(string $key)
  • exists(string $key): bool
  • remove(string $key)
  • removeAll()

A custom adapter lets you use Redis, Memcached, or a filesystem-based cache.

Testing locally

The project now uses PHPUnit (v9.6) for unit tests. Run the test suite using the repository-installed PHPUnit binary:

# install dependencies (including dev deps) if needed
composer install --no-interaction

# run PHPUnit with the repository configuration
vendor/bin/phpunit --configuration phpunit.xml.dist

Notes:

  • PHPUnit 9.6 supports PHP 7.4 through 8.2. Ensure your PHP runtime is within that range when running tests.
  • The test bootstrap (tests/bootstrap.php) provides an APCu polyfill when the apcu extension is not available or not enabled for CLI. If you have APCu installed and enabled for CLI (apc.enable_cli=1), native APCu will be used instead.
  • The previous KissTest harness has been removed as part of the migration — see CHECKLIST.md for migration details.

Troubleshooting

  • "Class not found" errors when running the examples or tests? Ensure Composer's autoloader is included:
require_once __DIR__ . '/vendor/autoload.php';
  • APCu functions not found? Ensure the apcu extension is installed and enabled for the PHP SAPI you are using. On Debian/Ubuntu:
sudo apt-get install php-apcu
# or for a specific PHP version, e.g. php8.1-apcu
sudo apt-get install php8.1-apcu
  • Stale items remain after removeByTag / removeAll? Ensure you are using the same namespace and that your adapter supports removing individual keys. If you implemented a custom adapter, verify its remove() and store() behavior.

Contributing

Contributions are welcome. Some suggestions:

  • Add adapters for other cache backends (Redis, Memcached, file).
  • Improve tests and CI configuration.
  • Fix bugs or improve the API ergonomics.

Please fork, open a feature branch, and submit a pull request. Keep changes small and include tests.

License

This project is licensed under the MIT license — see the LICENSE file for details.

Acknowledgements

Inspired by the need to tag and invalidate groups of cache entries when related data changes. If you find this useful, please star the repository and consider contributing improvements or adapters for other backends.

Contact / Support

If you need help integrating the library, open an issue with a minimal reproducible example and I'll take a look.

Built with ❤️ and the KISS principle.