lib/Class/Simple/Cached.pm

Structural Coverage (Approximate)

TER1 (Statement): 100.00%
TER2 (Branch): 96.67%
TER3 (LCSAJ): 100.0% (9/9)
Approximate LCSAJ segments: 61

LCSAJ Legend

● Covered — this LCSAJ path was executed during testing.

● Not covered — this LCSAJ path was never executed. These are the paths to focus on.

Multiple dots on a line indicate that multiple control-flow paths begin at that line. Hovering over any dot shows:

        start → end → jump
        

Uncovered paths show [NOT COVERED] in the tooltip.

Mutant Testing Legend

Survived (tests missed this) Killed (tests detected this) No mutation
    1: package Class::Simple::Cached;
    2: 
    3: use strict;
    4: use warnings;
    5: 
    6: use Carp ();
    7: use Class::Simple;
    8: use Params::Get 0.15;
    9: use Scalar::Util ();
   10: use Sub::Protected;
   11: 
   12: # Stored in the cache to distinguish "the object returned undef" from
   13: # "this key has never been cached".  Must never be a legitimate return value.
   14: use constant UNDEF_SENTINEL => __PACKAGE__ . '>UNDEF<';
   15: 
   16: # Evaluated once at compile time so DESTROY never pays a string comparison
   17: # on every object teardown just to decide whether ${^GLOBAL_PHASE} exists.
   18: use constant _GLOBAL_PHASE_AVAILABLE => defined($^V) && ($^V ge 'v5.14.0');
   19: 
   20: =head1 NAME
   21: 
   22: Class::Simple::Cached - cache getter results for any get/set object
   23: 
   24: =head1 VERSION
   25: 
   26: Version 0.07
   27: 
   28: =cut
   29: 
   30: our $VERSION = '0.07';
   31: 
   32: =encoding UTF-8
   33: 
   34: =head1 SYNOPSIS
   35: 
   36:     use CHI;
   37:     use Class::Simple::Cached;
   38: 
   39:     # Wrap an existing object with a cache layer
   40:     my $cache = CHI->new(driver => 'RawMemory', global => 1);
   41:     my $obj   = Class::Simple::Cached->new(
   42:         cache  => $cache,
   43:         object => My::Expensive::Object->new(),
   44:     );
   45: 
   46:     $obj->name('Alice');      # setter: delegates to wrapped object, updates cache
   47:     my $n = $obj->name();     # getter: returns cached 'Alice' without hitting object
   48: 
   49:     # Or use a plain hash ref as the cache backend
   50:     my %store;
   51:     my $simple = Class::Simple::Cached->new(cache => \%store);
   52:     $simple->colour('blue');
   53:     print $simple->colour();  # 'blue', served from %store
   54: 
   55: =head1 DESCRIPTION
   56: 
   57: A transparent caching wrapper for any L<Class::Simple>-compatible get/set object.
   58: Repeated getter calls hit the cache instead of the wrapped object, avoiding
   59: expensive recomputation or remote round-trips.
   60: 
   61: Cache coherency is I<not> automatic.  If the wrapped object's state changes
   62: through a path other than the cached wrapper, callers must invalidate the cache
   63: themselves.
   64: 
   65: =head1 SUBROUTINES/METHODS
   66: 
   67: =head2 new
   68: 
   69: Constructs a C<Class::Simple::Cached> instance that wraps C<object> behind
   70: C<cache>.
   71: 
   72: =head3 ARGUMENTS
   73: 
   74: =over 4
   75: 
   76: =item C<cache> (mandatory)
   77: 
   78: Either a blessed object implementing C<get($key)>, C<set($key, $val, $expires)>,
   79: and C<purge()> (e.g. any L<CHI> driver); or a plain hash reference used as an
   80: in-process store.
   81: 
   82: =item C<object> (optional)
   83: 
   84: The object whose methods will be proxied and cached.  Defaults to a fresh
   85: C<Class::Simple> instance.
   86: 
   87: =back
   88: 
   89: Calling C<< ->new() >> on an already-blessed instance returns a shallow clone
   90: (all stored fields are merged, including the existing cache handle).
   91: 
   92: =head3 RETURNS
   93: 
   94: A blessed C<Class::Simple::Cached> instance.
   95: 
   96: =head3 SIDE EFFECTS
   97: 
   98: None beyond allocating the new instance.
   99: 
  100: =head3 EXAMPLE
  101: 
  102:     # CHI-backed cache
  103:     use CHI;
  104:     my $obj = Class::Simple::Cached->new(
  105:         cache  => CHI->new(driver => 'RawMemory', global => 1),
  106:         object => My::Model->new(),
  107:     );
  108: 
  109:     # Hash-ref cache (useful for tests or short-lived objects)
  110:     my $obj = Class::Simple::Cached->new(cache => {});
  111: 
  112:     # Clone an existing wrapped object
  113:     my $clone = $obj->new();
  114: 
  115: =head3 API SPECIFICATION
  116: 
  117:     Input:
  118:       cache  => HashRef | CHICompatibleObject  # required
  119:       object => Object                          # optional
  120: 
  121:     Output:
  122:       Class::Simple::Cached instance
  123: 
  124: =head3 MESSAGES
  125: 
  126:     Message                                              Meaning                              Resolution
  127:     ---------------------------------------------------  -----------------------------------  ----------------------------------------
  128:     "use ->new() not ::new() to instantiate"             Called as Class::Simple::Cached::new  Use $obj->new() or ClassName->new()
  129:     "Usage: $class->new(cache => \$cache)"               No arguments supplied                 Pass at least cache => ...
  130:     "Cache must be ref to HASH or object"                cache is a plain scalar or wrong ref  Use a hashref or CHI-compatible object
  131:     "Cache object must implement get, set, purge"        Blessed cache lacks required methods  Use a fully CHI-compatible object
  132: 
  133: =head3 PSEUDOCODE
  134: 
  135:     new(class, args):
  136:       IF class undefined   → carp and return undef
  137:       IF class is blessed  → merge fields, return shallow clone
  138:       IF no args           → croak Usage message
  139:       PARSE args into hashref via Params::Get
  140:       IF params.object absent → params.object = Class::Simple->new()
  141:       IF params.cache is a blessed object:
  142:         VERIFY it can('get') AND can('set') AND can('purge')
  143:         IF not → croak capability message
  144:         RETURN bless params, class
  145:       IF params.cache is a HASH ref:
  146:         RETURN bless params, class
  147:       croak "Cache must be ref to HASH or object"
  148: 
  149: =cut
  150: 
  151: sub new
  152: {
●153 → 156 → 162  153: 	my $class = shift;
  154: 
  155: 	# Guard: always call as a method, not a bare function
  156: 	if(!defined($class)) {

Mutants (Total: 1, Killed: 1, Survived: 0)

157: Carp::carp(__PACKAGE__, ' use ->new() not ::new() to instantiate'); 158: return; 159: } 160: 161: # When called on a blessed instance, return a shallow clone ●162 → 162 → 191 162: if(Scalar::Util::blessed($class)) {

Mutants (Total: 1, Killed: 1, Survived: 0)

163: my $params = Params::Get::get_params(undef, \@_) || {}; 164: my %merged = (%{$class}, %{$params}); 165: # Always recalculate internal dispatch fields after the merge so that 166: # caller-supplied _is_hash_cache or _cache_prefix args cannot corrupt 167: # the cache-type flag or the key-prefix (internal field injection). 168: $merged{'_is_hash_cache'} = ref($merged{'cache'}) eq 'HASH'; 169: $merged{'_cache_prefix'} = ref($class) . ':'; 170: 171: # Validate the (potentially-replaced) cache object using the same rules 172: # as new() — the clone path must not produce an unusable object. 173: if(!$merged{'_is_hash_cache'}) {

Mutants (Total: 1, Killed: 1, Survived: 0)

174: if(Scalar::Util::blessed($merged{'cache'})) {

Mutants (Total: 1, Killed: 1, Survived: 0)

175: unless($merged{'cache'}->can('get')

Mutants (Total: 1, Killed: 1, Survived: 0)

176: && $merged{'cache'}->can('set') 177: && $merged{'cache'}->can('purge')) 178: { 179: Carp::croak("Cache object must implement 'get', 'set', and 'purge' methods"); 180: } 181: } else { 182: Carp::croak(ref($class) . ': Cache must be ref to HASH or object'); 183: } 184: } 185: 186: return bless \%merged, ref($class);

Mutants (Total: 2, Killed: 2, Survived: 0)

187: } 188: 189: # Require at least one argument so Params::Get's confess is never reached; 190: # we want croak (Test::Carp-compatible) not confess. ●191 → 191 → 195 191: if(scalar(@_) == 0) {

Mutants (Total: 2, Killed: 2, Survived: 0)

192: Carp::croak('Usage: ', $class, '->new(cache => $cache)'); 193: } 194: ●195 → 205 → 218 195: my $params = Params::Get::get_params('cache', @_) || {}; 196: 197: # Default the wrapped object to a bare Class::Simple instance 198: $params->{'object'} ||= Class::Simple->new(); 199: 200: # Precompute per-instance fields so AUTOLOAD (hot path) avoids ref() and 201: # string concatenation on every call. 202: $params->{'_is_hash_cache'} = ref($params->{'cache'}) eq 'HASH'; 203: $params->{'_cache_prefix'} = "$class:"; 204: 205: if(Scalar::Util::blessed($params->{'cache'})) {

Mutants (Total: 1, Killed: 1, Survived: 0)

206: # Verify the cache object speaks the required interface 207: unless($params->{'cache'}->can('get')

Mutants (Total: 1, Killed: 1, Survived: 0)

208: && $params->{'cache'}->can('set') 209: && $params->{'cache'}->can('purge')) 210: { 211: Carp::croak("Cache object must implement 'get', 'set', and 'purge' methods"); 212: } 213: return bless $params, $class;

Mutants (Total: 2, Killed: 2, Survived: 0)

214: } 215: 216: # Transitive reduction: _is_hash_cache already holds ref($cache) eq 'HASH' 217: # computed two statements earlier — no need to call ref() again. ●218 → 218 → 222 218: if($params->{'_is_hash_cache'}) {

Mutants (Total: 1, Killed: 1, Survived: 0)

219: return bless $params, $class;

Mutants (Total: 2, Killed: 2, Survived: 0)

220: } 221: 222: Carp::croak("$class: Cache must be ref to HASH or object"); 223: } 224: 225: =head2 can 226: 227: Reports whether this wrapper (or its embedded object) can handle a method. 228: 229: =head3 ARGUMENTS 230: 231: =over 4 232: 233: =item C<$method> — the method name to probe. 234: 235: =back 236: 237: =head3 RETURNS 238: 239: True if the method is known; false otherwise. 240: 241: =head3 EXAMPLE 242: 243: $obj->can('name'); # true if the wrapped object has a name() method 244: 245: =head3 API SPECIFICATION 246: 247: Input: method_name : Str 248: Output: Bool 249: 250: =head3 MESSAGES 251: 252: None. 253: 254: =cut 255: 256: sub can 257: { 258: my ($self, $method) = @_; 259: 260: # Undefined method name is outside the documented API; return undef cleanly. 261: return unless defined $method; 262: 263: # When called as a class method there is no wrapped object to probe 264: return $self->SUPER::can($method) unless Scalar::Util::blessed($self); 265: 266: return ($method eq 'new')

Mutants (Total: 2, Killed: 2, Survived: 0)

267: || $self->{'object'}->can($method) 268: || $self->SUPER::can($method); 269: } 270: 271: =head2 isa 272: 273: Reports whether this wrapper or its embedded object is of a given class. 274: 275: =head3 ARGUMENTS 276: 277: =over 4 278: 279: =item C<$class> — the class name to test. 280: 281: =back 282: 283: =head3 RETURNS 284: 285: True if the wrapper or the wrapped object is-a C<$class>. 286: 287: =head3 EXAMPLE 288: 289: $obj->isa('My::Model'); # delegates to the wrapped object 290: 291: =head3 API SPECIFICATION 292: 293: Input: class_name : Str 294: Output: Bool 295: 296: =head3 MESSAGES 297: 298: None. 299: 300: =cut 301: 302: sub isa 303: { 304: my ($self, $class) = @_; 305: 306: # Undefined class name is outside the documented API; return undef cleanly. 307: return unless defined $class; 308: 309: # When called as a class method there is no wrapped object to interrogate 310: return $self->SUPER::isa($class) unless Scalar::Util::blessed($self);

Mutants (Total: 2, Killed: 2, Survived: 0)

311: 312: return 1 if $class eq ref($self)

Mutants (Total: 2, Killed: 2, Survived: 0)

313: || $class eq __PACKAGE__ 314: || $self->SUPER::isa($class); 315: 316: return $self->{'object'}->isa($class);

Mutants (Total: 2, Killed: 2, Survived: 0)

317: } 318: 319: # DESTROY — purge this instance's entries from the cache on object teardown. 320: # 321: # Purpose: Prevent stale entries from leaking into a shared cache after 322: # the wrapper goes out of scope. 323: # Entry: Called automatically by Perl's garbage collector. 324: # Exit Status: None (void). 325: # Side Effects: Removes all cache keys prefixed with ref($self) for hash caches; 326: # calls purge() for CHI-style caches. 327: # See https://github.com/Perl/perl5/issues/14673 for why an 328: # explicit DESTROY is required even though AUTOLOAD could catch it. 329: sub DESTROY 330: { ●331 → 335 → 343 331: my $self = shift; 332: 333: my $cache = $self->{'cache'} or return; 334: 335: if($self->{'_is_hash_cache'}) {

Mutants (Total: 1, Killed: 1, Survived: 0)

336: # Remove only this class's keys to avoid stomping on siblings sharing the hash 337: my $prefix = $self->{'_cache_prefix'}; 338: delete $cache->{$_} for grep { index($_, $prefix) == 0 } keys %{$cache};

Mutants (Total: 1, Killed: 1, Survived: 0)

339: return; 340: } 341: 342: # Skip purge during global destruction to avoid order-of-destruction crashes 343: return if _GLOBAL_PHASE_AVAILABLE && ${^GLOBAL_PHASE} eq 'DESTRUCT'; 344: 345: $cache->purge(); 346: } 347: 348: # _cache_get / _cache_set — unified read/write; hides hash-ref vs. CHI dispatch 349: # so the rest of the code never needs to branch on ref($cache). 350: sub _cache_get :Protected 351: { 352: my ($self, $key) = @_; 353: my $cache = $self->{'cache'}; 354: return $self->{'_is_hash_cache'} ? $cache->{$key} : $cache->get($key);

Mutants (Total: 2, Killed: 2, Survived: 0)

355: } 356: 357: sub _cache_set :Protected 358: { ●359 → 361 → 0 359: my ($self, $key, $val) = @_; 360: my $cache = $self->{'cache'}; 361: if($self->{'_is_hash_cache'}) {

Mutants (Total: 1, Killed: 1, Survived: 0)

362: $cache->{$key} = $val; 363: } else { 364: $cache->set($key, $val, 'never'); 365: } 366: } 367: 368: =head2 AUTOLOAD (getter/setter proxy) 369: 370: Intercepts every method call that is not explicitly defined, proxying it to 371: the wrapped object with a caching layer for zero-argument (getter) calls. 372: 373: Setter calls (one or more arguments) always pass through to the wrapped object 374: and update the cache with the new value. 375: 376: =head3 ARGUMENTS 377: 378: $method() — getter: returns cached value if present, else calls object 379: $method($scalar) — scalar setter: stores scalar, updates cache 380: $method(@list) — array setter: stores list, updates cache 381: 382: =head3 RETURNS 383: 384: The value returned by the wrapped object (or the cached copy thereof). 385: 386: =head3 SIDE EFFECTS 387: 388: =over 4 389: 390: =item * Getter: may write to the cache on first call. 391: 392: =item * Setter: writes to both the wrapped object and the cache. 393: 394: =item * DESTROY: clears cache entries for this instance (see above). 395: 396: =back 397: 398: =head3 EXAMPLE 399: 400: $obj->colour('red'); # setter — writes 'red' to object and cache 401: $obj->colour(); # getter — returns 'red' from cache 402: 403: =head3 API SPECIFICATION 404: 405: Input (getter): method_name : Str, args : () 406: Input (setter): method_name : Str, args : (Scalar | List) 407: Output: the stored/retrieved value or list 408: 409: =head3 MESSAGES 410: 411: Message Meaning Resolution 412: -------------------------------- ---------------------------------- ---------------------------------------- 413: "$method" (croak) Cached array's first element is Do not store the sentinel string 414: the UNDEF_SENTINEL string as a real value in the wrapped object 415: 416: =head3 PSEUDOCODE 417: 418: AUTOLOAD(method, args...): 419: key = ref(self) + ":" + method 420: 421: IF no args (getter mode): 422: val = cache_get(key) 423: IF cache hit: 424: IF val is a plain string (not a ref): 425: IF val is UNDEF_SENTINEL → return undef 426: RETURN val 427: IF val is an arrayref: 428: IF first element is a plain string AND equals UNDEF_SENTINEL → croak 429: RETURN dereferenced list 430: RETURN val (blessed object) 431: # Cache miss — ask the wrapped object 432: IF list context: 433: result_list = object->method() 434: IF empty → return () 435: cache_set(key, \result_list) 436: RETURN result_list 437: # Scalar context 438: result = object->method() 439: IF defined: 440: cache_set(key, result) 441: RETURN result 442: cache_set(key, UNDEF_SENTINEL) 443: RETURN undef 444: 445: ELSE (setter mode): 446: IF more than one arg (array setter): 447: val = object->method(\@args) # wrapped object stores arrayref 448: IF defined: 449: cache_set(key, val) 450: RETURN @val 451: cache_set(key, UNDEF_SENTINEL) 452: RETURN undef 453: ELSE (scalar setter): 454: val = object->method(args[0]) 455: cache_set(key, val // UNDEF_SENTINEL) 456: RETURN val 457: 458: =cut 459: 460: sub AUTOLOAD 461: { ●462 → 473 → 515 462: our $AUTOLOAD; 463: # rindex+substr avoids regex engine startup on every method dispatch 464: my $param = substr($AUTOLOAD, rindex($AUTOLOAD, '::') + 2); 465: 466: my $self = shift; 467: # _cache_prefix is precomputed at new() time (= ref($self) . ":") to avoid 468: # calling ref() and doing string concat on every AUTOLOAD invocation. 469: my $key = $self->{'_cache_prefix'} . $param; 470: my $object = $self->{'object'}; 471: 472: # Getter path ───────────────────────────────────────────────────────────── 473: if(scalar(@_) == 0) {

Mutants (Total: 2, Killed: 2, Survived: 0)

474: my $rc = $self->_cache_get($key); 475: 476: # Truthiness check: falsy-but-defined values (0, '') are treated as cache 477: # misses so the object is re-invoked every call. This is a known 478: # limitation; see the LIMITATIONS section in the POD. 479: if($rc) {

Mutants (Total: 1, Killed: 1, Survived: 0)

480: # Premise 1: $rc is truthy (the if-guard guarantees this). 481: # Premise 2: ref($rc) returns '' for plain scalars, 'ARRAY' for 482: # arrayrefs, or a package name for blessed objects. 483: # The three branches below are mutually exclusive and exhaustive. 484: if(!ref($rc)) {

Mutants (Total: 1, Killed: 1, Survived: 0)

485: # Conclusion: plain scalar — only type that can equal the sentinel. 486: return if $rc eq UNDEF_SENTINEL; 487: return $rc;

Mutants (Total: 2, Killed: 2, Survived: 0)

488: } elsif(ref($rc) eq 'ARRAY') { 489: # Premise: !ref($rc) was false, so ref($rc) is truthy; no need 490: # to re-test ref() — the elsif is the deductive next step. 491: Carp::croak($param) 492: if !ref($rc->[0]) && $rc->[0] eq UNDEF_SENTINEL; 493: return @{$rc};

Mutants (Total: 2, Killed: 2, Survived: 0)

494: } 495: # Conclusion: ref($rc) is truthy and ≠ 'ARRAY' → blessed object. 496: # Overloaded eq was never invoked; return the object as-is. 497: return $rc;

Mutants (Total: 2, Killed: 2, Survived: 0)

498: } 499: 500: # Cache miss — call the real object 501: if(wantarray) {

Mutants (Total: 1, Killed: 1, Survived: 0)

502: my @result = $object->$param(); 503: return unless scalar(@result); # empty list: don't cache, return () 504: $self->_cache_set($key, \@result); 505: return @result;

Mutants (Total: 2, Killed: 2, Survived: 0)

506: } 507: 508: # Scalar / void context: cache the value (or the sentinel for undef) 509: my $val = $object->$param(); 510: $self->_cache_set($key, defined($val) ? $val : UNDEF_SENTINEL); 511: return $val;

Mutants (Total: 2, Killed: 2, Survived: 0)

512: } 513: 514: # Setter path — array ───────────────────────────────────────────────────── ●515 → 515 → 525 515: if(scalar(@_) > 1) {

Mutants (Total: 4, Killed: 4, Survived: 0)

516: # Pass the list as an arrayref; the wrapped object stores it 517: my $val = $object->$param(\@_); 518: $self->_cache_set($key, defined($val) ? $val : UNDEF_SENTINEL); 519: return defined($val) ? @{$val} : ();

Mutants (Total: 2, Killed: 2, Survived: 0)

520: } 521: 522: # Setter path — scalar ──────────────────────────────────────────────────── 523: # CHI's set() returns the cache object, not the stored value; 524: # capture the value before the set call and return it directly. 525: my $val = $object->$param($_[0]); 526: $self->_cache_set($key, defined($val) ? $val : UNDEF_SENTINEL); 527: return $val;

Mutants (Total: 2, Killed: 2, Survived: 0)

528: } 529: 530: =head1 FORMAL SPECIFICATION 531: 532: =head2 new 533: 534: ───────────────────────────────────────────────────────────────── 535: [State] 536: cache : ℙ(HashRef ∪ CHIObject) 537: object : Object 538: 539: [CHIObject] 540: can_get : Method 541: can_set : Method 542: can_purge : Method 543: 544: new ────────────────────────────────────────────────────────────── 545: Δ(cache, object) 546: cache? : HashRef ∪ CHIObject 547: object? : Object ∪ {∅} 548: ───────────────────────────────────────────────── 549: cache? ≠ ∅ 550: cache ′ = cache? 551: object ′ = (object? ≠ ∅ ⟹ object?) ∨ Class::Simple.new() 552: ───────────────────────────────────────────────── 553: 554: =head2 can 555: 556: can ────────────────────────────────────────────────── 557: Ξ(cache, object) 558: method? : MethodName 559: ───────────────────────────────────────────────── 560: result! = (method? = 'new') 561: ∨ object.can(method?) 562: ∨ SUPER::can(method?) 563: 564: =head2 isa 565: 566: isa ────────────────────────────────────────────────── 567: Ξ(cache, object) 568: class? : ClassName 569: ───────────────────────────────────────────────── 570: result! = (class? = ref(self)) 571: ∨ (class? = __PACKAGE__) 572: ∨ SUPER::isa(class?) 573: ∨ object.isa(class?) 574: 575: =head2 AUTOLOAD 576: 577: AUTOLOAD ───────────────────────────────────────────────────────── 578: Δ(cache) 579: method? : MethodName 580: args? : Seq(Any) 581: ───────────────────────────────────────────────── 582: key = ref(self) ⊕ ":" ⊕ method? 583: 584: Getter (args? = ∅): 585: (∃ v • cache_hit(key, v) ∧ v ≠ UNDEF_SENTINEL ⟹ result! = v) 586: ∨ (cache_hit(key, UNDEF_SENTINEL) ⟹ result! = undef) 587: ∨ (¬cache_hit(key, _) 588: ∧ result! = object.method?() 589: ∧ cache′ = cache ∪ {key ↦ encode(result!)}) 590: 591: Setter (args? ≠ ∅): 592: object′.method?(args?) = args? 593: cache′ = cache ∪ {key ↦ encode(object′.method?())} 594: result! = args? 595: 596: =head1 LIMITATIONS 597: 598: =over 4 599: 600: =item Setter arguments are not part of the cache key. 601: 602: Calls like C<< $obj->method($arg) >> use the key C<ClassName:method> regardless 603: of C<$arg>. Setters with different arguments therefore overwrite each other's 604: cache entries. For read-only caching of parameterised methods, see 605: L<Class::Simple::Readonly::Cached>. 606: 607: =item Falsy scalar return values are not cached. 608: 609: Methods that return C<0> or C<''> (false but defined) are treated as a cache 610: miss on every call: the underlying object is re-invoked each time. Only 611: C<undef> is cached (via the UNDEF_SENTINEL). If you need to cache C<0>, wrap 612: it in a container object or use a different caching strategy. 613: 614: =item Sentinel collision. 615: 616: If the wrapped object ever returns the literal string 617: C<< "Class::Simple::Cached\>UNDEF\<" >> as a scalar, or as the first element of a 618: list, the module will misinterpret it as a cached-undef marker. This string is 619: deliberately unusual, but callers working with arbitrary string data should be 620: aware of the constraint. 621: 622: =item Shared cache and multiple classes. 623: 624: When two C<Class::Simple::Cached> instances of B<different> classes share the 625: same cache object, their keys are namespaced by class name and do not collide. 626: However, C<purge()> on a CHI-style cache is global: destroying one instance will 627: purge I<all> entries from a shared CHI cache, including those of the other 628: instance. Use per-instance cache objects, or a hash-ref cache, to avoid this. 629: 630: =item Does not work with L<Memoize>. 631: 632: =item Overloaded C<eq> on cached values. 633: 634: If the wrapped object returns a blessed value that overloads the C<eq> operator, 635: the sentinel comparison in the getter uses a C<ref()> pre-check so that only 636: plain strings are compared against the sentinel. Array elements are subject to 637: the same pre-check. Callers wrapping objects that return sentinel-like strings 638: via overloading should be aware of this guard. 639: 640: =back 641: 642: =head1 AUTHOR 643: 644: Nigel Horne, C<< <njh at nigelhorne.com> >> 645: 646: =head1 BUGS 647: 648: Please report bugs and feature requests at 649: L<https://github.com/nigelhorne/Class-Simple-Cached/issues>. 650: 651: =head1 SEE ALSO 652: 653: L<Class::Simple>, L<CHI>, L<Class::Simple::Readonly::Cached> 654: 655: =over 4 656: 657: =item * L<Test Dashboard|https://nigelhorne.github.io/Class-Simple-Cached/coverage/> 658: 659: =back 660: 661: =head1 SUPPORT 662: 663: This module is provided as-is without any warranty. 664: 665: You can find documentation for this module with the perldoc command: 666: 667: perldoc Class::Simple::Cached 668: 669: =over 4 670: 671: =item * MetaCPAN: L<https://metacpan.org/release/Class-Simple-Cached> 672: 673: =item * Source: L<https://github.com/nigelhorne/Class-Simple-Cached> 674: 675: =item * CPANTS: L<http://cpants.cpanauthors.org/dist/Class-Simple-Cached> 676: 677: =item * Testers Matrix: L<http://matrix.cpantesters.org/?dist=Class-Simple-Cached> 678: 679: =back 680: 681: =head1 LICENCE AND COPYRIGHT 682: 683: Copyright (C) 2019-2026, Nigel Horne 684: 685: Usage is subject to the GPL2 licence terms. 686: If you use it, 687: please let me know. 688: 689: =cut 690: 691: 1;