File Coverage

blib/lib/Class/Simple/Cached.pm
Criterion Covered Total %
statement 101 115 87.8
branch 47 62 75.8
condition 13 25 52.0
subroutine 11 11 100.0
pod 3 3 100.0
total 175 216 81.0


line stmt bran cond sub pod time code
1             package Class::Simple::Cached;
2              
3 8     8   1946335 use strict;
  8         18  
  8         367  
4 8     8   49 use warnings;
  8         17  
  8         458  
5 8     8   89 use Carp;
  8         15  
  8         616  
6 8     8   5274 use Class::Simple;
  8         56924  
  8         416  
7 8     8   4593 use Params::Get;
  8         121462  
  8         637  
8 8     8   72 use Scalar::Util;
  8         18  
  8         12311  
9              
10             my @ISA = ('Class::Simple');
11              
12             =head1 NAME
13              
14             Class::Simple::Cached - cache messages to an object
15              
16             =head1 VERSION
17              
18             Version 0.06
19              
20             =cut
21              
22             our $VERSION = '0.06';
23              
24             =head1 SYNOPSIS
25              
26             A subclass of L<Class::Simple> which caches calls to read the status of an object that are otherwise expensive.
27              
28             It is up to the caller to maintain the cache if the object comes out of sync with the cache,
29             for example,
30             by changing its state.
31              
32             You can use this class to create a caching layer for any object of any class
33             that works on objects with a get/set model,
34             such as:
35              
36             use Class::Simple;
37             my $obj = Class::Simple->new();
38             $obj->val('foo');
39             my $oldval = $obj->val();
40              
41             =head1 SUBROUTINES/METHODS
42              
43             =head2 new
44              
45             Creates a Class::Simple::Cached object.
46              
47             It takes one mandatory parameter: cache,
48             which is either an object which understands purge(), get() and set() calls,
49             such as an L<CHI> object;
50             or is a reference to a hash where the return values are to be stored.
51              
52             It takes one optional argument: object,
53             which is an object that is taken to be the object to be cached.
54             If not given, an object of the class L<Class::Simple> is instantiated
55             and that is used.
56              
57             =cut
58              
59             sub new
60             {
61 15     15 1 1957636 my $class = shift;
62              
63             # Use Class::Simple::Cached->new(), not Class::Simple::Cached::new()
64 15 100       58 if(!defined($class)) {
65 1         13 carp(__PACKAGE__, ' use ->new() not ::new() to instantiate');
66 1         861 return;
67             }
68 14 100       46 if(Scalar::Util::blessed($class)) {
69 1   50     4 my $params = Params::Get::get_params(undef, @_) || {};
70             # clone the given object
71 1         15 return bless { %{$class}, %{$params} }, ref($class);
  1         3  
  1         12  
72             }
73              
74 13   50     58 my $params = Params::Get::get_params('cache', @_) || {};
75              
76             # Later Perls can use //=
77 11   66     370 $params->{object} ||= Class::Simple->new(%{$params}); # Default to Class::Simple object
  8         56  
78              
79             # FIXME: If there are arguments, put the values in the cache
80              
81             # Ensure cache implements required methods
82 11 100       2378 if(Scalar::Util::blessed($params->{cache})) {
83 3 50 33     53 unless($params->{cache}->can('get') && $params->{cache}->can('set') && $params->{cache}->can('purge')) {
      33        
84 0         0 Carp::croak("Cache object must implement 'get', 'set', and 'purge' methods");
85             }
86 3         11 return bless $params, $class;
87             }
88 8 100       32 if(ref($params->{'cache'}) eq 'HASH') {
89 4         23 return bless $params, $class;
90             }
91              
92 4         34 Carp::croak("$class: Cache must be ref to HASH or object");
93             }
94              
95             =head2 can
96              
97             Returns if the embedded object can handle a message
98              
99             =cut
100              
101             sub can
102             {
103 4     4 1 1503 my ($self, $method) = @_;
104              
105 4   66     97 return ($method eq 'new') || $self->{'object'}->can($method) || $self->SUPER::can($method);
106             }
107              
108             =head2 isa
109              
110             Returns if the embedded object is the given type of object
111              
112             =cut
113              
114             sub isa
115             {
116 11     11 1 836 my ($self, $class) = @_;
117              
118 11 100 66     64 if($class eq ref($self) || ($class eq __PACKAGE__) || $self->SUPER::isa($class)) {
      100        
119 9         30 return 1;
120             }
121 2         14 return $self->{'object'}->isa($class);
122             }
123              
124             # sub _caller_class
125             # {
126             # my $self = shift;
127             #
128             # if(ref($self->{'object'}) eq 'Class::Simple') {
129             # # return $self->SUPER::_caller_class(@_);
130             # return $self->Class::Simple::_caller_class(@_);
131             # }
132             # }
133              
134             # For older Perls - define a DESTROY method
135             # See https://github.com/Perl/perl5/issues/14673
136             sub DESTROY
137             {
138 8     8   4074 my $self = shift;
139 8 50       35 if(my $cache = $self->{'cache'}) {
140 8 100       26 if(ref($cache) eq 'HASH') {
141 5         10 my $class = ref($self);
142             # while(my($key, $value) = each %{$cache}) {
143             # if($key =~ /^$class/) {
144             # delete $cache->{$key};
145             # }
146             # }
147 5         9 delete $cache->{$_} for grep { /^$class/ } keys %{$cache};
  5         105  
  5         22  
148             } else {
149 3         15 $cache->purge();
150             }
151             }
152             }
153              
154             sub AUTOLOAD
155             {
156 36     36   14056 our $AUTOLOAD;
157 36         296 my ($param) = $AUTOLOAD =~ /::(\w+)$/;
158              
159 36         77 my $self = shift;
160 36         70 my $cache = $self->{'cache'};
161              
162 36 50       110 if($param eq 'DESTROY') {
163 0 0       0 if(ref($cache) eq 'HASH') {
164 0         0 my $class = ref($self);
165             # while(my($key, $value) = each %{$cache}) {
166             # if($key =~ /^$class/) {
167             # delete $cache->{$key};
168             # }
169             # }
170 0         0 delete $cache->{$_} for grep { /^$class/ } keys %{$cache};
  0         0  
  0         0  
171 0         0 return;
172             }
173 0 0 0     0 if(defined($^V) && ($^V ge 'v5.14.0')) {
174 0 0       0 return if ${^GLOBAL_PHASE} eq 'DESTRUCT'; # >= 5.14.0 only
175             }
176 0         0 $cache->purge();
177 0         0 return;
178             }
179              
180             # my $method = $self->{'object'} . "::$param";
181 36         81 my $object = $self->{'object'};
182              
183             # if($param =~ /^[gs]et_/) {
184             # # $param = "SUPER::$param";
185             # return $object->$method(\@_);
186             # }
187              
188             # TODO: To add argument support, make the code more than simply "param",
189             # e.g. my $cache_key = join('|', $param, @_);
190              
191 36         97 my $key = ref($self) . ":$param";
192              
193 36 100       105 if(scalar(@_) == 0) { # Getter
194             # Retrieving a value
195 30         45 my $rc;
196 30 100       88 if(ref($cache) eq 'HASH') {
197 14         38 $rc = $cache->{$key};
198             } else {
199 16         42 $rc = $cache->get($key);
200             }
201 30 100       1167 if($rc) {
202 17 50       56 die $param if($rc eq 'never');
203 17 100       44 if(ref($rc) eq 'ARRAY') {
204 4         8 my @foo = @{$rc};
  4         14  
205 4 50       12 die $param if($foo[0] eq __PACKAGE__ . '>UNDEF<');
206 4 50       12 die $param if($foo[0] eq 'never');
207 4         6 return @{$rc};
  4         22  
208             }
209 13 100       45 if($rc eq __PACKAGE__ . '>UNDEF<') {
210 1         5 return;
211             }
212 12         99 return $rc;
213             }
214 13 100       37 if(wantarray) {
215 8         40 my @rc = $object->$param();
216 8 100       144 if(scalar(@rc) == 0) {
217 3         12 return;
218             }
219 5 100       15 if(ref($cache) eq 'HASH') {
220 2         10 $cache->{$key} = \@rc;
221             } else {
222 3         8 $cache->set($key, \@rc, 'never');
223             }
224 5         448 return @rc;
225             }
226 5 100       22 if(defined(my $rc = $object->$param())) {
227 4 100       36 if(ref($cache) eq 'HASH') {
228 2         16 return $cache->{$key} = $rc;
229             }
230 2         7 return $cache->set($key, $rc, 'never');
231             }
232 1 50       5 if(ref($cache) eq 'HASH') {
233 0         0 return $cache->{$key} = __PACKAGE__ . '>UNDEF<';
234             }
235 1         4 $cache->set($key, __PACKAGE__ . '>UNDEF<', 'never');
236 1         102 return;
237             }
238              
239             # Setter
240              
241             # $param = "SUPER::$param";
242             # return $cache->set($key, $self->$param(@_), 'never');
243 6 100       20 if($_[1]) {
244             # Storing an array
245             # We store a ref to the array, and dereference on retrieval
246 2 100       10 if(defined(my $val = $object->$param(\@_))) {
247 1 50       111 if(ref($cache) eq 'HASH') {
248 0         0 $cache->{$key} = $val;
249             } else {
250 1         5 $cache->set($key, $val, 'never');
251             }
252 1         113 return @{$val};
  1         5  
253             }
254 1 50       6 if(ref($cache) eq 'HASH') {
255 0         0 return $cache->{$param} = __PACKAGE__ . '>UNDEF<';
256             }
257 1         4 $cache->set($key, __PACKAGE__ . '>UNDEF<', 'never');
258 1         132 return;
259             }
260             # Storing a scalar
261 4 100       25 if(ref($cache) eq 'HASH') {
262 2         17 return $cache->{$key} = $object->$param($_[0]);
263             }
264 2         16 return $cache->set($key, $object->$param($_[0]), 'never');
265             }
266              
267             =head1 AUTHOR
268              
269             Nigel Horne, C<< <njh at bandsman.co.uk> >>
270              
271             =head1 BUGS
272              
273             Doesn't work with L<Memoize>.
274              
275             Only works on messages that take no arguments.
276             For that, use L<Class::Simple::Readonly::Cached>.
277              
278             Please report any bugs or feature requests to L<https://github.com/nigelhorne/Class-Simple-Readonly/issues>.
279             I will be notified,
280             and then you'll automatically be notified of the progress on your bug as I make changes.
281              
282             =head1 SEE ALSO
283              
284             L<Class::Simple>, L<CHI>
285              
286             =head1 SUPPORT
287              
288             This module is provided as-is without any warranty.
289              
290             You can find documentation for this module with the perldoc command.
291              
292             perldoc Class::Simple::Cached
293              
294             You can also look for information at:
295              
296             =over 4
297              
298             =item * MetaCPAN
299              
300             L<https://metacpan.org/release/Class-Simple-Cached>
301              
302             =item * Source Repository
303              
304             L<https://github.com/nigelhorne/Class-Simple-Readonly-Cached>
305              
306             =item * CPANTS
307              
308             L<http://cpants.cpanauthors.org/dist/Class-Simple-Cached>
309              
310             =item * CPAN Testers' Matrix
311              
312             L<http://matrix.cpantesters.org/?dist=Class-Simple-Cached>
313              
314             =item * CPAN Testers Dependencies
315              
316             L<http://deps.cpantesters.org/?module=Class::Simple::Cached>
317              
318             =back
319              
320             =head1 LICENCE AND COPYRIGHT
321              
322             Author Nigel Horne: C<njh@bandsman.co.uk>
323             Copyright (C) 2019-2025, Nigel Horne
324              
325             Usage is subject to licence terms.
326             The licence terms of this software are as follows:
327             Personal single user, single computer use: GPL2
328             All other users (including Commercial, Charity, Educational, Government)
329             must apply in writing for a licence for use from Nigel Horne at the
330             above e-mail.
331             =cut
332              
333             1;