| 1 | package Method::Cached; |
|---|
| 2 | |
|---|
| 3 | use strict; |
|---|
| 4 | use warnings; |
|---|
| 5 | use 5.008007; |
|---|
| 6 | use Attribute::Handlers; |
|---|
| 7 | use Carp qw/croak confess/; |
|---|
| 8 | use UNIVERSAL::require; |
|---|
| 9 | use Method::Cached::KeyRule; |
|---|
| 10 | |
|---|
| 11 | our $VERSION = '0.0199'; |
|---|
| 12 | |
|---|
| 13 | my %_DOMAINS; |
|---|
| 14 | my $_DEFAULT_DOMAIN = { |
|---|
| 15 | storage_class => 'Cache::FastMmap', |
|---|
| 16 | storage_args => [], |
|---|
| 17 | key_rule => 'LIST', |
|---|
| 18 | }; |
|---|
| 19 | |
|---|
| 20 | sub UNIVERSAL::Cached :ATTR(CODE) { |
|---|
| 21 | my ($pkg, $symbol, $code, $options) = @_[0 .. 2, 4]; |
|---|
| 22 | $options = [ $options || () ] unless ref $options eq 'ARRAY'; |
|---|
| 23 | my $name = $pkg . '::' . *{$symbol}{NAME}; |
|---|
| 24 | my ($domain_name, $expires, $key_rule) = _parse_option(@{ $options }); |
|---|
| 25 | no strict 'refs'; |
|---|
| 26 | no warnings 'redefine'; |
|---|
| 27 | *{$name} = sub { |
|---|
| 28 | my $domain = $_DOMAINS{$domain_name} |
|---|
| 29 | ? $_DOMAINS{$domain_name} |
|---|
| 30 | : $_DEFAULT_DOMAIN; |
|---|
| 31 | $key_rule ||= $domain->{key_rule}; |
|---|
| 32 | my $key = Method::Cached::KeyRule::regularize($key_rule, $name, @_); |
|---|
| 33 | my $storage = _storage($domain); |
|---|
| 34 | my $ret = $storage->get($key); |
|---|
| 35 | return wantarray ? @{ $ret } : ${ $ret }[0] if $ret; |
|---|
| 36 | my @ret = ($code->(@_)); |
|---|
| 37 | $storage->set($key, \@ret, $expires || 0); |
|---|
| 38 | return wantarray ? @ret : $ret[0]; |
|---|
| 39 | }; |
|---|
| 40 | } |
|---|
| 41 | |
|---|
| 42 | sub import { |
|---|
| 43 | my ($class, %args) = @_; |
|---|
| 44 | if (exists $args{-domains} && defined $args{-domains}) { |
|---|
| 45 | my $domains = $args{-domains}; |
|---|
| 46 | ref $domains eq 'HASH' or confess '-domains option should be a hash reference'; |
|---|
| 47 | $class->set_domain(%{ $domains }); |
|---|
| 48 | } |
|---|
| 49 | if (exists $args{-default} && defined $args{-default}) { |
|---|
| 50 | my $default = $args{-default}; |
|---|
| 51 | ref $default eq 'HASH' or confess '-default option should be a hash reference'; |
|---|
| 52 | $class->default_domain($default); |
|---|
| 53 | } |
|---|
| 54 | } |
|---|
| 55 | |
|---|
| 56 | sub default_domain { |
|---|
| 57 | my $class = shift; |
|---|
| 58 | $_DEFAULT_DOMAIN = { |
|---|
| 59 | %{ $_DEFAULT_DOMAIN }, |
|---|
| 60 | %{ +shift }, |
|---|
| 61 | }; |
|---|
| 62 | } |
|---|
| 63 | |
|---|
| 64 | sub set_domain { |
|---|
| 65 | my $class = shift; |
|---|
| 66 | while (my ($name, $args) = splice @_, 0, 2) { |
|---|
| 67 | if (exists $_DOMAINS{$name}) { |
|---|
| 68 | warn 'This domain has already been defined: ' . $name; |
|---|
| 69 | next; |
|---|
| 70 | } |
|---|
| 71 | $_DOMAINS{$name} = $args; |
|---|
| 72 | } |
|---|
| 73 | } |
|---|
| 74 | |
|---|
| 75 | sub _parse_option { |
|---|
| 76 | my $domain_name = q{}; |
|---|
| 77 | my $expires = 0; |
|---|
| 78 | my $key_rule = undef; |
|---|
| 79 | if (0 < @_) { |
|---|
| 80 | if ((! defined $_[0]) || ($_[0] !~ /^-?\d+$/)) { |
|---|
| 81 | $domain_name = shift; |
|---|
| 82 | } |
|---|
| 83 | } |
|---|
| 84 | $domain_name ||= q{}; |
|---|
| 85 | if (0 < @_) { |
|---|
| 86 | $expires = |
|---|
| 87 | ($_[0] =~ /^-?\d+$/) ? shift @_ : |
|---|
| 88 | confess 'The first argument or the second argument should be a numeric value.'; |
|---|
| 89 | $key_rule = shift if 0 < @_; |
|---|
| 90 | } |
|---|
| 91 | return ($domain_name, $expires, $key_rule); |
|---|
| 92 | } |
|---|
| 93 | |
|---|
| 94 | sub _storage { |
|---|
| 95 | my $domain = shift; |
|---|
| 96 | $domain->{_storage_instance} && return $domain->{_storage_instance}; |
|---|
| 97 | my $storage_class = $domain->{storage_class} || croak 'storage_class is necessary'; |
|---|
| 98 | my $storage_args = $domain->{storage_args} || undef; |
|---|
| 99 | $storage_class->require || confess "Can't load module: $storage_class"; |
|---|
| 100 | $domain->{_storage_instance} = $storage_class->new(@{ $storage_args || [] }); |
|---|
| 101 | } |
|---|
| 102 | |
|---|
| 103 | 1; |
|---|
| 104 | |
|---|
| 105 | __END__ |
|---|
| 106 | |
|---|
| 107 | =head1 NAME |
|---|
| 108 | |
|---|
| 109 | Method::Cached - The return value of the method is cached to your storage |
|---|
| 110 | |
|---|
| 111 | =head1 SYNOPSIS |
|---|
| 112 | |
|---|
| 113 | package Foo; |
|---|
| 114 | |
|---|
| 115 | use Method::Cached; |
|---|
| 116 | |
|---|
| 117 | sub message :Cached(5) { |
|---|
| 118 | join ':', @_, time, rand |
|---|
| 119 | } |
|---|
| 120 | |
|---|
| 121 | package main; |
|---|
| 122 | use Perl6::Say; |
|---|
| 123 | |
|---|
| 124 | say Foo::message(1); # 1222333848 |
|---|
| 125 | sleep 1; |
|---|
| 126 | say Foo::message(1); # 1222333848 |
|---|
| 127 | |
|---|
| 128 | say Foo::message(5); # 1222333849 |
|---|
| 129 | |
|---|
| 130 | =head1 DESCRIPTION |
|---|
| 131 | |
|---|
| 132 | Method::Cached offers the following mechanisms: |
|---|
| 133 | The return value of the method is stored in storage, and |
|---|
| 134 | the value stored when being execute it next time is returned. |
|---|
| 135 | |
|---|
| 136 | =head1 AUTHOR |
|---|
| 137 | |
|---|
| 138 | Satoshi Ohkubo E<lt>s.ohkubo@gmail.comE<gt> |
|---|
| 139 | |
|---|
| 140 | =head1 LICENSE |
|---|
| 141 | |
|---|
| 142 | This library is free software; you can redistribute it and/or modify |
|---|
| 143 | it under the same terms as Perl itself. |
|---|
| 144 | |
|---|
| 145 | =cut |
|---|