Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Array Adapter Laravel Package

cache/array-adapter

In-memory PSR-6 and PSR-16 cache pool for tests and short-lived scripts, with tags, hierarchical keys, and an optional item limit. Values last only for the current PHP process.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install as a dev dependency. This pool is a test/dev tool. Anything else it can do, Laravel's array store or a real PSR-6 backend does better.

composer require --dev cache/array-adapter:^3.0

2. Instantiate it. There is nothing to configure — no driver name, no env vars, no service provider.

use Cache\Adapter\PHPArray\ArrayCachePool;

$pool = new ArrayCachePool();        // unlimited items
$pool = new ArrayCachePool(1_000);   // optional FIFO cap

3. Bind the interface, not the class. The only thing that makes this package worth installing is that it satisfies Psr\Cache\CacheItemPoolInterface. Wire it that way from the very first line of code, so swapping it later is a one-line change.

// tests/TestCase.php
use Psr\Cache\CacheItemPoolInterface;
use Cache\Adapter\PHPArray\ArrayCachePool;

protected function setUp(): void
{
    parent::setUp();
    $this->app->instance(CacheItemPoolInterface::class, new ArrayCachePool());
}

First legitimate use case: you have a service — yours or a vendor SDK's — that type-hints CacheItemPoolInterface and in production receives a Redis/Memcached PSR-6 pool. In tests you replace it with ArrayCachePool and delete the infrastructure dependency from your CI pipeline. That is the whole value proposition; if you grep the codebase for CacheItemPoolInterface and Psr\Cache and get zero hits, stop here and use Cache::store('array').

Where to look first: the README is four short sections. Read the "Limit stored items" paragraph carefully (it describes eviction semantics, not just a number) and the "Upgrading to version 3" note (tag storage changed). The changelog is not in this repo — it lives in the php-cache/cache monorepo, which is where you must go for upgrade-impact analysis.

Implementation Patterns

Test double for a PSR-6-typed dependency. The canonical pattern: production binds a real pool, the test case rebinds it before the container resolves the service.

$this->app->singleton(CacheItemPoolInterface::class, fn () => new ArrayCachePool());
$service = $this->app->make(ReportBuilder::class); // receives the array pool

Bind as a singleton within the test, not a fresh instance per resolution — otherwise save() in one collaborator is invisible to the next, and you get false passing tests.

Assertion surface. Because the pool is inspectable in-process, it doubles as a spy. Assert the side effect instead of mocking the pool:

$pool->hasItem('reports.2024-q1');       // was it primed?
$pool->getItem('reports.2024-q1')->get(); // with what value?

This is usually cheaper and less brittle than Mockery::mock(CacheItemPoolInterface::class)->shouldReceive('save').

Standard read-through, framework-agnostic. Keep the caching logic in your own class so the same code runs against Redis in production:

final class ProfileRepository
{
    public function __construct(private CacheItemPoolInterface $cache) {}

    public function find(int $id): Profile
    {
        $item = $this->cache->getItem("profile.{$id}");
        if ($item->isHit()) {
            return $item->get();
        }

        $profile = Profile::query()->findOrFail($id);

        $item->set($profile)->expiresAfter(300);
        $this->cache->save($item);

        return $profile;
    }
}

Decorate for observability. Rather than reaching for a debugger, wrap the pool in a thin decorator that logs hits/misses during a test run. Because it is an interface, the decorator survives the swap to production.

final class LoggingPool implements CacheItemPoolInterface { /* delegates, counts isHit() */ }

Prototype tag invalidation before buying infrastructure. If you plan to use tags in Redis, prove the invalidation flow here first: save items under users and reports, clear one tag, assert the other survives. It costs seconds, versus discovering a tag-design flaw after the Redis cluster is provisioned. Version 3 stores a generation snapshot with each tagged item plus a marker per tag — behaviour that is invisible in the API but affects memory and correctness when tags are used heavily.

Hierarchical keys. Dotted keys are the php-cache convention for logical nesting (tenant.7.users.list). Before depending on parent-deletes-children semantics, confirm what your installed version actually does with a two-line script — it is the one advertised feature with no README example:

$pool->save($pool->getItem('tenant.7.users'));
$pool->deleteItem('tenant.7');
var_dump($pool->hasItem('tenant.7.users')); // verify, don't assume

Seed the pool for state-dependent tests. When you need a warm cache at the start of a test, build it in setUp() from a small fixture array rather than relying on the code under test to populate it — that keeps the test about behaviour, not about cache warming.

One-off commands and scripts. For an Artisan command that memoizes expensively-derived values for its own process lifetime, this is a legitimate (if minor) use: no Redis required, no cleanup.

Gotchas and Tips

It is not a Laravel cache store. ArrayCachePool implements PSR-6; the Cache facade speaks PSR-16/Illuminate\Contracts\Cache\Store. There is no Cache::remember() here, and CACHE_STORE=array-adapter will not work without you writing a Store wrapper plus Cache::extend(). Write that wrapper only if you genuinely need facade access to a PSR-6 pool — otherwise type-hint the interface and be done.

Wiring it into a production path is a silent, expensive failure. A miss-every-time cache still works; the system just degrades to fully uncached and dumps Redis-sized load onto the database. It looks like a performance regression, not a misconfiguration. Guard against it structurally: instantiate the class only in tests/ and local dev providers, and consider an architecture test (PHPStan rule or an Ark test) banning the Cache\Adapter\PHPArray namespace outside tests/.

Long-running processes are the real hazard. Under Octane, Swoole, or a queue worker the pool survives across requests and jobs. Two consequences: (a) unbounded memory growth if you construct it without the item limit, and (b) — worse — correctness bugs, because job #2 can read a value job #1 cached in the same worker. Always pass a maximum: new ArrayCachePool(1_000).

Eviction is slot-based, not LRU. Per the README, when the pool is full, saving a new key evicts "the key in the next storage slot," and updating an existing key keeps its slot. So a hot key is not protected by being hot — a burst of new keys will evict it. Do not write eviction or hit-ratio tests against this pool and expect them to describe production behaviour.

Objects are held by reference, not serialized. Unlike Redis, there is no serialization boundary. If you save() an object and later mutate the same instance in your test, the cached value changes underneath you. Store arrays/scalars in tests, or clone on both save and read, and you avoid a class of confusing failures. (Verify in a one-liner if you are relying on either direction.)

Do not sleep() for TTL tests. Expiration is lazy and evaluated on read. To exercise expiry deterministically, set a past timestamp rather than waiting:

$item->set('x')->expiresAt(new \DateTimeImmutable('@1'));
$pool->save($item);
$this->assertFalse($pool->getItem('x')->isHit());

This also means stale items can still be readable until read/eviction — never treat TTL assertions here as proof of production expiry semantics.

PSR-6 key rules apply and throw. Reserved characters { } ( ) / \ @ : in keys raise InvalidArgumentException. Since dotted keys are the hierarchy convention, the : collision is the one that bites — pick a separator and stay consistent.

Fresh pool per test beats clear(). Because state is process-local and dies with the PHP process, a new instance per test is the cleanest isolation. Reuse one only if you deliberately want cross-test state — and then call clear() in tearDown().

Read-only split repo. The GitHub repo is a [READ-ONLY] subtree split of php-cache/cache; you cannot patch it locally without forking or vendoring, and PRs must go upstream. Release notes for most versions are the single line "See change log," with the changelog not present in this repo — treat every version bump as a small manual review of composer.json PHP constraints and the upstream changelog.

Pin the major and re-check at PHP upgrades. With 56 stars and zero dependents, ecosystem pressure will not surface a PHP-minor incompatibility for you. It will appear as a Composer resolution failure in your CI — the cheapest possible failure, but only if you keep the usage surface small enough to swap out in an afternoon.

Have the exit ramp written down. Bind Psr\Cache\CacheItemPoolInterface everywhere, reference ArrayCachePool in exactly two places (a dev provider and a test base class), and note that symfony/cache's ArrayAdapter is the drop-in replacement — it is PSR-6 and PSR-16, actively maintained, and has same-interface Redis/filesystem siblings for production. That single discipline is worth more than any argument about which array pool is marginally better.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support