File Coverage

lib/Class/Simple/Readonly/Cached.pm
Criterion Covered Total %
statement 96 103 93.2
branch 49 60 81.6
condition 2 6 33.3
subroutine 8 9 88.8
pod 3 3 100.0
total 158 181 87.2


line stmt bran cond sub pod time code
1              
2             use strict;
3 5     5   501423 use warnings;
  5         43  
  5         130  
4 5     5   22 use Carp;
  5         10  
  5         115  
5 5     5   25 use Class::Simple;
  5         9  
  5         270  
6 5     5   2194  
  5         24185  
  5         3626  
7             my @ISA = ('Class::Simple');
8              
9             =head1 NAME
10              
11             Class::Simple::Readonly::Cached - cache messages to an object
12              
13             =head1 VERSION
14              
15             Version 0.08
16              
17             =cut
18              
19             our $VERSION = '0.08';
20              
21             =head1 SYNOPSIS
22              
23             A sub-class of L<Class::Simple> which caches calls to read
24             the status of an object that are otherwise expensive.
25              
26             It is up to the caller to maintain the cache if the object comes out of sync with the cache,
27             for example by changing its state.
28              
29             You can use this class to create a caching layer to an object of any class
30             that works on objects which doesn't change its state based on input:
31              
32             $val = $obj->val();
33             $val = $obj->val(a => 'b');
34              
35             =head1 SUBROUTINES/METHODS
36              
37             =head2 new
38              
39             Creates a Class::Simple::Readonly::Cached object.
40              
41             It takes one mandatory parameter: cache,
42             which is either an object which understands clear(), get() and set() calls,
43             such as an L<CHI> object;
44             or is a reference to a hash where the return values are to be stored.
45              
46             It takes one optional argument: object,
47             which is an object which is taken to be the object to be cached.
48             If not given, an object of the class L<Class::Simple> is instantiated
49             and that is used.
50              
51             use Gedcom;
52              
53             my %hash;
54             my $person = Gedcom::Person->new();
55             ... # Set up some data
56             my $object = Class::Simple::Readonly::Cached(object => $person, cache => \%hash);
57             my $father1 = $object->father(); # Will call gedcom->father() to get the person's father
58             my $father2 = $object->father(); # Will retrieve the father from the cache without calling person->father()
59              
60             =cut
61              
62             my $proto = shift;
63             my $class = ref($proto) || $proto;
64 10     10 1 96375  
65 10   33     52 # Use Class::Simple::Readonly::Cached->new(), not Class::Simple::Readonly::Cached::new()
66             if(!defined($class)) {
67             Carp::carp(__PACKAGE__, ' use ->new() not ::new() to instantiate');
68 10 50       27 return;
69 0         0 }
70 0         0  
71             my %args;
72             if(ref($_[0]) eq 'HASH') {
73 10         21 %args = %{$_[0]};
74 10 100       57 } elsif(ref($_[0])) {
    50          
    50          
75 2         5 Carp::carp('Usage: ', __PACKAGE__, '->new(cache => $cache [, object => $object ], %args)');
  2         8  
76             return;
77 0         0 } elsif(@_ % 2 == 0) {
78 0         0 %args = @_;
79             }
80 8         25  
81             if(!$args{'cache'}) {
82             Carp::carp('Usage: ', __PACKAGE__, '->new(cache => $cache [, object => $object ], %args)');
83 10 100       33 return;
84 3         34 }
85 3         1176  
86             if(defined($args{'object'})) {
87             if(ref($args{'object'})) {
88 7 100       20 if(ref($args{'object'}) eq __PACKAGE__) {
89 5 100       14 Carp::carp(__PACKAGE__, ' warning: $object is already cached');
90 4 100       17 # Note that this isn't a technique for clearing the cache
91 1         18 return $args{'object'};
92             }
93 1         276 } else {
94             Carp::carp(__PACKAGE__, ' $object is a scalar');
95             return;
96 1         4 }
97 1         275 } else {
98             $args{'object'} = Class::Simple->new(%args);
99             }
100 2         21  
101             return bless \%args, $class;
102             }
103 5         770  
104             =head2 object
105              
106             Return the encapsulated object
107              
108             =cut
109              
110             {
111             my $self = shift;
112              
113             return $self->{'object'};
114 1     1 1 2 }
115              
116 1         4 {
117             my $self = shift;
118              
119             if(ref($self->{'object'}) eq 'Class::Simple') {
120             # return $self->SUPER::_caller_class(@_);
121 0     0   0 return $self->Class::Simple::_caller_class(@_);
122             }
123 0 0       0 }
124              
125 0         0 =head2 state
126              
127             Returns the state of the object
128              
129             print Data::Dumper->new([$obj->state()]->Dump();
130              
131             =cut
132              
133             my $self = shift;
134              
135             return { hits => $self->{_hits}, misses => $self->{_misses} };
136             }
137              
138 5     5 1 2996 # Returns a cached object, if you want it to be uncached, you'll need to clone it
139             our $AUTOLOAD;
140 5         19 my $param = $AUTOLOAD;
141             $param =~ s/.*:://;
142              
143             my $self = shift;
144             my $cache = $self->{'cache'};
145 43     43   11735  
146 43         69 if($param eq 'DESTROY') {
147 43         229 if($cache) {
148             if(ref($cache) eq 'HASH') {
149 43         80 while(my($key, $value) = each %{$cache}) {
150 43         78 delete $cache->{$key};
151             }
152 43 100       106 return;
153 5 50       18 }
154 5 100       21 if(defined($^V) && ($^V ge 'v5.14.0')) {
155 4         8 return if ${^GLOBAL_PHASE} eq 'DESTRUCT'; # >= 5.14.0 only
  14         40  
156 10         16 }
157             $cache->clear();
158 4         37 }
159             return;
160 1 50 33     53 }
161 1 50       5  
162             # my $func = $self->{'object'} . "::$param";
163 1         9 my $func = $param;
164              
165 1         27 # if($param =~ /^[gs]et_/) {
166             # # $param = "SUPER::$param";
167             # return $object->$func(\@_);
168             # }
169 38         53  
170             my $key = $param . '::' . join('::', grep defined, @_);
171              
172             my $rc;
173             if(ref($cache) eq 'HASH') {
174             $rc = $cache->{$key};
175             } else {
176 38         117 $rc = $cache->get($key);
177             }
178 38         48 if(defined($rc)) {
179 38 100       84 # Retrieving a value
180 21         50 die $key if($rc eq 'never');
181             if(ref($rc) eq 'ARRAY') {
182 17         51 $self->{_hits}{$key}++;
183             my @foo = @{$rc};
184 38 100       991 if(wantarray) {
185             if(defined($foo[0])) {
186 18 50       38 die $key if($foo[0] eq __PACKAGE__ . '>UNDEF<');
187 18 100       45 die $key if($foo[0] eq 'never');
188 6         16 }
189 6         7 return @{$rc};
  6         14  
190 6 100       15 }
191 5 100       12 return pop @foo;
192 4 50       9 }
193 4 50       9 if($rc eq __PACKAGE__ . '>UNDEF<') {
194             $self->{_hits}{$key}++;
195 5         7 return;
  5         38  
196             }
197 1         4 if(!wantarray) {
198             $self->{_hits}{$key}++;
199 12 100       29 return $rc;
200 2         4 }
201 2         8 # Want array from cached array after previously requesting it as a scalar
202             }
203 10 100       20 $self->{_misses}{$key}++;
204 9         17 my $object = $self->{'object'};
205 9         41 if(wantarray) {
206             my @rc = $object->$func(@_);
207             if(scalar(@rc) == 0) {
208             return;
209 21         47 }
210 21         31 if(ref($cache) eq 'HASH') {
211 21 100       38 $cache->{$key} = \@rc;
212 8         43 } else {
213 8 100       35 $cache->set($key, \@rc, 'never');
214 2         6 }
215             return @rc;
216 6 100       13 }
217 3         5 $rc = $object->$func(@_);
218             if(!defined($rc)) {
219 3         8 if(ref($cache) eq 'HASH') {
220             $cache->{$key} = __PACKAGE__ . '>UNDEF<';
221 6         311 } else {
222             $cache->set($key, __PACKAGE__ . '>UNDEF<', 'never');
223 13         37 }
224 13 100       64 return;
225 2 100       9 }
226 1         4 # This would be nice, but it does break gedcom. TODO: find out why
227             # if(ref($rc) && (ref($rc) =~ /::/) && (ref($rc) ne __PACKAGE__)) {
228 1         4 # $rc = Class::Simple::Readonly::Cached->new(object => $rc, cache => {});
229             # }
230 2         105 if(ref($cache) eq 'HASH') {
231             return $cache->{$key} = $rc;
232             }
233             return $cache->set($key, $rc, 'never');
234             }
235              
236 11 100       23 =head1 AUTHOR
237 7         35  
238             Nigel Horne, C<< <njh at bandsman.co.uk> >>
239 4         12  
240             =head1 BUGS
241              
242             Doesn't work with L<Memoize>.
243              
244             Please report any bugs or feature requests to L<https://github.com/nigelhorne/Class-Simple-Readonly-Cached/issues>.
245             I will be notified, and then you'll
246             automatically be notified of progress on your bug as I make changes.
247              
248             =head1 SEE ALSO
249              
250             L<Class::Simple>, L<CHI>
251              
252             =head1 SUPPORT
253              
254             You can find documentation for this module with the perldoc command.
255              
256             perldoc Class::Simple::Readonly::Cached
257              
258             You can also look for information at:
259              
260             =over 4
261              
262             =item * MetaCPAN
263              
264             L<https://metacpan.org/release/Class-Simple-Readonly-Cached>
265              
266             =item * Source Repository
267              
268             L<https://github.com/nigelhorne/Class-Simple-Readonly-Cached>
269              
270             =item * CPANTS
271              
272             L<http://cpants.cpanauthors.org/dist/Class-Simple-Readonly-Cached>
273              
274             =item * CPAN Testers' Matrix
275              
276             L<http://matrix.cpantesters.org/?dist=Class-Simple-Readonly-Cached>
277              
278             =item * CPAN Ratings
279              
280             L<http://cpanratings.perl.org/d/Class-Simple-Readonly-Cached>
281              
282             =item * CPAN Testers Dependencies
283              
284             L<http://deps.cpantesters.org/?module=Class::Simple::Readonly::Cached>
285              
286             =item * Search CPAN
287              
288             L<http://search.cpan.org/dist/Class-Simple-Readonly-Cached/>
289              
290             =back
291              
292             =head1 LICENSE AND COPYRIGHT
293              
294             Author Nigel Horne: C<njh@bandsman.co.uk>
295             Copyright (C) 2019-2022 Nigel Horne
296              
297             Usage is subject to licence terms.
298             The licence terms of this software are as follows:
299             Personal single user, single computer use: GPL2
300             All other users (including Commercial, Charity, Educational, Government)
301             must apply in writing for a licence for use from Nigel Horne at the
302             above e-mail.
303             =cut
304              
305             1;