TER1 (Statement): 100.00%
TER2 (Branch): 96.67%
TER3 (LCSAJ): 100.0% (9/9)
Approximate LCSAJ segments: 61
● 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.
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;