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
Requires
- php: >=7.4.0
- ext-apcu: *
Requires (Dev)
- phpunit/phpunit: ^9.6
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
Cacheableinterface, 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)
TaggedCacheaccepts an optionaldefaultExpiresInSecondsin its constructor (defaults to 1 year).- When storing an entry you may supply a custom TTL (seconds) as the 4th argument of
store(); passing0uses 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$expiresInSecondsis0the cache's default is used.retrieve(string $key)— returns the stored value ornullif missing or expired.exists(string $key)— returnstrueif 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 thisTaggedCacheinstance (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
nullas a value: the library storesnullvalues correctly;exists()will returntruefor a savednull(it reflects presence, not non-nullness).retrieve()will returnnullin 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
TaggedCacheto 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): boolremove(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 theapcuextension 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.mdfor 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
apcuextension 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 itsremove()andstore()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.