File Coverage

blib/lib/Class/Entangle.pm
Criterion Covered Total %
statement 84 85 98.8
branch 28 38 73.6
condition 7 16 43.7
subroutine 12 12 100.0
pod 3 3 100.0
total 134 154 87.0


line stmt bran cond sub pod time code
1             #{{{ POD
2              
3             =pod
4              
5             =head1 NAME
6              
7             Class::Entangle - Functions to entangle an object.
8              
9             =head1 DESCRIPTION
10              
11             Class::Entangle is names after Quantum Entanglement, which is where 2
12             partacles are entangled in such a way that events occuring to one occur to the
13             other as well, even if they are seperated by great distance.
14              
15             Using Class::Entangle you can pull an 'entanglement' from a class. This
16             entanglement contains a list of class properties, which includes subroutines,
17             variables, and handles. This entanglement can be used to create entanglers,
18             which in turn can be used to entangle a class.
19              
20             An entangler is a new class definition that contains definitions for subroutines,
21             variables, and handles that match another classes. When you define an entangler
22             you tell it how you want it to entangle each type. Subroutines are defined by
23             providing a callback routine. Variables and handers are defined using tie.
24              
25             Once you have an entangler for a class you can use that class as you would your
26             original class. If the class is an object defenition then you can use the
27             entangle() function to create an instance of the entangler against an existing
28             object.
29              
30             Note: You probably don't want to use construction methods through the entangler
31             class, it will return whatever the constructor for the original class returns.
32             This will return a new instance of the original class.
33              
34             =head1 SYNOPSIS
35              
36             #!/usr/bin/perl
37             use strict;
38             use warnings;
39              
40             use Class::Entangle;
41              
42             # First define a class to entangle, as well as a Tie::Scalar class for
43             # handling the scalars.
44             {
45             package MyClass;
46             use strict;
47             use warnings;
48              
49             sub new {
50             my $class = shift;
51             $class = ref $class if ref $class;
52             return bless {}, $class;
53             }
54              
55             sub subA {
56             my $self = shift;
57             my ( $in ) = @_;
58             return "subA($in)";
59             }
60              
61             our $scalarA = "scalar";
62             }
63              
64             {
65             package MyClass::TieScalar;
66             require Tie::Scalar;
67             our @ISA = qw(Tie::Scalar);
68              
69             sub TIESCALAR {
70             my $class = shift;
71             my ( $varname ) = @_;
72             bless( { varname => $varname }, $class );
73             }
74              
75             sub FETCH {
76             my $self = shift;
77             my $varname = $self->{ varname };
78              
79             no strict 'refs';
80             return ${ "MyClass\::$varname" };
81             }
82              
83             sub STORE {
84             my $self = shift;
85             my $varname = $self->{ varname };
86             my ( $value ) = @_;
87              
88             no strict 'refs';
89             return ${ "MyClass\::$varname" } = $value;
90             }
91             }
92              
93             my $one = MyClass->new;
94             my $entanglement = Class::Entangle::entanglement( $one );
95             # $entangler will contain the class name of the entangler.
96             my $entangler = Class::Entangle::entangler(
97             CODE => sub {
98             my $entangle = shift;
99             my $subname = shift;
100             if ( $entangle ) {
101             return $entangle->{ entangled }->$subname( @_ );
102             }
103             else {
104             return &{ "Test\::TestClass\::$subname" }->( @_ );
105             }
106             },
107             SCALAR => 'MyClass::TieScalar',
108             );
109             $entangle = Class::Entangle::entangle(
110             $entangler,
111             entangled => $test,
112             );
113              
114             # prints: 'subA(a)'
115             print $entangle->subA( 'a' );
116              
117             # prints: 'HaHa, I am messing with $MyClass::scalarA!'
118             no strict 'refs';
119             ${ "$entangler\::scalarA" } = 'HaHa, I am messing with MyClass::scalarA';
120             print ${ "$entangler\::scalarA" }
121              
122             =head1 EXPORTED FUNCTIONS
123              
124             =over 4
125              
126             =cut
127              
128             #}}}
129             package Class::Entangle;
130 1     1   4390 use strict;
  1         2  
  1         38  
131 1     1   5 use warnings;
  1         2  
  1         34  
132              
133 1     1   13 use Exporter 'import';
  1         1  
  1         133  
134              
135             our @EXPORT = qw/ entanglement entangler entangle /;
136             our $VERSION = '0.06';
137              
138             my %DEFINED = (
139             code => {},
140             loaded => {},
141             used => {},
142             );
143              
144             =item entanglement( $object )
145              
146             Only argument is the object to create an entanglement for. Returns an
147             entanglement.
148              
149             Note: Currently entanglements are simple hashes, this is subject to change,
150             always get the entanglement returned by this function and use it directly, do
151             not write code that treats it directly as a hash.
152              
153             =cut
154              
155             sub entanglement {
156 1     1 1 2309 my ( $object ) = @_;
157 1   33     4 my $class = ref $object || $object;
158              
159 1 50       3 unless ( $DEFINED{ $class }) {
160 1         1 my %defs;
161              
162 1     1   6 no strict 'refs';
  1         2  
  1         209  
163 1         1 for my $item ( $class, @{ "$class\::ISA" }) {
  1         5  
164 2         4 my %set = _class_properties( $item );
165 2         8 while ( my ( $prop, $value ) = each %set ) {
166 7 100       7 $defs{ $prop } = { %{ $defs{ $prop } || {}}, %$value };
  7         48  
167             }
168             }
169              
170 1         3 for my $key ( keys %defs ) {
171 5         4 $defs{ $key } = [ keys %{ $defs{ $key } }];
  5         14  
172             }
173              
174 1         5 $DEFINED{ $class } = { class => $class, %defs };
175             }
176              
177 1         482 return $DEFINED{ $class };
178             }
179              
180             sub _class_properties {
181 2     2   4 my ( $class ) = @_;
182 1     1   4 no strict 'refs';
  1         2  
  1         510  
183              
184 2         2 my %defs;
185 2         3 while( my ( $name, $ref ) = each %{ $class . "::" }) {
  20         84  
186 18 100       39 next if $name =~ m/::$/;
187 17 100       16 next if grep { $_ eq $name } (qw/BEGIN END/);
  34         74  
188              
189 14 100       13 $defs{ CODE }->{ $name }++ if defined &{$ref};
  14         32  
190 14 100       15 $defs{ HASH }->{ $name }++ if defined %{$ref};
  14         31  
191 14 100       12 $defs{ ARRAY }->{ $name }++ if defined @{$ref};
  14         32  
192 14 100       13 $defs{ SCALAR }->{ $name }++ if defined ${$ref};
  14         29  
193              
194             # IO have no sigil to use, have to dig into the glob.
195 14 100       12 $defs{ IO }->{ $name }++ if defined *{$ref}{IO};
  14         49  
196             }
197              
198 2         9 return %defs;
199             }
200              
201             =item entangler( $entanglement, %params )
202              
203             The first arguement should be an entanglement as returned by entanglement().
204             All additional params should be key => value pairs. All the following are acceptible:
205              
206             =over 4
207              
208             variation => 'default' - When you create an entangler it creates a class
209             definition. This defenition contains the variation name you pass in here. If
210             you don't pass in a variation 'default' is used. You can only define an
211             entangler class against an entanglement once for each variation. If you want to
212             have a different entangler for the same object you must provide a variation
213             name.
214              
215             CODE => sub { ... } - Define the subroutine callback to use on all class
216             subroutines. The first 2 parameters should always be shifted off before passing
217             @_ to the entangled class. The first is either the entagle object if the sub
218             was called as an object method, the entangler class if it was called as a class
219             method, or undef if not called as a method. The second parameter is always the
220             name of the sub that was called.
221              
222             HASH, ARRAY, SCALAR, IO => 'Tie::MyTie' - Specify the class to tie the specific
223             variable type to. If you are writing a Tie for a specific variable you should
224             know that the call to Tie looks like this:
225              
226             # $ref is "$entangler\::$item"
227             # $tieclass is the class you passed to 'HASH =>'
228             # $item is the name of the variable.
229             tie( %$ref, $tieclass, $item );
230              
231             =back 4
232              
233             =cut
234              
235             sub entangler {
236 2     2 1 1551 my $entanglement = shift;
237 2 100       12 my %params = (@_ > 1 ) ? @_ : ( CODE => shift( @_ ));
238              
239 2         3 my $class = $entanglement->{ class };
240 2   100     11 my $variation = delete( $params{ variation } ) || 'default';
241              
242 2         6 my $defined = $DEFINED{ loaded }->{ $class }->{ $variation };
243              
244 2 50 33     8 warn(
245             "Attempt to redefine '$class' variation '$variation'\n",
246             "You can only define a variation once, if you are using a\n",
247             "different subroutine callback, or different variable tie\n",
248             "classes, then things will not behave as you expect.\n",
249             ) if ( $defined and keys %params );
250              
251 2 50       9 _define_entangler( $entanglement, $variation, %params ) unless ( $defined );
252              
253 2         8 return $DEFINED{ loaded }->{ $class }->{ $variation };
254             }
255              
256             sub _define_entangler {
257 2     2   4 my ( $entanglement, $variation, %params ) = @_;
258 2         3 my $class = $entanglement->{ class };
259 2   33     8 my $entangleclass = ( delete $params{ entangleclass } ) ||
260             "Class\::_Entangle\::$class\::$variation";
261              
262             warn(
263             "Warning, redefining entangleclass: $entangleclass\n",
264             "This is almost certainly not what you want.\n"
265 2 50       7 ) if $DEFINED{ used }->{ $entangleclass };
266              
267 2         5 $DEFINED{ used }->{ $entangleclass }++;
268              
269 1     1   5 no strict 'refs';
  1         2  
  1         290  
270              
271             # Take care of the subs
272 2 50       6 if( my $ref = delete $params{ 'CODE' }) {
273 2         2 for my $sub ( @{ $entanglement->{ 'CODE' }}) {
  2         5  
274 8         51 *{ $entangleclass . '::' . $sub } = sub {
275 8     8   4480 my $first = $_[0];
276 8   33     21 $first = ref $first || $first;
277 8 50       19 my $self = shift if $first eq $entangleclass;
278 8   50     36 $ref->( $self || undef, $sub, @_ );
279             }
280 8         46 }
281             }
282              
283             # Take care of the variables and handles
284 2         8 while ( my ( $type, $tieclass ) = each %params ) {
285 1         2 for my $item ( @{ $entanglement->{ $type }}) {
  1         2  
286 4         7 my $ref = $entangleclass . '::' . $item;
287 4 50       11 tie( %$ref, $tieclass, $item ) if $type eq 'HASH';
288 4 50       8 tie( @$ref, $tieclass, $item ) if $type eq 'ARRAY';
289 4 50       40 tie( $$ref, $tieclass, $item ) if $type eq 'SCALAR';
290 4 50       34 tie( *{ $ref }, $tieclass, $item ) if $type eq 'IO';
  0         0  
291             }
292             }
293              
294 2         7 $DEFINED{ loaded }->{ $class }->{ $variation } = $entangleclass;
295             }
296              
297             =item entangle( $entangler, %object_params )
298              
299             First param should be an entangler class as returned by entangler().
300              
301             All additional parameters will be directly put into the hashref that is blessed
302             as an object of type $entangler.
303              
304             =cut
305              
306             sub entangle {
307 2     2 1 8 my $entangler = shift;
308 2         9 return bless( { @_ }, $entangler );
309             }
310              
311             1;
312              
313             #{{{ End Pod
314              
315             __END__