Kwalitee Tweet

NAME

Class::Simple::Cached - cache getter results for any get/set object

VERSION

Version 0.07

SYNOPSIS

use CHI;
use Class::Simple::Cached;

# Wrap an existing object with a cache layer
my $cache = CHI->new(driver => 'RawMemory', global => 1);
my $obj   = Class::Simple::Cached->new(
    cache  => $cache,
    object => My::Expensive::Object->new(),
);

$obj->name('Alice');      # setter: delegates to wrapped object, updates cache
my $n = $obj->name();     # getter: returns cached 'Alice' without hitting object

# Or use a plain hash ref as the cache backend
my %store;
my $simple = Class::Simple::Cached->new(cache => \%store);
$simple->colour('blue');
print $simple->colour();  # 'blue', served from %store

DESCRIPTION

A transparent caching wrapper for any Class::Simple-compatible get/set object. Repeated getter calls hit the cache instead of the wrapped object, avoiding expensive recomputation or remote round-trips.

Cache coherency is not automatic. If the wrapped object's state changes through a path other than the cached wrapper, callers must invalidate the cache themselves.

SUBROUTINES/METHODS

new

Constructs a Class::Simple::Cached instance that wraps object behind cache.

ARGUMENTS

Calling ->new() on an already-blessed instance returns a shallow clone (all stored fields are merged, including the existing cache handle).

RETURNS

A blessed Class::Simple::Cached instance.

SIDE EFFECTS

None beyond allocating the new instance.

EXAMPLE

# CHI-backed cache
use CHI;
my $obj = Class::Simple::Cached->new(
    cache  => CHI->new(driver => 'RawMemory', global => 1),
    object => My::Model->new(),
);

# Hash-ref cache (useful for tests or short-lived objects)
my $obj = Class::Simple::Cached->new(cache => {});

# Clone an existing wrapped object
my $clone = $obj->new();

API SPECIFICATION

Input:
  cache  => HashRef | CHICompatibleObject  # required
  object => Object                          # optional

Output:
  Class::Simple::Cached instance

MESSAGES

Message                                              Meaning                              Resolution
---------------------------------------------------  -----------------------------------  ----------------------------------------
"use ->new() not ::new() to instantiate"             Called as Class::Simple::Cached::new  Use $obj->new() or ClassName->new()
"Usage: $class->new(cache => \$cache)"               No arguments supplied                 Pass at least cache => ...
"Cache must be ref to HASH or object"                cache is a plain scalar or wrong ref  Use a hashref or CHI-compatible object
"Cache object must implement get, set, purge"        Blessed cache lacks required methods  Use a fully CHI-compatible object

PSEUDOCODE

new(class, args):
  IF class undefined   → carp and return undef
  IF class is blessed  → merge fields, return shallow clone
  IF no args           → croak Usage message
  PARSE args into hashref via Params::Get
  IF params.object absent → params.object = Class::Simple->new()
  IF params.cache is a blessed object:
    VERIFY it can('get') AND can('set') AND can('purge')
    IF not → croak capability message
    RETURN bless params, class
  IF params.cache is a HASH ref:
    RETURN bless params, class
  croak "Cache must be ref to HASH or object"

can

Reports whether this wrapper (or its embedded object) can handle a method.

ARGUMENTS

RETURNS

True if the method is known; false otherwise.

EXAMPLE

$obj->can('name');   # true if the wrapped object has a name() method

API SPECIFICATION

Input:  method_name : Str
Output: Bool

MESSAGES

None.

isa

Reports whether this wrapper or its embedded object is of a given class.

ARGUMENTS

RETURNS

True if the wrapper or the wrapped object is-a $class.

EXAMPLE

$obj->isa('My::Model');   # delegates to the wrapped object

API SPECIFICATION

Input:  class_name : Str
Output: Bool

MESSAGES

None.

AUTOLOAD (getter/setter proxy)

Intercepts every method call that is not explicitly defined, proxying it to the wrapped object with a caching layer for zero-argument (getter) calls.

Setter calls (one or more arguments) always pass through to the wrapped object and update the cache with the new value.

ARGUMENTS

$method()        — getter: returns cached value if present, else calls object
$method($scalar) — scalar setter: stores scalar, updates cache
$method(@list)   — array setter: stores list, updates cache

RETURNS

The value returned by the wrapped object (or the cached copy thereof).

SIDE EFFECTS

EXAMPLE

$obj->colour('red');    # setter — writes 'red' to object and cache
$obj->colour();         # getter — returns 'red' from cache

API SPECIFICATION

Input (getter):  method_name : Str,  args : ()
Input (setter):  method_name : Str,  args : (Scalar | List)
Output:          the stored/retrieved value or list

MESSAGES

Message                           Meaning                             Resolution
--------------------------------  ----------------------------------  ----------------------------------------
"$method" (croak)                 Cached array's first element is     Do not store the sentinel string
                                  the UNDEF_SENTINEL string           as a real value in the wrapped object

PSEUDOCODE

AUTOLOAD(method, args...):
  key = ref(self) + ":" + method

  IF no args (getter mode):
    val = cache_get(key)
    IF cache hit:
      IF val is a plain string (not a ref):
        IF val is UNDEF_SENTINEL → return undef
        RETURN val
      IF val is an arrayref:
        IF first element is a plain string AND equals UNDEF_SENTINEL → croak
        RETURN dereferenced list
      RETURN val (blessed object)
    # Cache miss — ask the wrapped object
    IF list context:
      result_list = object->method()
      IF empty      → return ()
      cache_set(key, \result_list)
      RETURN result_list
    # Scalar context
    result = object->method()
    IF defined:
      cache_set(key, result)
      RETURN result
    cache_set(key, UNDEF_SENTINEL)
    RETURN undef

  ELSE (setter mode):
    IF more than one arg (array setter):
      val = object->method(\@args)     # wrapped object stores arrayref
      IF defined:
        cache_set(key, val)
        RETURN @val
      cache_set(key, UNDEF_SENTINEL)
      RETURN undef
    ELSE (scalar setter):
      val = object->method(args[0])
      cache_set(key, val // UNDEF_SENTINEL)
      RETURN val

LIMITATIONS

AUTHOR

Nigel Horne, <njh at nigelhorne.com>

BUGS

Please report bugs and feature requests at https://github.com/nigelhorne/Class-Simple-Cached/issues.

SEE ALSO

Class::Simple, CHI, Class::Simple::Readonly::Cached

SUPPORT

This module is provided as-is without any warranty.

You can find documentation for this module with the perldoc command:

perldoc Class::Simple::Cached

FORMAL SPECIFICATION

new

─────────────────────────────────────────────────────────────────
[State]
  cache  : ℙ(HashRef ∪ CHIObject)
  object : Object

[CHIObject]
  can_get   : Method
  can_set   : Method
  can_purge : Method

new ──────────────────────────────────────────────────────────────
Δ(cache, object)
cache? : HashRef ∪ CHIObject
object? : Object ∪ {∅}
─────────────────────────────────────────────────
cache? ≠ ∅
cache ′ = cache?
object ′ = (object? ≠ ∅ ⟹ object?) ∨ Class::Simple.new()
─────────────────────────────────────────────────

can

can ──────────────────────────────────────────────────
Ξ(cache, object)
method? : MethodName
─────────────────────────────────────────────────
result! = (method? = 'new')
        ∨ object.can(method?)
        ∨ SUPER::can(method?)

isa

isa ──────────────────────────────────────────────────
Ξ(cache, object)
class? : ClassName
─────────────────────────────────────────────────
result! = (class? = ref(self))
        ∨ (class? = __PACKAGE__)
        ∨ SUPER::isa(class?)
        ∨ object.isa(class?)

AUTOLOAD

AUTOLOAD ─────────────────────────────────────────────────────────
Δ(cache)
method? : MethodName
args?   : Seq(Any)
─────────────────────────────────────────────────
key = ref(self) ⊕ ":" ⊕ method?

Getter (args? = ∅):
  (∃ v • cache_hit(key, v) ∧ v ≠ UNDEF_SENTINEL ⟹ result! = v)
  ∨ (cache_hit(key, UNDEF_SENTINEL)              ⟹ result! = undef)
  ∨ (¬cache_hit(key, _)
      ∧ result! = object.method?()
      ∧ cache′ = cache ∪ {key ↦ encode(result!)})

Setter (args? ≠ ∅):
  object′.method?(args?) = args?
  cache′ = cache ∪ {key ↦ encode(object′.method?())}
  result! = args?

LICENCE AND COPYRIGHT

Copyright (C) 2019-2026, Nigel Horne

Usage is subject to the GPL2 licence terms. If you use it, please let me know.