File Coverage

blib/lib/CAM/Template/Cache.pm
Criterion Covered Total %
statement 12 114 10.5
branch 0 42 0.0
condition 0 24 0.0
subroutine 4 18 22.2
pod 14 14 100.0
total 30 212 14.1


line stmt bran cond sub pod time code
1             package CAM::Template::Cache;
2              
3             =head1 NAME
4              
5             CAM::Template::Cache - Template files with database storage
6              
7             =head1 LICENSE
8              
9             Copyright 2005 Clotho Advanced Media, Inc.,
10              
11             This library is free software; you can redistribute it and/or modify it
12             under the same terms as Perl itself.
13              
14             =head1 SYNOPSIS
15              
16             use CAM::Template::Cache;
17             CAM::Template::Cache->setDBH($dbh);
18             CAM::Template::Cache->setExpiration(60*60); # seconds ago
19            
20             my $key = $username.":".$pagetype; # whatever you like
21             my $template = new CAM::Template::Cache($key);
22             $template->setExpiration(24*60*60); # seconds ago
23             if ($template->isFresh()) {
24             $template->printCache();
25             } else {
26             $template->setFilename($templateFilename);
27             $template->addParams(blah blah);
28             $template->print();
29             }
30              
31             =head1 DESCRIPTION
32              
33             CAM::Template provides an interface for parameter replacement in a
34             template file. This package provides the additional functionality of
35             storing the completed template in a MySQL database for later quick
36             retrieval.
37              
38             Use of the cached version of the template requires a unique key that
39             will allow retrieval of the completed file, if present. The cache
40             uses a time stamp and an expiration interval (default: 1 day) to
41             decide if the cached copy is recent enough.
42              
43             This module also includes the class methods setup() and clean() as
44             convenience functions for initialization and maintenance of the cache
45             database.
46              
47             =cut
48              
49             require 5.005_62;
50 1     1   31456 use strict;
  1         2  
  1         34  
51 1     1   4 use warnings;
  1         1  
  1         26  
52 1     1   4 use Carp;
  1         2  
  1         75  
53 1     1   836 use CAM::Template;
  1         3491  
  1         1481  
54              
55             our @ISA = qw(CAM::Template);
56             our $VERSION = '0.91';
57              
58             # global settings, can be overridden for the whole class or for
59             # individual instances.
60             our $global_expiration = 24*60*60; # one day, in seconds
61             our $global_dbh = undef;
62             our $global_dbTablename = "TemplateCache";
63             our $global_uselock = 0;
64              
65             our $colname_key = "TemplateCache_key";
66             our $colname_time = "TemplateCache_time";
67             our $colname_data = "TemplateCache_content";
68              
69             #--------------------------
70              
71             =head1 FUNCTIONS
72              
73             =over 4
74              
75             =cut
76              
77             #--------------------------
78              
79             =item new
80              
81             =item new CACHEKEY
82              
83             =item new CACHEKEY, DBIHANDLE
84              
85             Create a new template object. To get the caching functionality, the
86             cachekey is required, and must uniquely identify the content of
87             interest. If the cachekey is not specified, then this template
88             behaves without any of the caching infrastructure.
89              
90             If the database handle is not set here, it must have been
91             set previously via the class method setDBH().
92              
93             Any additional function arguments (namely, a filename or replacement
94             parameters) are passed on to the CAM::Template constructor.
95              
96             =cut
97              
98             sub new
99             {
100 0     0 1   my $pkg = shift;
101 0           my $cachekey = shift;
102 0           my $dbh;
103 0 0         $dbh = shift if (ref $_[0]);
104              
105 0           my $self = $pkg->SUPER::new(@_);
106 0           $self->{cachekey} = $cachekey;
107 0           $self->{expiration} = $global_expiration;
108 0           $self->{dbTablename} = $global_dbTablename;
109 0   0       $self->{dbh} = $dbh || $global_dbh;
110              
111 0 0         if (defined $self->{cachekey})
112             {
113 0 0         if (!$self->{dbh})
114             {
115 0           &carp("No database connection has been specified. Please use ".$pkg."::setDBH()");
116 0           return undef;
117             }
118 0 0         if (ref($self->{dbh}) !~ /^(DBI|DBD)\b/)
119             {
120 0           &carp("The DBH object is not a valid DBI/DBD connection: " . ref($self->{dbh}));
121 0           return undef;
122             }
123             }
124              
125 0           return $self;
126             }
127             #--------------------------
128              
129             =item setDBH DBI_HANDLE
130              
131             Set the global database handle for this package. Use like this:
132              
133             CAM::Template::Cache->setDBH($dbh);
134              
135             =cut
136              
137             sub setDBH
138             {
139 0     0 1   my $pkg = shift; # unused
140 0           my $val = shift;
141 0           $global_dbh = $val;
142             }
143             #--------------------------
144              
145             =item setExpiration SECONDS
146              
147             Set the duration for the cached content. If the cache is older than
148             the specified time, the isFresh() method will return false.
149              
150             Use like this:
151              
152             CAM::Template::Cache->setExpiration($seconds);
153              
154             or like this:
155              
156             $template->setExpiration($seconds);
157              
158             =cut
159              
160             sub setExpiration
161             {
162 0     0 1   my $self = shift;
163 0           my $val = shift;
164              
165 0 0         if (ref $self)
166             {
167 0           $self->{expiration} = $val;
168             }
169             else
170             {
171 0           $global_expiration = $val;
172             }
173             }
174             #--------------------------
175              
176             =item setTableName NAME
177              
178             Set the name of the database table that is used for the cache.
179              
180             Use like this:
181              
182             CAM::Template::Cache->setTableName($name);
183              
184             or like this:
185              
186             $template->setTableName($name);
187              
188             =cut
189              
190             sub setTableName
191             {
192 0     0 1   my $self = shift;
193 0           my $val = shift;
194              
195 0 0         if (ref $self)
196             {
197 0           $self->{dbTablename} = $val;
198             }
199             else
200             {
201 0           $global_dbTablename = $val;
202             }
203             }
204             #--------------------------
205              
206             =item setUseLock 0|1
207              
208             Set the global preference for whether to lock the database table when
209             doing a save (since save() does both a delete and an insert). Turning
210             off lock may lead to a (rare!) race condition where two inserts
211             happen, leading to a duplicate record. Turning on locking may lead to
212             performance bottlenecks. The default is off.
213              
214             =cut
215              
216             sub setUseLock
217             {
218 0     0 1   my $pkg = shift; # unused
219 0           my $val = shift;
220 0           $global_uselock = $val;
221             }
222             #--------------------------
223              
224             =item isFresh
225              
226             Returns a boolean indicating whether the cache is present and
227             whether it is up to date.
228              
229             =cut
230              
231             sub isFresh
232             {
233 0     0 1   my $self = shift;
234              
235 0 0         return undef if (!defined $self->{cachekey});
236              
237 0           my $dbh = $self->{dbh};
238 0           my $sth = $dbh->prepare("select *," .
239             "date_add(now(), interval -$$self{expiration} second) as expires " .
240             "from $$self{dbTablename} " .
241             "where $colname_key=? " .
242             "limit 1");
243 0           $sth->execute($self->{cachekey});
244 0           my $row = $sth->fetchrow_hashref();
245 0           $sth->finish();
246              
247 0 0         return undef if (!$row);
248              
249 0           $row->{$colname_time} =~ s/\D//g;
250 0           $row->{expires} =~ s/\D//g;
251              
252 0 0         if ($row->{$colname_time} lt $row->{expires})
253             {
254 0           $dbh->do("delete from $$self{dbTablename} " .
255             "where $colname_key=" . $dbh->quote($self->{cachekey}));
256 0           return undef;
257             }
258              
259 0           $self->{lastrow} = $row;
260 0           return $self;
261             }
262             #--------------------------
263              
264             =item clear
265              
266             =item clear CACHEKEY
267              
268             Invalidates the existing cached data for this key. This can be called
269             as a class method, in which case the cache key argument is required.
270             As an instance method, the instance's key is used if a key is not
271             passed as an argument.
272              
273             =cut
274              
275             sub clear
276             {
277 0     0 1   my $pkg_or_self = shift;
278 0           my $key = shift;
279            
280 0           my $dbh = $global_dbh;
281 0           my $dbtable = $global_dbTablename;
282 0 0         if (ref($pkg_or_self))
283             {
284 0           my $self = $pkg_or_self;
285 0   0       $key ||= $self->{cachekey};
286 0           $dbh = $self->{dbh};
287 0           $dbtable = $self->{dbTablename};
288             }
289 0 0 0       return undef unless ($key && $dbh);
290 0           $dbh->do("update $dbtable set $colname_time='0000-00-00'" .
291             "where $colname_key=" . $dbh->quote($key));
292 0           return $pkg_or_self;
293             }
294             #--------------------------
295              
296             =item toStringCache
297              
298             Returns the cached content, or undef on failure. If isFresh() has
299             already been called, information is recycled from that inquiry.
300              
301             =cut
302              
303             sub toStringCache
304             {
305 0     0 1   my $self = shift;
306              
307 0 0         if (!$self->{lastrow})
308             {
309 0 0         if (!$self->isFresh())
310             {
311 0           return undef;
312             }
313             }
314 0           return $self->{lastrow}->{$colname_data};
315             }
316             #--------------------------
317              
318             =item printCache
319              
320             Prints the cached content. Returns a boolean indicating success or
321             failure. If isFresh() has already been called, information is
322             recycled from that inquiry.
323              
324             =cut
325              
326             sub printCache
327             {
328 0     0 1   my $self = shift;
329              
330 0           my $content = $self->toStringCache();
331 0 0         if (!defined $content)
332             {
333 0           return undef;
334             }
335             else
336             {
337 0           print $content;
338 0           return $self;
339             }
340             }
341             #--------------------------
342              
343             =item save CONTENT
344              
345             Record the content in the database. This is typically only called
346             from within toString(), but is provided here for the benefit of
347             subclasses.
348              
349             =cut
350              
351             sub save
352             {
353 0     0 1   my $self = shift;
354 0           my $string = shift;
355              
356 0 0         return undef if (!defined $self->{cachekey});
357              
358 0           my $dbh = $self->{dbh};
359              
360 0 0         $dbh->do("lock table $$self{dbTablename} write") if ($global_uselock);
361 0           $dbh->do("delete from $$self{dbTablename} " .
362             "where $colname_key=" . $dbh->quote($self->{cachekey}));
363 0           my $result = $dbh->do("insert into $$self{dbTablename} set " .
364             "$colname_key=".$dbh->quote($self->{cachekey})."," .
365             "$colname_time=now()," .
366             "$colname_data=" . $dbh->quote($string));
367 0 0         $dbh->do("unlock table") if ($global_uselock);
368 0 0         if (!$result)
369             {
370 0           &carp("Failed to cache the template string");
371 0           return undef;
372             }
373 0           return $self;
374             }
375             #--------------------------
376              
377             =item toString
378              
379             Same as CAM::Template->toString except that the result is stored in
380             the database.
381              
382             =cut
383              
384             sub toString
385             {
386 0     0 1   my $self = shift;
387              
388 0           my $string = $self->SUPER::toString(@_);
389 0           $self->save($string);
390 0           return $string;
391             }
392             #--------------------------
393              
394             =item print
395              
396             Same as CAM::Template->print except that the result is stored in
397             the database.
398              
399             =cut
400              
401             sub print
402             {
403 0     0 1   my $self = shift;
404              
405             # no work to do. It's all done in toString.
406 0           return $self->SUPER::print(@_);
407             }
408             #--------------------------
409              
410             =item setup
411              
412             =item setup DBIHANDLE, TABLENAME
413              
414             Create a database table for storing cached templates. This is not
415             intended to be called often, if ever. This is a class method. It
416             should be used in a separate script like this:
417              
418             use DBI;
419             use CAM::Template::Cache;
420             my $dbh = DBI->connect(...);
421             CAM::Template::Cache->setup($dbh, "TemplateCache");
422              
423             =cut
424              
425             sub setup
426             {
427 0 0   0 1   if (!ref($_[0]))
428             {
429 0           shift; # skip package name, if applicable
430             }
431 0   0       my $dbh = shift || $global_dbh;
432 0   0       my $tablename = shift || $global_dbTablename;
433              
434 0           my $result = $dbh->do("create table if not exists $tablename (" .
435             "$colname_key text not null," .
436             "$colname_time timestamp," .
437             "$colname_data mediumtext," .
438             "KEY $colname_key ($colname_key(255))" .
439             ")");
440 0           return $result;
441             }
442             #--------------------------
443              
444             =item clean
445              
446             =item clean DBIHANDLE, TABLENAME, SECONDS
447              
448             Cleans out all records older than the specified number of seconds.
449             This is a class method. It should be used in a separate script like
450             this, likely running as a cron:
451              
452             use DBI;
453             use CAM::Template::Cache;
454             my $dbh = DBI->connect(...);
455             CAM::Template::Cache->clean($dbh, "TemplateCache", 2*60*60);
456              
457             =cut
458              
459             sub clean
460             {
461 0 0   0 1   if (!ref($_[0]))
462             {
463 0           shift; # skip package name, if applicable
464             }
465 0   0       my $dbh = shift || $global_dbh;
466 0   0       my $tablename = shift || $global_dbTablename;
467 0   0       my $seconds = shift || $global_expiration;
468              
469 0 0         return 1 if (!$seconds); # no time means no expiration
470              
471 0           return $dbh->do("delete from $tablename " .
472             "where $colname_time < " .
473             "date_add(now(),interval -$seconds second)");
474             }
475             #--------------------------
476              
477             1;
478             __END__