Saltar al contenido principal
Para ofrecer un backend de caché propio desde un paquete, separa el Store, que implementa el almacenamiento, del Repository, que ofrece la API de Laravel. Lo que se registra con Cache::extend() es el método de creación, y Laravel no envuelve automáticamente en un Repository el Store que devuelvas. Esta página se basa en la revisión de la implementación de Laravel Framework v13.39.0. Para las operaciones básicas, consulta Caché, y para preparar el provider, Desarrollo de paquetes.

Separa el nombre del driver del nombre del store

Si existe un creador personalizado para el driver de la configuración, CacheManager::build() lo llama antes que a los drivers integrados. No reutilices nombres como redis o array; usa un nombre propio del paquete.

Regístralo antes del boot de otros providers

Si otro paquete usa la caché en su boot(), registrar el driver en tu propio boot() puede llegar tarde según el orden. Siguiendo la documentación oficial, registra en register() un callback booting de la aplicación. El siguiente es un ejemplo mínimo que usa el ArrayStore estándar para comprobar el flujo de registro sin servicios externos. Colócalo en src/MemoServiceProvider.php del paquete Acme\Memo.
El provider se carga, por ejemplo, mediante el descubrimiento automático de paquetes. Como este provider solo registra la extensión, no lo conviertas en un provider diferido, para que se cargue tanto en HTTP como en Artisan.
  • El booting de la aplicación se ejecuta, en un arranque normal, después de que terminen los register() de todos los providers y antes de cada boot(). Es distinto del $this->booting() del propio provider.
  • El callback de extend() recibe la aplicación y el array de configuración del store. Coloca la generación del Store y las conexiones externas en este callback de creación.
  • Este momento de registro no garantiza el uso de la caché dentro del register() de otros providers. Separa el registro de servicios de su uso real.
Si solo necesitas la caché en memoria estándar, basta con el driver array. Este ejemplo sirve para explicar el registro y los contratos, y no sirve para compartir ni persistir datos entre procesos. En un backend propio, sustituye la parte que genera el ArrayStore por una clase que implemente Illuminate\Contracts\Cache\Store.
CacheManager::extend() vuelve a enlazar el ámbito del closure anónimo al Manager. No asumas que $this en un closure no estático es el provider; usa, como en el ejemplo, un closure estático y el argumento $app.

Añade el store a la aplicación que usa el paquete

Añádelo al array stores existente de config/cache.php. No diseñes el paquete para que sobrescriba el default ni todo el array stores de la aplicación.
Si quieres usarlo como predeterminado, lo que se indica en CACHE_STORE es también el nombre del store, catalog, no el nombre del driver. Si creas varias configuraciones con el mismo driver, se genera un Repository distinto para cada nombre de store.

Construye el Repository tú mismo

CacheManager::callCustomCreator() devuelve tal cual el valor de retorno del callback. Si devuelves solo el Store, aunque get() y put() funcionen, no podrás usar remember() y otros métodos del Repository, y la interpretación del TTL también cambia. El repository($store, $config) del ejemplo se encarga de lo siguiente:
  • Envuelve el Store con Illuminate\Cache\Repository.
  • Pasa al Repository el nombre store que el Manager añadió a la configuración.
  • Si hay un Dispatcher de eventos registrado y la opción events no está desactivada, asigna el Dispatcher al Repository.
No es un proceso que aplique toda tu configuración propia al Store. El destino de conexión, el prefix, la serialización, etc., se reflejan en la implementación del Store y en el proceso de creación. Lo mismo ocurre con serialize, que el ejemplo lee de la configuración.

Contrasta el contrato Store con la implementación actual

Implementa los métodos que exige Illuminate\Contracts\Cache\Store en Laravel 13. No distribuyas como driver funcional una lista de métodos vacíos copiada de ejemplos antiguos. El Repository trata null como un fallo de caché. Implementa el Store de forma que false o 0 no se traten como inexistentes. Guardar null no sirve como marca para evitar que remember() vuelva a calcular el valor.

Verifica el TTL a través del Repository

Repository::put() procesa el TTL antes de pasarlo al Store.
El ArrayStore del ejemplo guarda sin expiración si se pasan 0 segundos directamente a put(). En cambio, put(..., 0) del Repository elimina el elemento. Probar solo el Store de bajo nivel obtenido con getStore() no confirma el comportamiento de la API que llaman los usuarios.

Comprueba por separado add, locks y etiquetas

Implementar el contrato Store no garantiza por sí solo la compatibilidad con todas las funciones de la caché.
  • add() atómico: no forma parte del contrato Store. Si se llama con un TTL positivo y el Store tiene add(), el Repository lo usa. Si no, recurre a get() y put(), así que envolverlo en un Repository no garantiza la atomicidad. En backends compartidos, impleméntalo con las operaciones atómicas de ese backend y verifica también la ejecución concurrente.
  • Locks: lock() y restoreLock() de Illuminate\Contracts\Cache\LockProvider son un contrato aparte. Verifica la adquisición, la coincidencia del propietario, la expiración y que no se liberen locks de otros propietarios. Los locks del ArrayStore del ejemplo no proporcionan exclusión mutua entre procesos distintos.
  • Etiquetas: el Repository comprueba si el Store tiene tags(). Por lo general se necesita una implementación basada en TaggableStore u otra similar, y no se activan automáticamente solo con el contrato Store.
No asumas que flush() es un borrado parcial que respeta el prefix de la configuración. Comprueba el alcance del borrado en backends compartidos y explícalo a los usuarios, incluido php artisan cache:clear.

Distingue la retención de instancias del borrado de datos

CacheManager es un singleton, y también retiene el resultado de store('catalog') por nombre de store. Cambiar la configuración o volver a registrar extend() no actualiza los Repository ya generados.
$same es el mismo objeto que $before. Al obtenerlo después de forgetDriver(), $after se genera a partir de la nueva configuración. forgetDriver() no borra todo el backend. Sin embargo, como el ArrayStore del ejemplo guarda los datos dentro de la instancia, el nuevo Store no ve los datos antiguos. Además, no se sustituyen el $before del código que lo usa ni las referencias al Repository antiguo que retiene el singleton cache.store ya resuelto. En producción, actualiza la caché de configuración siguiendo el despliegue habitual y reinicia los workers de larga duración. Ten en cuenta también el ciclo de vida de los servicios, y no trates la nueva resolución como una actualización simultánea de todos los procesos.

Prueba el flujo de creación, no solo mocks de facades

En el entorno de pruebas del paquete, pasa por el Manager y el Repository reales. El siguiente ejemplo de Testbench comprueba que el driver también puede usarse desde el boot() de otro provider registrado antes. En el ejemplo se guarda un array sin cambiar la restricción de restauración de cache.serializable_classes, y se comprueba también el valor guardado en bruto con ArrayStore::all(false).
En un backend propio, verifica además la expiración, las claves inexistentes en many(), el ida y vuelta de false y 0, touch(), los fallos de conexión, las actualizaciones concurrentes, y el prefix y el alcance del borrado. Que un simple Cache::shouldReceive('get') tenga éxito no es prueba del registro del driver ni de la compatibilidad con servicios externos.

Lista de comprobación para el mantenimiento a largo plazo

  • Haz que el nombre del driver, la configuración del store, el formato de serialización, las excepciones y los valores de retorno en caso de fallo formen parte de la especificación pública.
  • Comprueba los métodos del contrato Store y las funciones opcionales, como etiquetas y locks, para cada versión de Laravel compatible.
  • Verifica que el driver se registra incluso en un arranque nuevo con la caché de configuración activa, y que puede usarse desde el boot() de otros providers.
  • Al anunciar cambios de compatibilidad en los datos guardados, incluye la política de migración o descarte de las claves existentes.
  • Distingue la retención de instancias dentro del proceso, los datos del backend y la caché de configuración.

Fuentes primarias consultadas

Última modificación el 29 de octubre de 2026