> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Eigen cachestores en contracten voor packages

> Ontleed de CacheManager en het Store-contract van Laravel 13 en verifieer het registratiemoment van drivers, de TTL-conversie door de Repository, het vasthouden van instanties en de ondersteuning van onder meer locks.

Wanneer je vanuit een package een eigen cachebackend wilt aanbieden, scheid je **de Store die het opslaan implementeert** van **de Repository die de Laravel-API biedt**. Wat je met `Cache::extend()` registreert, is de manier van aanmaken; Laravel pakt een geretourneerde Store niet automatisch in een Repository in.

Deze pagina is gecontroleerd aan de hand van de implementatie van Laravel Framework `v13.39.0`. Zie [Cache](https://kawax.biz/nl/cache.md) voor de basisbediening en [Packageontwikkeling](https://kawax.biz/nl/advanced/package-development.md) voor het voorbereiden van de provider.

## Scheid de drivernaam van de storenaam

| Element | Voorbeeld op deze pagina | Rol |
| - | - | - |
| Drivernaam | `acme-memory` | De aanmaakmethode die je met `extend()` registreert |
| Storenaam | `catalog` | De configuratie in `cache.stores` en de identifier van de aangemaakte Repository |
| Store | `Illuminate\Cache\ArrayStore` | Opslaan en ophalen van keys en beheer van de vervaltijd |
| Repository | `Illuminate\Cache\Repository` | `remember()`, TTL-conversie, cache-events enzovoort |

`CacheManager::build()` roept een eigen aanmaakfunctie voor de `driver` uit de configuratie aan vóór de ingebouwde drivers, als die bestaat. Hergebruik dus geen namen als `redis` of `array`, maar gebruik een packagespecifieke naam.

```mermaid theme={null}
flowchart TD
    A["Cache::store('catalog')"] --> B{"Is catalog al geresolved?"}
    B -->|Ja| C["Vastgehouden Repository retourneren"]
    B -->|Nee| D["cache.stores.catalog inlezen"]
    D --> E["Aanmaakfunctie voor driver: acme-memory"]
    E --> F["Store aanmaken en<br>inpakken met repository()"]
    F --> G["Vasthouden onder de naam catalog"]
```

## Registreer vóór de boot van andere providers

Als een ander package de cache in zijn `boot()` gebruikt, kan het afhankelijk van de volgorde te laat zijn wanneer je de driver in je eigen `boot()` registreert. Registreer, in lijn met de officiële documentatie, in `register()` een **`booting`-callback van de applicatie**.

Hieronder volgt een minimaal voorbeeld met de standaard `ArrayStore`, zodat je het registratiepad zonder externe service kunt controleren. Plaats het in `src/MemoServiceProvider.php` van het package `Acme\Memo`.

```php theme={null}
<?php

namespace Acme\Memo;

use Illuminate\Cache\ArrayStore;
use Illuminate\Cache\Repository;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;

class MemoServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->booting(static function (Application $app): void {
            $app['cache']->extend('acme-memory', static function (
                Application $app,
                array $config,
            ): Repository {
                $store = new ArrayStore(
                    $config['serialize'] ?? false,
                    $app['config']->get('cache.serializable_classes'),
                );

                return $app['cache']->repository($store, $config);
            });
        });
    }
}
```

Laad de provider bijvoorbeeld via [package auto-discovery](https://kawax.biz/nl/advanced/package-discovery.md). Maak deze provider, die alleen een uitbreiding registreert, geen deferred provider, zodat hij zowel bij HTTP als bij Artisan wordt geladen.

* De `booting` van de applicatie wordt bij een normale start uitgevoerd nadat de `register()` van alle providers is voltooid en vóór elke `boot()`. Dit is iets anders dan `$this->booting()` van de provider zelf.
* De callback van `extend()` ontvangt de applicatie en de configuratie-array van de store. Plaats het aanmaken van de Store en externe verbindingen in deze aanmaakcallback.
* Dit registratiemoment garandeert niet dat **cachegebruik binnen `register()`** van andere providers werkt. Scheid het registreren van services van het daadwerkelijke gebruik.

> [!TIP]
>
> Als je alleen een standaard in-memory cache nodig hebt, volstaat de `array`-driver. Dit voorbeeld dient om registratie en contracten uit te leggen en is niet geschikt voor delen tussen processen of persistentie. Vervang voor een eigen backend het aanmaken van de `ArrayStore` door een klasse die `Illuminate\Contracts\Cache\Store` implementeert.

`CacheManager::extend()` bindt de scope van anonieme closures opnieuw aan de Manager. Ga er niet van uit dat `$this` in een niet-statische closure de provider is, maar gebruik zoals in het voorbeeld een statische closure en het argument `$app`.

### Voeg de store toe in de gebruikende applicatie

Voeg hem toe aan de bestaande `stores`-array in `config/cache.php`. Ontwerp het package niet zo dat het de `default` of de volledige `stores` van de gebruiker overschrijft.

```php theme={null}
'catalog' => [
    'driver' => 'acme-memory',
    'serialize' => true,
],
```

```php theme={null}
use Illuminate\Support\Facades\Cache;

$value = Cache::store('catalog')->remember('product:42', 60, function () {
    return ['id' => 42, 'name' => 'Sample'];
});
```

Ook wanneer je hem als standaard wilt instellen, geef je in `CACHE_STORE` niet de drivernaam op, maar **de storenaam `catalog`**. Maak je meerdere configuraties met dezelfde driver, dan wordt per storenaam een aparte Repository aangemaakt.

## Stel de Repository zelf samen

`CacheManager::callCustomCreator()` retourneert de returnwaarde van de callback ongewijzigd. Retourneer je alleen een Store, dan werken `get()` en `put()` misschien wel, maar kun je bijvoorbeeld `remember()` van de Repository niet gebruiken en wordt de TTL anders geïnterpreteerd.

De `repository($store, $config)` uit het voorbeeld zorgt voor het volgende:

* De Store inpakken met `Illuminate\Cache\Repository`.
* De `store`-naam die de Manager aan de configuratie heeft toegevoegd doorgeven aan de Repository.
* Als er een event-Dispatcher is geregistreerd en de `events`-instelling niet is uitgeschakeld, de Dispatcher op de Repository instellen.

Dit past niet al je eigen instellingen toe op de Store. Verbindingsdoel, prefix, serialisatie en dergelijke verwerk je in de implementatie van de Store en in de aanmaakfunctie. Dat geldt ook voor `serialize`, dat in het voorbeeld uit de configuratie wordt gelezen.

## Vergelijk het Store-contract met de huidige implementatie

Implementeer de methoden die `Illuminate\Contracts\Cache\Store` in Laravel 13 vereist. Distribueer een lijst met lege methoden uit oudere voorbeelden niet zomaar als werkende driver.

| Methode | Te controleren gedrag |
| - | - |
| `get()` / `many()` | Niet gevonden of verlopen geeft `null`. `many()` retourneert een array met de opgevraagde keys als keys, met `null` voor niet-gevonden items |
| `put()` / `putMany()` | Opslaan met een TTL in seconden en succes of mislukking retourneren |
| `forever()` | Opslaan zonder vervaltijd. Dit betekent niet dat verdringing of verwijdering door de backend wordt voorkomen |
| `touch()` | De vervaltijd van een bestaande key wijzigen. Bij niet gevonden of verlopen `false` retourneren |
| `increment()` / `decrement()` | De bijgewerkte waarde of een mislukking retourneren. Verifieer ook gelijktijdige updates en het behoud van de TTL |
| `forget()` | De key verwijderen en het resultaat retourneren |
| `flush()` | De hele cache verwijderen. Maak duidelijk wat er daadwerkelijk wordt verwijderd |
| `getPrefix()` | De keyprefix retourneren die de Store gebruikt |

`null` wordt door de Repository als cache-miss behandeld. Zorg dat de implementatie `false` of `0` niet als niet-gevonden behandelt. Ook als je `null` opslaat, werkt dat niet als markering die herberekening door `remember()` voorkomt.

### Verifieer de TTL via de Repository

`Repository::put()` verwerkt de TTL voordat die aan de Store wordt doorgegeven.

| Aanroep op de Repository | Verwerking in de Store |
| - | - |
| `put('key', $value)` | `forever()` |
| `put('key', $value, 60)` | `put()` met het aantal seconden |
| `put('key', $value, $expiresAt)` | Omzetten van bijvoorbeeld een datum/tijd naar seconden en bij een positieve waarde `put()` |
| `put('key', $value, 0)` of een negatieve TTL | `forget()` |
| `touch('key', 60)` | `touch()` |
| `touch('key', 0)` of een negatieve TTL | `forget()` |

> [!WARNING]
>
> De `ArrayStore` uit het voorbeeld slaat een item zonder vervaltijd op wanneer je rechtstreeks `0` seconden aan `put()` doorgeeft. `put(..., 0)` op de Repository betekent daarentegen verwijderen. Test je alleen de low-level Store die je met `getStore()` ophaalt, dan controleer je niet het gedrag van de API die gebruikers aanroepen.

### Controleer add, locks en tags afzonderlijk

Alleen het Store-contract implementeren garandeert niet dat alle cachefuncties worden ondersteund.

* **Atomische `add()`**: maakt geen deel uit van het Store-contract. Wordt hij aangeroepen met een positieve TTL en heeft de Store een `add()`, dan gebruikt de Repository die. Zo niet, dan valt hij terug op `get()` en `put()`, dus alleen inpakken met een Repository garandeert geen atomiciteit. Implementeer het voor een gedeelde backend met de atomische bewerkingen van die backend en verifieer ook gelijktijdige uitvoering.
* **Locks**: `lock()` en `restoreLock()` van `Illuminate\Contracts\Cache\LockProvider` vormen een apart contract. Verifieer verkrijgen, overeenkomst van de eigenaar, verlopen en dat locks van andere eigenaren niet worden vrijgegeven. De locks van de `ArrayStore` uit het voorbeeld bieden geen wederzijdse uitsluiting tussen verschillende processen.
* **Tags**: de Repository controleert of de Store `tags()` heeft. Doorgaans is een implementatie met bijvoorbeeld `TaggableStore` nodig; met alleen het Store-contract worden tags niet automatisch ingeschakeld.

Ga er niet van uit dat `flush()` een gedeeltelijke verwijdering is die de prefix uit de configuratie respecteert. Controleer wat er op een gedeelde backend wordt verwijderd en leg dat, inclusief `php artisan cache:clear`, uit aan gebruikers.

## Maak onderscheid tussen het vasthouden van instanties en het verwijderen van data

`CacheManager` is een singleton en houdt ook het resultaat van `store('catalog')` per storenaam vast. Alleen de configuratie wijzigen of `extend()` opnieuw registreren werkt een al aangemaakte Repository niet bij.

```php theme={null}
use Illuminate\Support\Facades\Cache;

$before = Cache::store('catalog');

config(['cache.stores.catalog.serialize' => false]);

$same = Cache::store('catalog');

Cache::forgetDriver('catalog');

$after = Cache::store('catalog');
```

`$same` is hetzelfde object als `$before`. Bij het ophalen na `forgetDriver()` wordt `$after` aangemaakt op basis van de nieuwe configuratie.

| Bewerking | Doel |
| - | - |
| `Cache::forgetDriver('catalog')` | Verwijdert de referentie naar de Repository die de Manager vasthoudt |
| `Cache::store('catalog')->forget('key')` | Verwijdert één opgeslagen key |
| `Cache::store('catalog')->flush()` | Verwijdert alle cachedata van de Store |

`forgetDriver()` verwijdert niet alle data in de backend. Omdat de `ArrayStore` uit het voorbeeld de data in de instantie bewaart, zijn oude gegevens echter niet zichtbaar vanuit een nieuwe Store. Verder worden oude Repository-referenties die worden vastgehouden door `$before` aan de gebruikende kant of door de al geresolvede `cache.store`-singleton niet vervangen.

Werk in productie de configuratiecache bij volgens je normale deployment en herstart langlopende workers. Houd ook rekening met de [levensduur van services](https://kawax.biz/nl/advanced/package-service-lifetimes.md) en behandel opnieuw resolven niet als een gelijktijdige update van alle processen.

## Test niet alleen facade-mocks, maar ook het aanmaakpad

Laat in de [testomgeving voor packages](https://kawax.biz/nl/advanced/package-testing.md) alles via de echte Manager en Repository lopen. Het volgende Testbench-voorbeeld controleert dat de driver ook bruikbaar is in de `boot()` van een andere provider die eerder is geregistreerd. In het voorbeeld wordt een array opgeslagen zonder de herstelbeperking van `cache.serializable_classes` te wijzigen, en wordt met `ArrayStore::all(false)` ook de ruwe opgeslagen waarde gecontroleerd.

```php theme={null}
<?php

namespace Acme\Memo\Tests;

use Acme\Memo\MemoServiceProvider;
use Illuminate\Cache\ArrayStore;
use Illuminate\Cache\Repository;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\ServiceProvider;
use Orchestra\Testbench\TestCase;

class CacheConsumerProvider extends ServiceProvider
{
    public function boot(): void
    {
        Cache::store('catalog')->forever('boot-marker', 'ready');
    }
}

class PackageCacheTest extends TestCase
{
    protected function getPackageProviders($app): array
    {
        return [CacheConsumerProvider::class, MemoServiceProvider::class];
    }

    protected function defineEnvironment($app): void
    {
        $app['config']->set('cache.default', 'array');
        $app['config']->set('cache.stores.catalog', [
            'driver' => 'acme-memory',
            'serialize' => true,
        ]);
    }

    public function test_the_driver_is_available_during_another_providers_boot(): void
    {
        $cache = Cache::store('catalog');

        $this->assertInstanceOf(Repository::class, $cache);
        $this->assertInstanceOf(ArrayStore::class, $cache->getStore());
        $this->assertSame('catalog', $cache->getName());
        $this->assertSame('ready', $cache->get('boot-marker'));
        $this->assertNotNull($cache->getEventDispatcher());
    }

    public function test_serialization_is_applied_from_the_store_configuration(): void
    {
        $cache = Cache::store('catalog');
        $value = ['status' => 'queued'];

        $cache->put('job', $value, 60);

        $this->assertSame($value, $cache->get('job'));
        $this->assertSame(serialize($value), $cache->getStore()->all(false)['job']['value']);
    }

    public function test_a_zero_ttl_deletes_the_item_through_the_repository(): void
    {
        $cache = Cache::store('catalog');
        $cache->forever('product:42', 'old');

        $this->assertTrue($cache->put('product:42', 'new', 0));
        $this->assertNull($cache->get('product:42'));
    }

    public function test_forgetting_a_driver_rebuilds_only_the_manager_reference(): void
    {
        $before = Cache::store('catalog');

        config(['cache.stores.catalog.serialize' => false]);
        $this->assertSame($before, Cache::store('catalog'));

        Cache::forgetDriver('catalog');
        $after = Cache::store('catalog');

        $this->assertNotSame($before, $after);
        $this->assertSame('ready', $before->get('boot-marker'));
        $this->assertNull($after->get('boot-marker'));

        $value = ['status' => 'queued'];
        $after->forever('job', $value);

        $this->assertSame($value, $after->get('job'));
        $this->assertSame($value, $after->getStore()->all(false)['job']['value']);
    }
}
```

Verifieer voor een eigen backend daarnaast verlopen items, niet-gevonden keys in `many()`, het heen en terug opslaan van `false` en `0`, `touch()`, verbindingsstoringen, gelijktijdige updates, en de prefix en wat er wordt verwijderd. Beschouw het slagen van een simpele `Cache::shouldReceive('get')` niet als bewijs van driverregistratie of compatibiliteit met een externe service.

## Checklist voor langdurig onderhoud

* Maak de drivernaam, de storeconfiguratie, het serialisatieformaat en de exceptions en returnwaarden bij mislukking onderdeel van de publieke specificatie.
* Controleer per ondersteunde Laravel-versie de methoden van het Store-contract en optionele functies zoals tags en locks.
* Verifieer dat registratie ook werkt bij een verse start met configuratiecache en dat de driver vanuit de `boot()` van andere providers bruikbaar is.
* Kondig compatibiliteitswijzigingen in opgeslagen data aan, inclusief het beleid voor het migreren of weggooien van bestaande keys.
* Maak onderscheid tussen het vasthouden van instanties binnen een proces, de data in de backend en de configuratiecache.

## Geraadpleegde primaire bronnen

* [Officiële Laravel-documentatie: eigen cachedrivers](https://github.com/laravel/docs/blob/960eedf0911155ad16326ade49c0ea5d18eda0b8/cache.md#adding-custom-cache-drivers)
* [CacheManager: build, repository, extend en forgetDriver](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Cache/CacheManager.php)
* [Repository: TTL-conversie, add, tags en events](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Cache/Repository.php)
* [Store-contract](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Contracts/Cache/Store.php) / [LockProvider-contract](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Contracts/Cache/LockProvider.php)
* [ArrayStore: opslaan, vervaltijd en serialisatie](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Cache/ArrayStore.php)
* [CacheServiceProvider: singletonregistratie van de Manager en cache.store](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Cache/CacheServiceProvider.php)
* [Application: booting van de applicatie en boot van providers](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Foundation/Application.php)
* [Officiële Laravel-tests: CacheManagerTest](https://github.com/laravel/framework/blob/v13.39.0/tests/Cache/CacheManagerTest.php) / [CacheRepositoryTest](https://github.com/laravel/framework/blob/v13.39.0/tests/Cache/CacheRepositoryTest.php)


## Related topics

- [Geavanceerde onderwerpen](https://kawax.biz/nl/advanced/index.md)
- [Cache](https://kawax.biz/nl/cache.md)
- [Core-package en eigen drivers - Feedable](https://kawax.biz/nl/packages/feedable/core.md)
- [Logchannels voor packages uitbreiden en opnieuw resolven](https://kawax.biz/nl/advanced/package-logging.md)
- [Laravel Package Skeleton — officiële startertemplate voor packages](https://kawax.biz/nl/blog/package-skeleton-introduction.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.