File Coverage

blib/lib/Class/Proxy/Lite.pm
Criterion Covered Total %
statement 38 40 95.0
branch 10 18 55.5
condition 4 15 26.6
subroutine 7 7 100.0
pod n/a
total 59 80 73.7


line stmt bran cond sub pod time code
1             package Class::Proxy::Lite;
2              
3 2     2   65322 use strict;
  2         5  
  2         91  
4 2     2   12 use vars qw($VERSION);
  2         3  
  2         148  
5              
6             $VERSION = '1.01';
7              
8 2     2   12 use constant TOKEN => 0;
  2         8  
  2         163  
9 2     2   11 use constant RESOLVER => 1;
  2         4  
  2         76  
10 2     2   10 use constant CACHED => 2;
  2         3  
  2         803  
11              
12             sub AUTOLOAD {
13 6     6   3887 my $self_or_class = shift();
14            
15 6         42 (my $method = $Class::Proxy::Lite::AUTOLOAD) =~ s/(.*):://;
16            
17 6         15 my $is_class_method = ref($self_or_class) eq '';
18            
19             # --- Check for special cases and non-references
20 6 50       22 return undef if $method eq 'DESTROY';
21 6 50 33     23 return undef if $method eq 'import' and $is_class_method;
22            
23             # --- XXX Try to deal with can and isa?
24            
25             # --- Emulate class method new()
26 6 100       18 if ($is_class_method) {
27 2 50 33     26 die "Can't call a class method on Class::Proxy::Lite or a subclass"
28             unless $method eq 'new'
29             and UNIVERSAL::isa($self_or_class, __PACKAGE__);
30             # --- Create a proxy
31 2         7 my ($token, $resolver, $cached) = @_;
32 2         4 my @self;
33 2         5 $self[TOKEN] = $token;
34 2         5 $self[RESOLVER] = $resolver;
35 2 50       7 $self[CACHED] = $cached if defined $cached;
36 2         12 return bless \@self, $self_or_class;
37             }
38            
39             # --- Reject attempts to call functions in Class::Proxy::Lite
40 4 50 33     19 die "No such function: $Class::Proxy::Lite::AUTOLOAD"
41             if $is_class_method
42             and $self_or_class ne 'Class::Proxy::Lite';
43            
44             # --- Resolve the token
45 4         24 my ($token, $resolver, $cached) = @$self_or_class[TOKEN, RESOLVER, CACHED];
46 4         8 my $target;
47 4 50       13 if ($cached) {
48 0   0     0 $target = $$cached ||= $resolver->($token);
49             }
50             else {
51 4         13 $target = $resolver->($token);
52             }
53 4 50 33     41 die "Couldn't resolve proxy target"
54             unless defined $target and ref $target;
55              
56             # --- These don't work -- see under `Default UNIVERSAL methods'
57             # in perlobj for an explanation
58             # return ref($target)->isa(shift) if $method eq 'isa';
59             # return ref($target)->can(shift) if $method eq 'can';
60            
61             # --- Invoke the method of the same name on the target object
62             # goto &{UNIVERSAL::can($target, $method)} won't work,
63             # because $target might rely upon its own AUTOLOAD!!
64 2     2   11 no strict 'refs';
  2         4  
  2         327  
65             return wantarray
66 4 50       24 ? @{ [ $target->${method}(@_) ] }
  0            
67             : $target->${method}(@_)
68             ;
69             }
70              
71              
72             1;
73              
74              
75             =head1 NAME
76              
77             Class::Proxy::Lite - Simple, lightweight object proxies
78              
79             =head1 SYNOPSIS
80              
81             # Make a proxy to a particular object
82             $proxy = Class::Proxy::Lite->new($token, \&resolver);
83            
84             # Make a caching proxy
85             $proxy = Class::Proxy::Lite->new($token, \&resolver, \$cache);
86              
87             # Methods invoked on the proxy are passed to the target object
88             $proxy->foo(...);
89             $proxy->bar(...);
90             $proxy->etc(...);
91              
92             =head1 DESCRIPTION
93              
94             Each instance of this class serves as a proxy to a target object. The proxy
95             is constructed from a I and a I. The resolver is a code
96             reference called with the token as its only argument; its job is to resolve
97             the token into a reference to the desired target object.
98              
99             The proxy doesn't hold a reference to its target; instead, the token must be
100             resolved each time a method call is made on the proxy.
101              
102             =head1 METHODS
103              
104             =head2 new
105              
106             $proxy = Class::Proxy::Lite->new($token, \&resolver);
107             $proxy = Class::Proxy::Lite->new($token, \&resolver, \$cache);
108              
109             Construct a proxy. The resolver is expected to return an object exactly
110             equivalent (if not identical) to the desired target object. This constraint
111             can't be formally enforced by this module, so your resolver must be written
112             in such a way as to meet the constraint itself.
113              
114             If you want one-time resolution, you may pass a reference to an undefined scalar
115             variable as a third argument to the C method; this will be used to cache
116             the target object the first time it's resolved, and as a result the target
117             object won't need to be resolved again. Or you might pass a reference to a tied
118             variable that implements caching with some sort of expiry.
119              
120             (There's a lot of room for clever hacks here. For instance, you could use a
121             resolver that returns a different object each time it's called. Also,
122             consider passing a closure as the resolver rather than a plain old reference
123             to a function.)
124              
125             B Strictly speaking, the method C doesn't exist as such: it isn't
126             actually defined. Instead, it's emulated using C (see below) --
127             B<< but only when called as a class method! >> This way, your target
128             objects' class(es) can safely implement a method C that can be called
129             as either a class method or an object method:
130              
131             $obj1 = MyClass->new(...);
132             $obj2 = $obj1->new(...);
133              
134             See L for information on how to implement this style of
135             constructor.
136              
137             When C is called as a class method on your own class,
138             L isn't involved (unless you set up
139             your objects' classes to inherit from it, which is a very bad idea). When
140             C is called as an object method, the call is passed on to the target
141             object just as would happen for any other object method call.
142              
143             =head2 AUTOLOAD
144              
145             This is where the action takes place. It simply calls the resolver to get a
146             reference to the target object, then passes the method call on to it.
147              
148             The methods C and C are special-cased; the former is
149             ignored, while the latter is ignored if and only if it was invoked on an
150             object (i.e., not called implicitly as the result of a C statement).
151              
152             Except for C and C, all methods invoked on this class or a
153             subclass of it (as opposed to methods invoked on an actual object) result in
154             an exception being thrown. An exception is also thrown if the resolver
155             returns C or a non-reference -- in other words, if it can't resolve
156             the token into an actual object.
157              
158             B Never call AUTOLOAD directly!
159              
160             =head1 SUBCLASSING
161              
162             Depending on your needs, it may not be necessary to subclass
163             L. If you do, however, your subclass
164             will probably look something like this:
165              
166             package MyObject::Proxy;
167             @ISA = qw(Class::Proxy::Lite);
168             sub new {
169             my ($cls, $target) = @_;
170             my $token = obj2token($target);
171             my $resolver = \&token2obj;
172             return $self->SUPER::new($token, $resolver);
173             }
174             sub obj2token { ... }
175             sub token2obj { ... }
176              
177             See F for a slightly different example.
178              
179             C was designed to avoid method name clashes; the only
180             method defined for it is C. If your subclass must inherit from
181             another class that uses AUTOLOAD, this is probably not the right solution
182             for you.
183              
184             =head1 BACKGROUND
185              
186             L didn't fit my needs. I was implementing an
187             object model in which objects are loaded dynamically and references to
188             loaded objects are stored in a master table. I wanted a solution that
189             served both as a proxy and a reference (generally speaking) to an object.
190             This module is what resulted.
191              
192             =head1 LIMITATIONS
193              
194             Apparently, it's not possible to catch calls to the C and C
195             methods on B of C. This makes it impossible
196             to implement a true proxy without defining C and
197             C, which I'm reluctant to do.
198              
199             The following note in L (under `Default UNIVERSAL methods')
200             appears to explain the problem:
201              
202             NOTE: `can' directly uses Perl's internal code for method
203             lookup, and `isa' uses a very similar method and cache-ing
204             strategy. This may cause strange effects if the Perl code
205             dynamically changes @ISA in any package.
206              
207             I might be wrong about all this, though; any insights on this problem are
208             welcome.
209              
210             =head1 SEE ALSO
211              
212             L is a better alternative for more sophisticated
213             proxy capabilities.
214              
215             =head1 VERSION
216              
217             1.01
218              
219             =head1 AUTHOR
220              
221             Paul Hoffman .
222              
223             =head1 CREDITS
224              
225             Thanks to Kurt Starsinic (KSTAR) for L,
226             which got me thinking, and to Murat Uenalan (MUENALAN) for
227             L, which set a good example.
228              
229             =head1 COPYRIGHT
230              
231             Copyright 2003 Paul M. Hoffman. All rights reserved.
232              
233             This program is free software; you can redistribute it and modify it under
234             the same terms as Perl itself.
235              
236             =cut
237