File Coverage

blib/lib/Catalyst/Plugin/Cache.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Catalyst::Plugin::Cache;
4 5     5   145384 use Moose;
  0            
  0            
5              
6             with 'Catalyst::ClassData';
7              
8             our $VERSION = "0.12";
9              
10             use Scalar::Util ();
11             use Catalyst::Utils ();
12             use Carp ();
13             use MRO::Compat;
14             use Scalar::Util qw/ blessed /;
15             use Catalyst::Plugin::Cache::Curried;
16              
17             __PACKAGE__->mk_classdata( "_cache_backends" );
18             has _default_curried_cache => (
19             is => 'rw',
20             );
21             no Moose;
22              
23             sub setup {
24             my $app = shift;
25              
26             # set it once per app, not once per plugin,
27             # and don't overwrite if some plugin was wicked
28             $app->_cache_backends({}) unless $app->_cache_backends;
29              
30             my $ret = $app->maybe::next::method( @_ );
31              
32             $app->setup_cache_backends;
33              
34             $ret;
35             }
36             {
37             my %has_warned_for;
38             sub _get_cache_plugin_config {
39             my ($app) = @_;
40             my $config = $app->config->{'Plugin::Cache'};
41             if (!$config) {
42             $config = $app->config->{cache};
43             my $appname = ref($app);
44             if (! $has_warned_for{$appname}++ ) {
45             $app->log->warn($config ?
46             'Catalyst::Plugin::Cache config found in deprecated $c->config->{cache}, please move to $c->config->{"Plugin::Cache"}.'
47             : 'Catalyst::Plugin::Cache config not found, using empty config!'
48             );
49             }
50             }
51             return $config || {};
52             }
53             }
54              
55             sub get_default_cache_backend_config {
56             my ( $app, $name ) = @_;
57             $app->_get_cache_plugin_config->{backend} || $app->get_cache_backend_config("default");
58             }
59              
60             sub get_cache_backend_config {
61             my ( $app, $name ) = @_;
62             $app->_get_cache_plugin_config->{backends}{$name};
63             }
64              
65             sub setup_cache_backends {
66             my $app = shift;
67              
68             # give plugins a chance to find things for themselves
69             $app->maybe::next::method;
70              
71             # FIXME - Don't know why the _get_cache_plugin_config method doesn't work here!
72             my $conf = $app->_get_cache_plugin_config->{backends};
73             foreach my $name ( keys %$conf ) {
74             next if $app->get_cache_backend( $name );
75             $app->setup_generic_cache_backend( $name, $app->get_cache_backend_config( $name ) || {} );
76             }
77              
78             if ( !$app->get_cache_backend("default") ) {
79             ### XXX currently we dont have a fallback scenario
80             ### so die here with the error message. Once we have
81             ### an in memory fallback, we may consider silently
82             ### logging the error and falling back to that.
83             ### If we dont die here, the app will silently start
84             ### up and then explode at the first cache->get or
85             ### cache->set request with a FIXME error
86             #local $@;
87             #eval {
88             $app->setup_generic_cache_backend( default => $app->get_default_cache_backend_config || {} );
89             #};
90            
91             }
92             }
93              
94             sub default_cache_store {
95             my $app = shift;
96             $app->_get_cache_plugin_config->{default_store} || $app->guess_default_cache_store;
97             }
98              
99             sub guess_default_cache_store {
100             my $app = shift;
101              
102             my @stores = map { /Cache::Store::(.*)$/ ? $1 : () } $app->registered_plugins;
103              
104             if ( @stores == 1 ) {
105             return $stores[0];
106             } else {
107             Carp::croak "You must configure a default store type unless you use exactly one store plugin.";
108             }
109             }
110              
111             sub setup_generic_cache_backend {
112             my ( $app, $name, $config ) = @_;
113             my %config = %$config;
114              
115             if ( my $class = delete $config{class} ) {
116            
117             ### try as list and as hashref, collect the
118             ### error if things go wrong
119             ### if all goes well, exit the loop
120             my @errors;
121             for my $aref ( [%config], [\%config] ) {
122             eval { $app->setup_cache_backend_by_class(
123             $name, $class, @$aref
124             );
125             } ? do { @errors = (); last }
126             : push @errors, "\t$@";
127             }
128            
129             ### and die with the errors if we have any
130             die "Couldn't construct $class with either list style or hash ref style param passing:\n @errors" if @errors;
131            
132             } elsif ( my $store = delete $config->{store} || $app->default_cache_store ) {
133             my $method = lc("setup_${store}_cache_backend");
134              
135             Carp::croak "You must load the $store cache store plugin (if it exists). ".
136             "Please consult the Catalyst::Plugin::Cache documentation on how to configure hetrogeneous stores."
137             unless $app->can($method);
138              
139             $app->$method( $name, \%config );
140             } else {
141             $app->log->warn("Couldn't setup the cache backend named '$name'");
142             }
143             }
144              
145             sub setup_cache_backend_by_class {
146             my ( $app, $name, $class, @args ) = @_;
147             Catalyst::Utils::ensure_class_loaded( $class );
148             $app->register_cache_backend( $name => $class->new( @args ) );
149             }
150              
151             # end of spaghetti setup DWIM
152              
153             sub cache {
154             my ( $c, @meta ) = @_;
155              
156             if ( @meta == 1 ) {
157             my $name = $meta[0];
158             return ( $c->get_preset_curried($name) || $c->get_cache_backend($name) );
159             } elsif ( !@meta && blessed $c ) {
160             # be nice and always return the same one for the simplest case
161             return ( $c->_default_curried_cache || $c->_default_curried_cache( $c->curry_cache( @meta ) ) );
162             } else {
163             return $c->curry_cache( @meta );
164             }
165             }
166              
167             sub construct_curried_cache {
168             my ( $c, @meta ) = @_;
169             return $c->curried_cache_class( @meta )->new( @meta );
170             }
171              
172             sub curried_cache_class {
173             my ( $c, @meta ) = @_;
174             $c->_get_cache_plugin_config->{curried_class} || "Catalyst::Plugin::Cache::Curried";
175             }
176              
177             sub curry_cache {
178             my ( $c, @meta ) = @_;
179             return $c->construct_curried_cache( $c, $c->_cache_caller_meta, @meta );
180             }
181              
182             sub get_preset_curried {
183             my ( $c, $name ) = @_;
184              
185             if ( ref( my $preset = $c->_get_cache_plugin_config->{profiles}{$name} ) ) {
186             return $preset if Scalar::Util::blessed($preset);
187              
188             my @meta = ( ( ref $preset eq "HASH" ) ? %$preset : @$preset );
189             return $c->curry_cache( @meta );
190             }
191              
192             return;
193             }
194              
195             sub get_cache_backend {
196             my ( $c, $name ) = @_;
197             $c->_cache_backends->{$name};
198             }
199              
200             sub register_cache_backend {
201             my ( $c, $name, $backend ) = @_;
202              
203             no warnings 'uninitialized';
204             Carp::croak("$backend does not look like a cache backend - "
205             . "it must be an object supporting get, set and remove")
206             unless eval { $backend->can("get") && $backend->can("set") && $backend->can("remove") };
207              
208             $c->_cache_backends->{$name} = $backend;
209             }
210              
211             sub unregister_cache_backend {
212             my ( $c, $name ) = @_;
213             delete $c->_cache_backends->{$name};
214             }
215              
216             sub default_cache_backend {
217             my $c = shift;
218             $c->get_cache_backend( "default" ) || $c->temporary_cache_backend;
219             }
220              
221             sub temporary_cache_backend {
222             my $c = shift;
223             die "FIXME - make up an in memory cache backend, that hopefully works well for the current engine";
224             }
225              
226             sub _cache_caller_meta {
227             my $c = shift;
228              
229             my ( $caller, $component, $controller );
230            
231             for my $i ( 0 .. 15 ) { # don't look to far
232             my @info = caller(2 + $i) or last;
233              
234             $caller ||= \@info unless $info[0] =~ /Plugin::Cache/;
235             $component ||= \@info if $info[0]->isa("Catalyst::Component");
236             $controller ||= \@info if $info[0]->isa("Catalyst::Controller");
237            
238             last if $caller && $component && $controller;
239             }
240              
241             my ( $caller_pkg, $component_pkg, $controller_pkg ) =
242             map { $_ ? $_->[0] : undef } $caller, $component, $controller;
243              
244             return (
245             'caller' => $caller_pkg,
246             component => $component_pkg,
247             controller => $controller_pkg,
248             caller_frame => $caller,
249             component_frame => $component,
250             controller_frame => $controller,
251             );
252             }
253              
254             # this gets a shit name so that the plugins can override a good name
255             sub choose_cache_backend_wrapper {
256             my ( $c, @meta ) = @_;
257              
258             Carp::croak("metadata must be an even sized list") unless @meta % 2 == 0;
259              
260             my %meta = @meta;
261              
262             unless ( exists $meta{'caller'} ) {
263             my %caller = $c->_cache_caller_meta;
264             @meta{keys %caller} = values %caller;
265             }
266            
267             # allow the cache client to specify who it wants to cache with (but loeave room for a hook)
268             if ( exists $meta{backend} ) {
269             if ( Scalar::Util::blessed($meta{backend}) ) {
270             return $meta{backend};
271             } else {
272             return $c->get_cache_backend( $meta{backend} ) || $c->default_cache_backend;
273             }
274             };
275            
276             if ( my $chosen = $c->choose_cache_backend( %meta ) ) {
277             $chosen = $c->get_cache_backend( $chosen ) unless Scalar::Util::blessed($chosen); # if it's a name find it
278             return $chosen if Scalar::Util::blessed($chosen); # only return if it was an object or name lookup worked
279              
280             # FIXME
281             # die "no such backend"?
282             # currently, we fall back to default
283             }
284            
285             return $c->default_cache_backend;
286             }
287              
288             sub choose_cache_backend { shift->maybe::next::method( @_ ) } # a convenient fallback
289              
290             sub cache_set {
291             my ( $c, $key, $value, %meta ) = @_;
292             $c->choose_cache_backend_wrapper( key => $key, value => $value, %meta )
293             ->set( $key, $value, exists $meta{expires} ? $meta{expires} : () );
294             }
295              
296             sub cache_get {
297             my ( $c, $key, @meta ) = @_;
298             $c->choose_cache_backend_wrapper( key => $key, @meta )->get( $key );
299             }
300              
301             sub cache_remove {
302             my ( $c, $key, @meta ) = @_;
303             $c->choose_cache_backend_wrapper( key => $key, @meta )->remove( $key );
304             }
305              
306             sub cache_compute {
307             my ($c, $key, $code, %meta) = @_;
308              
309             my $backend = $c->choose_cache_backend_wrapper( key => $key, %meta );
310             if ($backend->can('compute')) {
311             return $backend->compute( $key, $code, exists $meta{expires} ? $meta{expires} : () );
312             }
313              
314             Carp::croak "must specify key and code" unless defined($key) && defined($code);
315              
316             my $value = $c->cache_get( $key, %meta );
317             if ( !defined $value ) {
318             $value = $code->();
319             $c->cache_set( $key, $value, %meta );
320             }
321             return $value;
322             }
323              
324             __PACKAGE__;
325              
326             __END__
327              
328             =pod
329              
330             =head1 NAME
331              
332             Catalyst::Plugin::Cache - Flexible caching support for Catalyst.
333              
334             =head1 SYNOPSIS
335              
336             use Catalyst qw/
337             Cache
338             /;
339              
340             # configure a backend or use a store plugin
341             __PACKAGE__->config->{'Plugin::Cache'}{backend} = {
342             class => "Cache::Bounded",
343             # ... params for Cache::Bounded...
344             };
345              
346             # typical example for Cache::Memcached::libmemcached
347             __PACKAGE__->config->{'Plugin::Cache'}{backend} = {
348             class => "Cache::Memcached::libmemcached",
349             servers => ['127.0.0.1:11211'],
350             debug => 2,
351             };
352              
353              
354             # In a controller:
355              
356             sub foo : Local {
357             my ( $self, $c, $id ) = @_;
358              
359             my $cache = $c->cache;
360              
361             my $result;
362              
363             unless ( $result = $cache->get( $id ) ) {
364             # ... calculate result ...
365             $c->cache->set( $id, $result );
366             }
367             };
368              
369             =head1 DESCRIPTION
370              
371             This plugin gives you access to a variety of systems for caching
372             data. It allows you to use a very simple configuration API, while
373             maintaining the possibility of flexibility when you need it later.
374              
375             Among its features are support for multiple backends, segmentation based
376             on component or controller, keyspace partitioning, and so more, in
377             various subsidiary plugins.
378              
379             =head1 METHODS
380              
381             =over 4
382              
383             =item cache $profile_name
384              
385             =item cache %meta
386              
387             Return a curried object with metadata from C<$profile_name> or as
388             explicitly specified.
389              
390             If a profile by the name C<$profile_name> doesn't exist, but a backend
391             object by that name does exist, the backend will be returned instead,
392             since the interface for curried caches and backends is almost identical.
393              
394             This method can also be called without arguments, in which case is
395             treated as though the C<%meta> hash was empty.
396              
397             See L</METADATA> for details.
398              
399             =item curry_cache %meta
400              
401             Return a L<Catalyst::Plugin::Cache::Curried> object, curried with C<%meta>.
402              
403             See L</METADATA> for details.
404              
405             =item cache_set $key, $value, %meta
406              
407             =item cache_get $key, %meta
408              
409             =item cache_remove $key, %meta
410              
411             =item cache_compute $key, $code, %meta
412              
413             These cache operations will call L<choose_cache_backend> with %meta, and
414             then call C<set>, C<get>, C<remove>, or C<compute> on the resulting backend
415             object.
416              
417             If the backend object does not support C<compute> then we emulate it by
418             calling L<cache_get>, and if the returned value is undefined we call the passed
419             code reference, stores the returned value with L<cache_set>, and then returns
420             the value. Inspired by L<CHI>.
421              
422             =item choose_cache_backend %meta
423              
424             Select a backend object. This should return undef if no specific backend
425             was selected - its caller will handle getting C<default_cache_backend>
426             on its own.
427              
428             This method is typically used by plugins.
429              
430             =item get_cache_backend $name
431              
432             Get a backend object by name.
433              
434             =item default_cache_backend
435              
436             Return the default backend object.
437              
438             =item temporary_cache_backend
439              
440             When no default cache backend is configured this method might return a
441             backend known to work well with the current L<Catalyst::Engine>. This is
442             a stub.
443              
444             =item
445              
446             =back
447              
448             =head1 METADATA
449              
450             =head2 Introduction
451              
452             Whenever you set or retrieve a key you may specify additional metadata
453             that will be used to select a specific backend.
454              
455             This metadata is very freeform, and the only key that has any meaning by
456             default is the C<backend> key which can be used to explicitly choose a backend
457             by name.
458              
459             The C<choose_cache_backend> method can be overridden in order to
460             facilitate more intelligent backend selection. For example,
461             L<Catalyst::Plugin::Cache::Choose::KeyRegexes> overrides that method to
462             select a backend based on key regexes.
463              
464             Another example is a L<Catalyst::Plugin::Cache::ControllerNamespacing>,
465             which wraps backends in objects that perform key mangling, in order to
466             keep caches namespaced per controller.
467              
468             However, this is generally left as a hook for larger, more complex
469             applications. Most configurations should make due XXXX
470              
471             The simplest way to dynamically select a backend is based on the
472             L</Cache Profiles> configuration.
473              
474             =head2 Meta Data Keys
475              
476             C<choose_cache_backend> is called with some default keys.
477              
478             =over 4
479              
480             =item key
481              
482             Supplied by C<cache_get>, C<cache_set>, and C<cache_remove>.
483              
484             =item value
485              
486             Supplied by C<cache_set>.
487              
488             =item caller
489              
490             The package name of the innermost caller that doesn't match
491             C<qr/Plugin::Cache/>.
492              
493             =item caller_frame
494              
495             The entire C<caller($i)> frame of C<caller>.
496              
497             =item component
498              
499             The package name of the innermost caller who C<isa>
500             L<Catalyst::Component>.
501              
502             =item component_frame
503              
504             This entire C<caller($i)> frame of C<component>.
505              
506             =item controller
507              
508             The package name of the innermost caller who C<isa>
509             L<Catalyst::Controller>.
510              
511             =item controller_frame
512              
513             This entire C<caller($i)> frame of C<controller>.
514              
515             =back
516              
517             =head2 Metadata Currying
518              
519             In order to avoid specifying C<%meta> over and over again you may call
520             C<cache> or C<curry_cache> with C<%meta> once, and get back a B<curried
521             cache object>. This object responds to the methods C<get>, C<set>, and
522             C<remove>, by appending its captured metadata and delegating them to
523             C<cache_get>, C<cache_set>, and C<cache_remove>.
524              
525             This is simpler than it sounds.
526              
527             Here is an example using currying:
528              
529             my $cache = $c->cache( %meta ); # cache is curried
530              
531             $cache->set( $key, $value );
532              
533             $cache->get( $key );
534              
535             And here is an example without using currying:
536              
537             $c->cache_set( $key, $value, %meta );
538              
539             $c->cache_get( $key, %meta );
540              
541             See L<Catalyst::Plugin::Cache::Curried> for details.
542              
543             =head1 CONFIGURATION
544              
545             $c->config->{'Plugin::Cache'} = {
546             ...
547             };
548              
549             All configuration parameters should be provided in a hash reference
550             under the C<Plugin::Cache> key in the C<config> hash.
551              
552             =head2 Backend Configuration
553              
554             Configuring backend objects is done by adding hash entries under the
555             C<backends> key in the main config.
556              
557             A special case is that the hash key under the C<backend> (singular) key
558             of the main config is assumed to be the backend named C<default>.
559              
560             =over 4
561              
562             =item class
563              
564             Instantiate a backend from a L<Cache> compatible class. E.g.
565              
566             $c->config->{'Plugin::Cache'}{backends}{small_things} = {
567             class => "Cache::Bounded",
568             interval => 1000,
569             size => 10000,
570             };
571            
572             $c->config->{'Plugin::Cache'}{backends}{large_things} = {
573             class => "Cache::Memcached",
574             data => '1.2.3.4:1234',
575             };
576              
577             The options in the hash are passed to the class's C<new> method.
578              
579             The class will be C<required> as necessary during setup time.
580              
581             =item store
582              
583             Instantiate a backend using a store plugin, e.g.
584              
585             $c->config->{'Plugin::Cache'}{backend} = {
586             store => "FastMmap",
587             };
588              
589             Store plugins typically require less configuration because they are
590             specialized for L<Catalyst> applications. For example
591             L<Catalyst::Plugin::Cache::Store::FastMmap> will specify a default
592             C<share_file>, and additionally use a subclass of L<Cache::FastMmap>
593             that can also store non reference data.
594              
595             The store plugin must be loaded.
596              
597             =back
598              
599             =head2 Cache Profiles
600              
601             =over 4
602              
603             =item profiles
604              
605             Supply your own predefined profiles for cache metadata, when using the
606             C<cache> method.
607              
608             For example when you specify
609              
610             $c->config->{'Plugin::Cache'}{profiles}{thumbnails} = {
611             backend => "large_things",
612             };
613              
614             And then get a cache object like this:
615              
616             $c->cache("thumbnails");
617              
618             It is the same as if you had done:
619              
620             $c->cache( backend => "large_things" );
621              
622             =back
623              
624             =head2 Miscellaneous Configuration
625              
626             =over 4
627              
628             =item default_store
629              
630             When you do not specify a C<store> parameter in the backend
631             configuration this one will be used instead. This configuration
632             parameter is not necessary if only one store plugin is loaded.
633              
634             =back
635              
636             =head1 TERMINOLOGY
637              
638             =over 4
639              
640             =item backend
641              
642             An object that responds to the methods detailed in
643             L<Catalyst::Plugin::Cache::Backend> (or more).
644              
645             =item store
646              
647             A plugin that provides backends of a certain type. This is a bit like a
648             factory.
649              
650             =item cache
651              
652             Stored key/value pairs of data for easy re-access.
653              
654             =item metadata
655              
656             "Extra" information about the item being stored, which can be used to
657             locate an appropriate backend.
658              
659             =item curried cache
660              
661             my $cache = $c->cache(type => 'thumbnails');
662             $cache->set('pic01', $thumbnaildata);
663              
664             A cache which has been pre-configured with a particular set of
665             namespacing data. In the example the cache returned could be one
666             specifically tuned for storing thumbnails.
667              
668             An object that responds to C<get>, C<set>, and C<remove>, and will
669             automatically add metadata to calls to C<< $c->cache_get >>, etc.
670              
671             =back
672              
673             =head1 SEE ALSO
674              
675             L<Cache> - the generic cache API on CPAN.
676              
677             L<Catalyst::Plugin::Cache::Store> - how to write a store plugin.
678              
679             L<Catalyst::Plugin::Cache::Curried> - the interface for curried caches.
680              
681             L<Catalyst::Plugin::Cache::Choose::KeyRegexes> - choose a backend based on
682             regex matching on the keys. Can be used to partition the keyspace.
683              
684             L<Catalyst::Plugin::Cache::ControllerNamespacing> - wrap backend objects in a
685             name mangler so that every controller gets its own keyspace.
686              
687             =head1 AUTHOR
688              
689             Yuval Kogman, C<nothingmuch@woobling.org>
690              
691             Jos Boumans, C<kane@cpan.org>
692              
693             =head1 COPYRIGHT & LICENSE
694              
695             Copyright (c) Yuval Kogman, 2006. All rights reserved.
696              
697             This library is free software, you can redistribute it and/or modify it under
698             the same terms as Perl itself, as well as under the terms of the MIT license.
699              
700             =cut
701