VOOZH about

URL: https://deepwiki.com/hypervel/cache/2.3-store-contracts

⇱ Store Contracts | hypervel/cache | DeepWiki


Loading...
Menu

Store Contracts

This page documents the core interface contracts that define the behavior of cache stores in the hypervel/cache package. It covers the Store interface, which all cache implementations must implement, and related interfaces including LockProvider and RefreshableLock. Understanding these contracts is essential for implementing custom cache drivers or understanding how the system achieves polymorphic behavior across different storage backends.

For information about specific store implementations, see Cache Stores. For details on how stores are instantiated and managed, see Cache Manager.

Overview of Store Contracts

The cache system uses interface contracts to define the behaviors that all cache stores must support. This design enables the system to treat different storage backends (Redis, Database, Filesystem, etc.) uniformly through a common API while allowing each implementation to optimize for its specific storage characteristics.

The contract hierarchy consists of three main interfaces:

ContractPurposeKey Implementations
StoreCore cache operations (get, put, forget, etc.)All store classes
LockProviderDistributed locking capabilitiesArrayStore, FileStore, RedisStore, DatabaseStore
RefreshableLockLock renewal for long-running operationsArrayLock, RedisLock, DatabaseLock

Sources: src/Contracts/Store.php1-60

The Store Interface

The Store interface defines the fundamental contract that all cache implementations must fulfill. This interface ensures that every cache backend provides a consistent API for storing, retrieving, and managing cached data.

Store Interface Structure


Sources: src/Contracts/Store.php7-59 src/StackStore.php11 src/StackStoreProxy.php10

Core Store Methods

The Store interface defines ten methods that cache implementations must provide:

Data Retrieval Methods

MethodSignaturePurposeReturn Value
get()get(string $key): mixedRetrieve a single item by keyThe cached value or null if not found
many()many(array $keys): arrayRetrieve multiple items by keysAssociative array with keys and values (null for missing items)

The retrieval methods are responsible for fetching data from the cache. If an item doesn't exist or has expired, they return null.

Sources: src/Contracts/Store.php10-18

Data Storage Methods

MethodSignaturePurposeReturn Value
put()put(string $key, mixed $value, int $seconds): boolStore an item with TTLSuccess boolean
putMany()putMany(array $values, int $seconds): boolStore multiple items with TTLSuccess boolean
forever()forever(string $key, mixed $value): boolStore an item indefinitelySuccess boolean

Storage methods persist data to the cache backend. The $seconds parameter defines the time-to-live (TTL) in seconds. The forever() method stores data without expiration, though the underlying implementation may still impose limits.

Sources: src/Contracts/Store.php20-43

Atomic Counter Methods

MethodSignaturePurposeReturn Value
increment()increment(string $key, int $value = 1): bool|intAtomically increment a numeric valueNew value on success, false on failure
decrement()decrement(string $key, int $value = 1): bool|intAtomically decrement a numeric valueNew value on success, false on failure

Counter methods provide atomic numeric operations. If the key doesn't exist, implementations typically initialize it to the increment/decrement value. These operations are critical for use cases like rate limiting and statistics tracking.

Sources: src/Contracts/Store.php31-38

Data Removal Methods

MethodSignaturePurposeReturn Value
forget()forget(string $key): boolRemove a specific itemSuccess boolean
flush()flush(): boolRemove all items from cacheSuccess boolean

Removal methods delete data from the cache. The forget() method removes a single item, while flush() clears the entire cache store. Both return true on success.

Sources: src/Contracts/Store.php45-53

Utility Methods

MethodSignaturePurposeReturn Value
getPrefix()getPrefix(): stringGet the cache key prefixThe prefix string

The prefix method returns the key prefix used by the store to namespace cache entries. This is useful for multi-tenant applications or avoiding key collisions between different applications sharing the same backend.

Sources: src/Contracts/Store.php56-58

Store Implementation Contract Flow


Sources: src/CacheManager.php56-61 src/CacheManager.php172-191 src/CacheManager.php233-244

LockProvider Interface

The LockProvider interface extends cache stores with distributed locking capabilities. This contract defines methods for acquiring and restoring locks, enabling coordination between concurrent processes.

LockProvider Contract

Stores that implement the LockProvider interface provide two key methods:

MethodPurposeReturn Type
lock(string $name, int $seconds = 0, string|null $owner = null)Create a new lock instanceLock
restoreLock(string $name, string $owner)Restore an existing lock by its owner tokenLock

The lock() method creates a new lock instance with an optional timeout. The $owner parameter identifies the lock holder, typically generated as a unique token. The restoreLock() method allows a process to regain control of a lock it previously held.

Lock Provider Implementations


Each store implementation creates lock instances appropriate to its storage backend. For detailed information about specific lock implementations and their behavior, see Distributed Locking.

Sources: Referenced from architectural context; specific implementations not included in provided files.

RefreshableLock Interface

The RefreshableLock interface extends the base Lock interface with capabilities for refreshing lock TTL and inspecting remaining lifetime. This interface is specifically designed for locks that can atomically refresh their expiration, enabling long-running operations to maintain exclusive access.

RefreshableLock Contract Structure


Sources: src/Contracts/RefreshableLock.php16

RefreshableLock Methods

The interface defines two methods for managing lock lifetime:

MethodSignaturePurposeReturn Value
refresh()refresh(?int $seconds = null): boolAtomically refresh the lock's TTL if still ownedtrue if refreshed, false if not owned or expired
getRemainingLifetime()getRemainingLifetime(): ?floatGet seconds until lock expiresSeconds remaining, or null if no expiry or lock doesn't exist

Sources: src/Contracts/RefreshableLock.php33 src/Contracts/RefreshableLock.php40

Atomic Refresh Behavior

The refresh() method provides atomic TTL renewal with the following characteristics:

  • Atomicity: The refresh operation is atomic - if the lock has been released or acquired by another process, the method returns false without modifying anything
  • Optional TTL Parameter: When called without arguments (refresh()), it uses the original TTL from lock construction. When called with a specific value (refresh(60)), it sets the TTL to that value
  • Permanent Lock Handling: When called on a permanent lock (acquired with TTL of 0), it becomes a no-op that returns true since there's no TTL to refresh
  • Validation: Throws InvalidArgumentException if $seconds is explicitly provided and is not positive

Sources: src/Contracts/RefreshableLock.php18-33

Implementation Requirements

The RefreshableLock interface documentation explicitly states:

Not all lock drivers can implement this interface atomically. Drivers that cannot guarantee atomic refresh operations (like CacheLock) should not implement this interface.

This design constraint ensures that only locks with true atomic refresh capabilities implement the interface, preventing race conditions in lock management.

Sources: src/Contracts/RefreshableLock.php9-15

Refreshable Lock Implementations

The following table shows which lock implementations support the RefreshableLock interface:

Lock TypeImplements RefreshableLockAtomic Mechanism
RedisLock✓ YesLua scripts ensure atomic refresh via EVAL commands
DatabaseLock✓ YesSQL WHERE clauses verify ownership during UPDATE
ArrayLock✓ YesIn-memory operations are inherently atomic
FileLock✗ NoFile-based locks have no expiration concept
NoLock✗ NoNo-op implementation has no state to refresh

Use Cases for Refreshable Locks

Refreshable locks are essential for scenarios requiring extended exclusive access:

  • Long-running batch jobs: Jobs that process large datasets and need to maintain the lock throughout execution
  • Queue workers: Workers processing jobs that may exceed initial timeout estimates, refreshing periodically
  • Maintenance operations: Database migrations or cleanup tasks with unpredictable durations
  • Distributed coordination: Tasks requiring sustained coordination across multiple servers

Remaining Lifetime Inspection

The getRemainingLifetime() method enables monitoring of lock expiration:


The method returns null in two scenarios:

  • The lock doesn't exist (never acquired or already released)
  • The lock has no expiry (permanent lock)

Sources: src/Contracts/RefreshableLock.php36-40

Polymorphism Through Contracts

The store contracts enable polymorphic behavior throughout the cache system. Different storage backends can be used interchangeably because they all conform to the same interface contract.

CacheManager Driver Resolution

The CacheManager demonstrates contract-based polymorphism by instantiating different store implementations based on configuration while returning them through the uniform Store contract:


All driver creation methods in CacheManager return implementations of the Store contract. This allows application code to work with any cache backend without knowing the specific implementation:

Sources: src/CacheManager.php172-191 src/CacheManager.php204-300

StackStore Composition Pattern

The StackStore implementation exemplifies how the Store contract enables the composite pattern. A StackStore contains multiple other stores and delegates operations to them:


The StackStore can compose any combination of store implementations because they all implement the Store contract:

This composition is enabled by the fact that StackStore itself implements Store, allowing recursive nesting if needed.

Sources: src/StackStore.php11-18 src/StackStore.php20-92

StackStoreProxy Decorator Pattern

The StackStoreProxy class demonstrates the decorator pattern using the Store contract. It wraps another store and modifies its behavior, particularly around TTL enforcement:


The proxy pattern works because:

This allows StackStoreProxy to modify behavior transparently while maintaining the same interface contract.

Sources: src/StackStoreProxy.php10-82

Contract Extension Points

The store contracts provide several extension points for custom implementations:

Implementing a Custom Store

To create a custom cache store:

  1. Implement the Store interface src/Contracts/Store.php7-59
  2. Implement all ten required methods
  3. Optionally implement LockProvider for distributed locking support
  4. Register the custom driver with CacheManager::extend() src/CacheManager.php142-147

Method Implementation Requirements

AspectRequirementRationale
Return typesMust match interface signatures exactlyType safety and contract compliance
Null handlingReturn null for missing keys in get()Consistent behavior across all stores
TTL interpretation$seconds parameter is absolute secondsPrevents confusion with relative time formats
Boolean returnsReturn true on success, false on failureClear success/failure indication
Atomic operationsincrement()/decrement() should be atomicPrevent race conditions

Store Implementation Patterns

Common patterns observed in store implementations:

  • Serialization: Most stores serialize values before storage and deserialize on retrieval
  • Key prefixing: Stores use prefixes to namespace cache entries
  • Expiration handling: Each store manages expiration differently (database columns, Redis TTL, file timestamps)
  • Connection management: Distributed stores manage connections to external services
  • Error handling: Stores return false on failures rather than throwing exceptions

Sources: src/Contracts/Store.php1-60 src/CacheManager.php142-147