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

Polyfill Apcu Laravel Package

symfony/polyfill-apcu

Symfony Polyfill / APCu brings the apcu_* functions and the APCuIterator class to projects still running the legacy APC extension, letting older code use APCu without changes. Lightweight compatibility layer from Symfony, released under the MIT license.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're installing. This package is not a cache. It contains no store, no service provider, no config file. It is a small set of global functions (apcu_fetch, apcu_store, apcu_add, apcu_delete, apcu_exists, apcu_inc, apcu_dec, apcu_entry, apcu_clear_cache, apcu_cache_info, apcu_sma_info, plus the APCuIterator class) that are auto-loaded by Composer's files directive and forward each call to the legacy apc_* function of the same name. If ext-apc isn't loaded, the forwarding call is a fatal Error.

Install (only when the preconditions hold):

composer require symfony/polyfill-apcu

Preconditions: the target host runs PHP ≤ 5.4 with ext-apc loaded, and there is code in app/ or vendor/ that calls apcu_* directly rather than going through Laravel's Cache facade.

Three commands before anything else:

composer why symfony/polyfill-apcu          # who pulled it in, and is it direct?
php -m | grep -i apc                        # apc? apcu? neither?
grep -rn "apcu_" app/ config/ database/     # is any of it load-bearing?

Where to look first: vendor/symfony/polyfill-apcu/bootstrap.php (the registration) and the Apcu class beside it — it's a few hundred lines of straight function_exists() guards and delegation. The README defers to the polyfill monorepo README, which is the only source that explains the conditional-definition mechanism.

First verification snippet — run it on the actual target host, not locally:

php -r "var_dump(function_exists('apcu_fetch'), extension_loaded('apcu'), extension_loaded('apc'));"

You want true, false, true for the package to do anything at all. true, false, false means you installed a shim over an undefined function and calls to apcu_fetch() will fatal.

First real use case: a legacy Symfony 2 / Laravel 4.2-era app on a frozen PHP 5.4 image, where a vendored library calls apcu_* and a PHP upgrade is genuinely blocked. Everywhere else — including every Laravel version from 5.1 through 12, all of which require PHP ≥ 5.5.9 — the correct first move is to not install it and instead read the next section.

Implementation Patterns

Pattern 1 — inert transitive dependency (the common case). Something in vendor/ requires it; you never call apcu_*. Do nothing except record it in the next dependency-audit notes. Confirm inertness with the three commands above, and check vendor/composer/autoload_files.php for the polyfill-apcu/bootstrap.php entry. Removal is a one-line composer remove if it's direct, otherwise wait for the upstream package to drop it.

Pattern 2 — replace the call sites, don't shim them. This is the pattern to reach for in 99% of Laravel work. The mapping is mechanical:

Direct APC call Cache-facade replacement
apcu_fetch($k) Cache::get($k)
apcu_store($k, $v, $ttl) Cache::put($k, $v, $ttl)
apcu_add($k, $v, $ttl) Cache::add($k, $v, $ttl)
apcu_delete($k) Cache::forget($k)
apcu_exists($k) Cache::has($k)
apcu_inc($k, $n) Cache::increment($k, $n)
apcu_clear_cache() Cache::flush()
new APCuIterator($regex) tag the keys, or maintain an index key, and use Cache::tags()

Then make the store a deployment concern, not a code concern:

# .env — production
CACHE_STORE=redis
# .env — local / CI
CACHE_STORE=file

Pattern 3 — adapter object around the legacy call sites, mid-refactor. When you can't change every call site in one PR, hide the raw apcu_* calls behind one class that delegates to the Laravel cache and gives you a single place to grep later:

namespace App\Support;

use Illuminate\Support\Facades\Cache;

class AppCache
{
    public static function fetch(string $key, mixed $default = null): mixed
    {
        return Cache::get($key, $default);
    }

    public static function store(string $key, mixed $value, int $ttl = 0): bool
    {
        return Cache::put($key, $value, $ttl ?: null);
    }
}

Swap AppCache:: for Cache:: in the long run; the point is that grep -rn "apcu_" app/ returning zero is the exit criterion, and this wrapper gets you there without a big-bang rewrite.

Pattern 4 — tests that touch apcu_*. Don't rely on the polyfill to make function_exists('apcu_fetch') true in CI — on a PHP 8 CI image the shim exists but the underlying apc_fetch doesn't, so the test fatals the moment it's exercised. Either bind the code under test to the Cache contract and inject Cache::store('array'), or define your own no-op stubs in tests/bootstrap.php before autoloading:

// tests/bootstrap.php — explicit, greppable, never production
if (! function_exists('apcu_fetch')) {
    function apcu_store($k, $v, $t = 0) { return true; }
    function apcu_fetch($k, &$ok = null) { $ok = false; return false; }
    function apcu_delete($k) { return true; }
}

That is more honest than a polyfill that claims to exist and then forwards into the void.

Pattern 5 — if you genuinely must use it. Only on a host with ext-apc loaded and only for lossy, process-local data (config hints, feature-flag snapshots). Guard every entry point with function_exists('apcu_fetch') — never extension_loaded('apcu') — and never let a miss be a correctness failure. Keep it off request hot paths; every call pays a function-call hop and, when the shim is active, an extra dispatch.

Workflow tip: make the driver decision reviewable. If a PR adds symfony/polyfill-apcu to composer.json, the review question is one line: "Which host has ext-apc and not ext-apcu?" If nobody can name one, the PR closes.

Gotchas and Tips

extension_loaded('apcu') stays false. This is the one that bites hardest. The polyfill defines functions; it does not register an extension. Laravel's CacheManager::createApcuDriver() guards on the extension being loaded and throws "This cache store requires the PHP APCu extension." So CACHE_STORE=apcu does not start working after composer require symfony/polyfill-apcu. Same for any third-party package gating on extension_loaded('apcu') or apcu_enabled() — those paths bypass the shim entirely.

Fatal error, not graceful degradation. With neither apc nor apcu present, apcu_fetch() resolves to the polyfill's body, which calls the undefined apc_fetch(): Error: Call to undefined function apc_fetch(). There is no fallback store and no warning. If your mental model was "safety net for hosts without APCu," delete it.

The polyfill is harmless where APCu is installed. Every definition is wrapped in function_exists(), and since PHP extensions load before Composer's autoloader runs, native APCu always wins. Installing this on a modern box costs ~1.5MB and nothing else — which is exactly why it can sit inert for years unnoticed.

CLI and FPM are different memories. APC/APCu shared memory is per-SAPI and per-process-pool. php artisan tinker, queue workers, and artisan cache:warm see an empty cache while web requests see a populated one. If a value "exists in the browser but not in the queue," this is why — and it's a property of the extension, not something the polyfill can fix.

Not atomic across workers. apcu_inc, apcu_cas, and anything you'd build a lock or rate limiter on are per-process. Laravel's RateLimiter and Cache::lock() need a shared store: Redis, Memcached, DynamoDB, or database. Never APCu, polyfilled or not.

Composer quirks. The registration lives in vendor/composer/autoload_files.php; if function definitions "disappear," check composer dump-autoload ran and that nobody set "autoload": {"files": ...} ordering assumptions. composer why symfony/polyfill-apcu is the fastest way to find the package that dragged it in — with zero listed dependents on the registry, in your tree it will always be either a direct require or a sibling polyfill's dependency.

Release history is not a maturity signal. v1.34 → v1.37 all say "no significant changes" and one entry is future-dated. That's Symfony's monorepo split-release automation, not active development. Treat the version number as a PHP-floor marker: when Symfony raises its floor, this package stops receiving releases and is eventually abandoned.

There is no config surface and no extension point. No config/polyfill-apcu.php, no service provider, no ApcuFunctions::register()/isRegistered() API (that class/API is sometimes cited in internal write-ups and does not exist in the package — don't write acceptance criteria against it). The only supported extension point is Laravel's own: implement Illuminate\Contracts\Cache\Store and register it with Cache::extend() in a service provider. If you find yourself doing that to bypass ensureExtensionIsLoaded(), you are on an unsupported path and you own the bugs.

Debugging checklist when behavior looks wrong:

php -r "var_dump(function_exists('apcu_fetch'), extension_loaded('apcu'), extension_loaded('apc'));"
php -r "var_dump(apcu_store('k','v'), apcu_fetch('k'));"   # does it actually round-trip?
php artisan config:show cache                              # which store is actually bound?
composer show symfony/polyfill-apcu                        # installed where?

If the second command errors or returns false, false, no shim will save you — change CACHE_STORE and move on.

Removal runbook. composer remove symfony/polyfill-apcu (if direct) → grep -rn "apcu_" app/ config/ returns nothing load-bearing → run the full test suite on the lowest PHP version you support → confirm every environment's CACHE_STORE resolves to a store that doesn't need ext-apcu. Budget this as a small cleanup ticket, not a migration.

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.
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics
robbiep/afterthedeadline
frankkessler/incontact-laravel-oauth2-rest
jlxh/laravel-userlog
davidmgilo/timetables
qylinfly/role-permission
reflect-digital/conduit
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr