Cache::extend() is a creation method; Laravel does not automatically wrap a returned Store in a Repository.
This page is based on the implementation of Laravel Framework v13.39.0. For basic usage, see Cache. For preparing a provider, see Package development.
Separate the driver name from the store name
If a custom creator exists for the configured
driver, CacheManager::build() calls it before any built-in driver. Do not reuse names such as redis or array; use a package-specific name.
Register before other providers boot
If another package uses the cache in itsboot(), registering your driver in your own boot() may be too late, depending on the order. Following the official documentation, register an application booting callback in register().
The following minimal example uses the standard ArrayStore so you can verify the registration path without an external service. Place it in src/MemoServiceProvider.php of the Acme\Memo package.
- During a normal boot, the application’s
bootingcallbacks run after every provider’sregister()has completed and before anyboot(). This is different from the provider’s own$this->booting(). - The
extend()callback receives the application and the store configuration array. Put Store creation and external connections in this creator callback. - This registration timing does not guarantee cache usage inside other providers’
register(). Keep service registration separate from actual usage.
CacheManager::extend() rebinds the scope of the anonymous closure to the Manager. Do not assume $this in a non-static closure refers to the provider; use a static closure and the $app argument, as in the example.
Add the store to the consuming application
Add it to the existingstores array in config/cache.php. Do not design the package to overwrite the consumer’s default or the entire stores array.
CACHE_STORE to the store name catalog, not the driver name. If you create multiple configurations for the same driver, a separate Repository is created for each store name.
Build the Repository yourself
CacheManager::callCustomCreator() returns the callback’s return value as is. If you return only a Store, get() and put() may work, but Repository methods such as remember() are unavailable and TTLs are interpreted differently.
In the example, repository($store, $config) is responsible for the following:
- Wrapping the Store in
Illuminate\Cache\Repository. - Passing the
storename that the Manager added to the configuration to the Repository. - Setting the event Dispatcher on the Repository if one is registered and the
eventssetting is not disabled.
serialize, which the example reads from the configuration.
Check the Store contract against the current implementation
Implement the methods required by Laravel 13’sIlluminate\Contracts\Cache\Store. Do not distribute a list of empty methods from an old sample as a working driver.
The Repository treats
null as a cache miss. Do not treat false or 0 as missing in your implementation. Storing null does not act as a marker that prevents remember() from recomputing.
Verify TTLs through the Repository
Repository::put() processes the TTL before passing it to the Store.
Check support for add, locks, and tags separately
Implementing the Store contract alone does not guarantee support for every cache feature.- Atomic
add(): This is not part of the Store contract. When called with a positive TTL, the Repository uses the Store’sadd()if it exists. Otherwise, it falls back toget()andput(), so wrapping the Store in a Repository does not guarantee atomicity. For a shared backend, implement it with the backend’s atomic operations and verify concurrent execution. - Locks:
lock()andrestoreLock()inIlluminate\Contracts\Cache\LockProviderare a separate contract. Verify acquisition, owner matching, expiration, and that locks held by other owners are not released. The example’sArrayStorelocks do not provide mutual exclusion across processes. - Tags: The Repository checks whether the Store has
tags(). This generally requires an implementation using something likeTaggableStore; the Store contract alone does not enable it automatically.
flush() is a partial deletion that respects the configured prefix. Check what is deleted on a shared backend and explain it to users, including php artisan cache:clear.
Distinguish instance retention from data deletion
CacheManager is a singleton, and it also retains the result of store('catalog') per store name. Changing the configuration or re-registering with extend() does not update an already created Repository.
$same is the same object as $before. After forgetDriver(), the next retrieval creates $after from the new configuration.
forgetDriver() does not delete everything in the backend. However, because the example’s ArrayStore holds data inside the instance, the old data is not visible from the new Store. Also, references to the old Repository held by the consumer’s $before or by an already resolved cache.store singleton are not replaced.
In production, refresh the configuration cache and restart long-running workers as part of your normal deployment. Also consider service lifetimes, and do not treat re-resolution as a bulk update across all processes.
Test the creation path, not just facade mocks
In your package test environment, go through the real Manager and Repository. The following Testbench example verifies that the driver is available even in theboot() of another provider registered earlier. The example stores an array without changing the cache.serializable_classes restoration restriction, and also checks the raw stored value with ArrayStore::all(false).
many(), round-tripping false and 0, touch(), connection failures, concurrent updates, and the prefix and deletion scope. Do not treat a simple successful Cache::shouldReceive('get') as evidence of driver registration or compatibility with an external service.
Long-term maintenance checklist
- Make the driver name, store configuration, serialization format, exceptions, and return values on failure part of the public specification.
- Check the Store contract methods and optional features such as tags and locks for each supported Laravel version.
- Verify that the driver registers on a fresh boot with a cached configuration and can be used from other providers’
boot(). - When announcing compatibility changes to stored data, include your policy for migrating or discarding existing keys.
- Distinguish in-process instance retention, backend data, and the configuration cache.
Primary sources consulted
- Laravel official documentation: Adding custom cache drivers
- CacheManager: build, repository, extend, forgetDriver
- Repository: TTL conversion, add, tags, events
- Store contract / LockProvider contract
- ArrayStore: storage, expiration, serialization
- CacheServiceProvider: singleton registration of the Manager and cache.store
- Application: application booting and provider boot
- Laravel official tests: CacheManagerTest / CacheRepositoryTest