Version 0.12
[catagits/Catalyst-Plugin-Cache.git] / lib / Catalyst / Plugin / Cache.pm
CommitLineData
c28ee69c 1#!/usr/bin/perl
2
3package Catalyst::Plugin::Cache;
5662d9dc 4use Moose;
c28ee69c 5
5662d9dc 6with 'Catalyst::ClassData';
c28ee69c 7
d1cb9db3 8our $VERSION = "0.12";
d6861a7f 9
c28ee69c 10use Scalar::Util ();
23b2d59b 11use Catalyst::Utils ();
c28ee69c 12use Carp ();
ab8e499f 13use MRO::Compat;
a78b4047 14use Scalar::Util qw/ blessed /;
2e4bde89 15use Catalyst::Plugin::Cache::Curried;
16
c28ee69c 17__PACKAGE__->mk_classdata( "_cache_backends" );
5662d9dc 18has _default_curried_cache => (
19 is => 'rw',
20);
21no Moose;
c28ee69c 22
23sub 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
ab8e499f 30 my $ret = $app->maybe::next::method( @_ );
c28ee69c 31
32 $app->setup_cache_backends;
33
34 $ret;
35}
4d098922 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 }
0ba5145c 53}
54
23b2d59b 55sub get_default_cache_backend_config {
56 my ( $app, $name ) = @_;
0ba5145c 57 $app->_get_cache_plugin_config->{backend} || $app->get_cache_backend_config("default");
23b2d59b 58}
59
60sub get_cache_backend_config {
61 my ( $app, $name ) = @_;
0ba5145c 62 $app->_get_cache_plugin_config->{backends}{$name};
23b2d59b 63}
64
65sub setup_cache_backends {
66 my $app = shift;
67
68 # give plugins a chance to find things for themselves
ab8e499f 69 $app->maybe::next::method;
23b2d59b 70
0ba5145c 71 # FIXME - Don't know why the _get_cache_plugin_config method doesn't work here!
4d098922 72 my $conf = $app->_get_cache_plugin_config->{backends};
0ba5145c 73 foreach my $name ( keys %$conf ) {
23b2d59b 74 next if $app->get_cache_backend( $name );
75 $app->setup_generic_cache_backend( $name, $app->get_cache_backend_config( $name ) || {} );
76 }
77
33002c69 78 if ( !$app->get_cache_backend("default") ) {
fba82fef 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
85a748b9 91 }
23b2d59b 92}
93
94sub default_cache_store {
95 my $app = shift;
0ba5145c 96 $app->_get_cache_plugin_config->{default_store} || $app->guess_default_cache_store;
23b2d59b 97}
98
99sub 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
111sub setup_generic_cache_backend {
112 my ( $app, $name, $config ) = @_;
113 my %config = %$config;
114
115 if ( my $class = delete $config{class} ) {
fba82fef 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
23b2d59b 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
887cc08f 139 $app->$method( $name, \%config );
23b2d59b 140 } else {
141 $app->log->warn("Couldn't setup the cache backend named '$name'");
142 }
143}
144
145sub 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
c28ee69c 152
153sub cache {
2e4bde89 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) );
a78b4047 159 } elsif ( !@meta && blessed $c ) {
2e4bde89 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 ) ) );
c28ee69c 162 } else {
2e4bde89 163 return $c->curry_cache( @meta );
c28ee69c 164 }
165}
166
5a00a29b 167sub construct_curried_cache {
168 my ( $c, @meta ) = @_;
169 return $c->curried_cache_class( @meta )->new( @meta );
170}
171
172sub curried_cache_class {
173 my ( $c, @meta ) = @_;
0ba5145c 174 $c->_get_cache_plugin_config->{curried_class} || "Catalyst::Plugin::Cache::Curried";
5a00a29b 175}
176
2e4bde89 177sub curry_cache {
178 my ( $c, @meta ) = @_;
5a00a29b 179 return $c->construct_curried_cache( $c, $c->_cache_caller_meta, @meta );
2e4bde89 180}
181
182sub get_preset_curried {
183 my ( $c, $name ) = @_;
184
0ba5145c 185 if ( ref( my $preset = $c->_get_cache_plugin_config->{profiles}{$name} ) ) {
2e4bde89 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
c28ee69c 195sub get_cache_backend {
196 my ( $c, $name ) = @_;
197 $c->_cache_backends->{$name};
198}
199
200sub register_cache_backend {
201 my ( $c, $name, $backend ) = @_;
202
203 no warnings 'uninitialized';
204 Carp::croak("$backend does not look like a cache backend - "
aed484da 205 . "it must be an object supporting get, set and remove")
206 unless eval { $backend->can("get") && $backend->can("set") && $backend->can("remove") };
c28ee69c 207
208 $c->_cache_backends->{$name} = $backend;
209}
210
211sub unregister_cache_backend {
212 my ( $c, $name ) = @_;
213 delete $c->_cache_backends->{$name};
214}
215
216sub default_cache_backend {
217 my $c = shift;
218 $c->get_cache_backend( "default" ) || $c->temporary_cache_backend;
219}
220
221sub 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
5a00a29b 226sub _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
85a748b9 234 $caller ||= \@info unless $info[0] =~ /Plugin::Cache/;
5a00a29b 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
85a748b9 241 my ( $caller_pkg, $component_pkg, $controller_pkg ) =
242 map { $_ ? $_->[0] : undef } $caller, $component, $controller;
243
5a00a29b 244 return (
85a748b9 245 'caller' => $caller_pkg,
246 component => $component_pkg,
247 controller => $controller_pkg,
248 caller_frame => $caller,
249 component_frame => $component,
250 controller_frame => $controller,
5a00a29b 251 );
252}
253
c28ee69c 254# this gets a shit name so that the plugins can override a good name
255sub choose_cache_backend_wrapper {
256 my ( $c, @meta ) = @_;
257
c627df81 258 Carp::croak("metadata must be an even sized list") unless @meta % 2 == 0;
c28ee69c 259
260 my %meta = @meta;
5a00a29b 261
262 unless ( exists $meta{'caller'} ) {
263 my %caller = $c->_cache_caller_meta;
264 @meta{keys %caller} = values %caller;
265 }
c28ee69c 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
c28ee69c 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
ab8e499f 288sub choose_cache_backend { shift->maybe::next::method( @_ ) } # a convenient fallback
c28ee69c 289
290sub cache_set {
390db05f 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} : () );
c28ee69c 294}
295
296sub cache_get {
297 my ( $c, $key, @meta ) = @_;
298 $c->choose_cache_backend_wrapper( key => $key, @meta )->get( $key );
299}
300
aed484da 301sub cache_remove {
c28ee69c 302 my ( $c, $key, @meta ) = @_;
aed484da 303 $c->choose_cache_backend_wrapper( key => $key, @meta )->remove( $key );
c28ee69c 304}
305
26dcff5b 306sub 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
c28ee69c 324__PACKAGE__;
325
326__END__
327
328=pod
329
330=head1 NAME
331
85a748b9 332Catalyst::Plugin::Cache - Flexible caching support for Catalyst.
c28ee69c 333
334=head1 SYNOPSIS
335
5a00a29b 336 use Catalyst qw/
337 Cache
338 /;
339
340 # configure a backend or use a store plugin
0ba5145c 341 __PACKAGE__->config->{'Plugin::Cache'}{backend} = {
85a748b9 342 class => "Cache::Bounded",
fba82fef 343 # ... params for Cache::Bounded...
85a748b9 344 };
5a00a29b 345
fba82fef 346 # typical example for Cache::Memcached::libmemcached
0ba5145c 347 __PACKAGE__->config->{'Plugin::Cache'}{backend} = {
fba82fef 348 class => "Cache::Memcached::libmemcached",
349 servers => ['127.0.0.1:11211'],
350 debug => 2,
351 };
352
353
c627df81 354 # In a controller:
5a00a29b 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 ) ) {
c627df81 364 # ... calculate result ...
5a00a29b 365 $c->cache->set( $id, $result );
366 }
367 };
c28ee69c 368
369=head1 DESCRIPTION
370
c627df81 371This plugin gives you access to a variety of systems for caching
372data. It allows you to use a very simple configuration API, while
373maintaining the possibility of flexibility when you need it later.
5a00a29b 374
c627df81 375Among its features are support for multiple backends, segmentation based
376on component or controller, keyspace partitioning, and so more, in
377various subsidiary plugins.
5a00a29b 378
85a748b9 379=head1 METHODS
380
381=over 4
382
383=item cache $profile_name
384
385=item cache %meta
386
c627df81 387Return a curried object with metadata from C<$profile_name> or as
388explicitly specified.
85a748b9 389
c627df81 390If a profile by the name C<$profile_name> doesn't exist, but a backend
391object by that name does exist, the backend will be returned instead,
392since the interface for curried caches and backends is almost identical.
85a748b9 393
c627df81 394This method can also be called without arguments, in which case is
395treated as though the C<%meta> hash was empty.
85a748b9 396
c627df81 397See L</METADATA> for details.
85a748b9 398
399=item curry_cache %meta
400
c627df81 401Return a L<Catalyst::Plugin::Cache::Curried> object, curried with C<%meta>.
85a748b9 402
c627df81 403See L</METADATA> for details.
85a748b9 404
405=item cache_set $key, $value, %meta
406
407=item cache_get $key, %meta
408
409=item cache_remove $key, %meta
410
26dcff5b 411=item cache_compute $key, $code, %meta
412
c627df81 413These cache operations will call L<choose_cache_backend> with %meta, and
26dcff5b 414then call C<set>, C<get>, C<remove>, or C<compute> on the resulting backend
415object.
416
417If the backend object does not support C<compute> then we emulate it by
418calling L<cache_get>, and if the returned value is undefined we call the passed
419code reference, stores the returned value with L<cache_set>, and then returns
420the value. Inspired by L<CHI>.
85a748b9 421
422=item choose_cache_backend %meta
423
c627df81 424Select a backend object. This should return undef if no specific backend
425was selected - its caller will handle getting C<default_cache_backend>
426on its own.
85a748b9 427
428This method is typically used by plugins.
429
430=item get_cache_backend $name
431
432Get a backend object by name.
433
434=item default_cache_backend
435
436Return the default backend object.
437
438=item temporary_cache_backend
439
c627df81 440When no default cache backend is configured this method might return a
441backend known to work well with the current L<Catalyst::Engine>. This is
442a stub.
85a748b9 443
444=item
445
446=back
447
c627df81 448=head1 METADATA
85a748b9 449
450=head2 Introduction
451
c627df81 452Whenever you set or retrieve a key you may specify additional metadata
453that will be used to select a specific backend.
85a748b9 454
455This metadata is very freeform, and the only key that has any meaning by
456default is the C<backend> key which can be used to explicitly choose a backend
457by name.
458
c627df81 459The C<choose_cache_backend> method can be overridden in order to
460facilitate more intelligent backend selection. For example,
461L<Catalyst::Plugin::Cache::Choose::KeyRegexes> overrides that method to
462select a backend based on key regexes.
85a748b9 463
c627df81 464Another example is a L<Catalyst::Plugin::Cache::ControllerNamespacing>,
465which wraps backends in objects that perform key mangling, in order to
466keep caches namespaced per controller.
85a748b9 467
468However, this is generally left as a hook for larger, more complex
c627df81 469applications. Most configurations should make due XXXX
85a748b9 470
c627df81 471The simplest way to dynamically select a backend is based on the
472L</Cache Profiles> configuration.
85a748b9 473
474=head2 Meta Data Keys
475
476C<choose_cache_backend> is called with some default keys.
477
478=over 4
479
480=item key
481
c627df81 482Supplied by C<cache_get>, C<cache_set>, and C<cache_remove>.
85a748b9 483
484=item value
485
c627df81 486Supplied by C<cache_set>.
85a748b9 487
488=item caller
489
490The package name of the innermost caller that doesn't match
491C<qr/Plugin::Cache/>.
492
493=item caller_frame
494
495The entire C<caller($i)> frame of C<caller>.
496
497=item component
498
c627df81 499The package name of the innermost caller who C<isa>
500L<Catalyst::Component>.
85a748b9 501
502=item component_frame
503
504This entire C<caller($i)> frame of C<component>.
505
506=item controller
507
c627df81 508The package name of the innermost caller who C<isa>
509L<Catalyst::Controller>.
85a748b9 510
511=item controller_frame
512
513This entire C<caller($i)> frame of C<controller>.
514
515=back
516
c627df81 517=head2 Metadata Currying
85a748b9 518
c627df81 519In order to avoid specifying C<%meta> over and over again you may call
520C<cache> or C<curry_cache> with C<%meta> once, and get back a B<curried
521cache object>. This object responds to the methods C<get>, C<set>, and
522C<remove>, by appending its captured metadata and delegating them to
523C<cache_get>, C<cache_set>, and C<cache_remove>.
85a748b9 524
525This is simpler than it sounds.
526
527Here 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
535And here is an example without using currying:
536
537 $c->cache_set( $key, $value, %meta );
538
539 $c->cache_get( $key, %meta );
540
541See L<Catalyst::Plugin::Cache::Curried> for details.
542
0f0237aa 543=head1 CONFIGURATION
544
0ba5145c 545 $c->config->{'Plugin::Cache'} = {
85a748b9 546 ...
547 };
0f0237aa 548
c627df81 549All configuration parameters should be provided in a hash reference
0ba5145c 550under the C<Plugin::Cache> key in the C<config> hash.
0f0237aa 551
85a748b9 552=head2 Backend Configuration
553
554Configuring backend objects is done by adding hash entries under the
c627df81 555C<backends> key in the main config.
85a748b9 556
c627df81 557A special case is that the hash key under the C<backend> (singular) key
558of the main config is assumed to be the backend named C<default>.
85a748b9 559
0f0237aa 560=over 4
561
562=item class
563
85a748b9 564Instantiate a backend from a L<Cache> compatible class. E.g.
0f0237aa 565
0ba5145c 566 $c->config->{'Plugin::Cache'}{backends}{small_things} = {
85a748b9 567 class => "Cache::Bounded",
568 interval => 1000,
569 size => 10000,
570 };
571
0ba5145c 572 $c->config->{'Plugin::Cache'}{backends}{large_things} = {
bd2e2f72 573 class => "Cache::Memcached",
85a748b9 574 data => '1.2.3.4:1234',
575 };
0f0237aa 576
85a748b9 577The options in the hash are passed to the class's C<new> method.
0f0237aa 578
85a748b9 579The class will be C<required> as necessary during setup time.
0f0237aa 580
85a748b9 581=item store
0f0237aa 582
c627df81 583Instantiate a backend using a store plugin, e.g.
0f0237aa 584
0ba5145c 585 $c->config->{'Plugin::Cache'}{backend} = {
85a748b9 586 store => "FastMmap",
587 };
0f0237aa 588
c627df81 589Store plugins typically require less configuration because they are
590specialized for L<Catalyst> applications. For example
85a748b9 591L<Catalyst::Plugin::Cache::Store::FastMmap> will specify a default
c627df81 592C<share_file>, and additionally use a subclass of L<Cache::FastMmap>
593that can also store non reference data.
85a748b9 594
595The store plugin must be loaded.
596
597=back
0f0237aa 598
85a748b9 599=head2 Cache Profiles
600
601=over 4
0f0237aa 602
603=item profiles
604
c627df81 605Supply your own predefined profiles for cache metadata, when using the
606C<cache> method.
85a748b9 607
608For example when you specify
609
0ba5145c 610 $c->config->{'Plugin::Cache'}{profiles}{thumbnails} = {
85a748b9 611 backend => "large_things",
612 };
613
614And then get a cache object like this:
615
616 $c->cache("thumbnails");
617
618It is the same as if you had done:
619
620 $c->cache( backend => "large_things" );
621
622=back
623
c627df81 624=head2 Miscellaneous Configuration
85a748b9 625
626=over 4
627
628=item default_store
629
c627df81 630When you do not specify a C<store> parameter in the backend
631configuration this one will be used instead. This configuration
632parameter is not necessary if only one store plugin is loaded.
0f0237aa 633
634=back
635
636=head1 TERMINOLOGY
23b2d59b 637
638=over 4
639
640=item backend
641
642An object that responds to the methods detailed in
643L<Catalyst::Plugin::Cache::Backend> (or more).
644
645=item store
646
c627df81 647A plugin that provides backends of a certain type. This is a bit like a
648factory.
23b2d59b 649
0f0237aa 650=item cache
651
652Stored key/value pairs of data for easy re-access.
653
c627df81 654=item metadata
85a748b9 655
c627df81 656"Extra" information about the item being stored, which can be used to
657locate an appropriate backend.
85a748b9 658
23b2d59b 659=item curried cache
660
0f0237aa 661 my $cache = $c->cache(type => 'thumbnails');
662 $cache->set('pic01', $thumbnaildata);
663
c627df81 664A cache which has been pre-configured with a particular set of
665namespacing data. In the example the cache returned could be one
666specifically tuned for storing thumbnails.
0f0237aa 667
c627df81 668An object that responds to C<get>, C<set>, and C<remove>, and will
669automatically add metadata to calls to C<< $c->cache_get >>, etc.
23b2d59b 670
671=back
672
772299b1 673=head1 SEE ALSO
674
c627df81 675L<Cache> - the generic cache API on CPAN.
772299b1 676
677L<Catalyst::Plugin::Cache::Store> - how to write a store plugin.
678
679L<Catalyst::Plugin::Cache::Curried> - the interface for curried caches.
680
681L<Catalyst::Plugin::Cache::Choose::KeyRegexes> - choose a backend based on
682regex matching on the keys. Can be used to partition the keyspace.
683
684L<Catalyst::Plugin::Cache::ControllerNamespacing> - wrap backend objects in a
c627df81 685name mangler so that every controller gets its own keyspace.
772299b1 686
271f5106 687=head1 AUTHOR
688
689Yuval Kogman, C<nothingmuch@woobling.org>
690
dc5fda2b 691Jos Boumans, C<kane@cpan.org>
692
271f5106 693=head1 COPYRIGHT & LICENSE
c28ee69c 694
271f5106 695Copyright (c) Yuval Kogman, 2006. All rights reserved.
696
697This library is free software, you can redistribute it and/or modify it under
698the same terms as Perl itself, as well as under the terms of the MIT license.
699
700=cut
c28ee69c 701