File Coverage

blib/lib/DBIx/Roles.pm
Criterion Covered Total %
statement 226 257 87.9
branch 56 86 65.1
condition 6 8 75.0
subroutine 47 54 87.0
pod 2 4 50.0
total 337 409 82.4


line stmt bran cond sub pod time code
1             # $Id: Roles.pm,v 1.18 2006/01/30 10:58:51 dk Exp $
2              
3             package DBIx::Roles;
4              
5 1     1   42885 use DBI;
  1         33469  
  1         86  
6 1     1   13 use Scalar::Util qw(weaken);
  1         2  
  1         141  
7 1     1   5 use strict;
  1         2  
  1         36  
8 1     1   5 use vars qw($VERSION %loaded_packages $DBI_connect %DBI_select_methods $debug $ExportDepth);
  1         3  
  1         187  
9              
10             $VERSION = '1.04';
11             $ExportDepth = 0;
12             $DBI_connect = \&DBI::connect;
13             %DBI_select_methods = map { $_ => 1 } qw(
14             selectrow_array
15             selectrow_arrayref
16             selectrow_hashref
17             selectall_arrayref
18             selectall_hashref
19             selectcol_arrayref
20             );
21              
22             sub import
23             {
24 5     5   4293 shift;
25 5 100       2275 return unless @_;
26              
27             # if given list of imports, override DBI->connect() with it
28 1         4 my $callpkg = caller($ExportDepth);
29 1     1   5 no strict;
  1         1  
  1         36  
30 1         4 *{$callpkg."::DBIx_ROLES"}=[@_];
  1         5  
31 1     1   3 use strict;
  1         1  
  1         94  
32 1     0   9 local $SIG{__WARN__} = sub {};
  0         0  
33 1         775 *DBI::connect = \&__DBI_import_connect;
34             }
35              
36             # called instead of DBI-> connect
37             sub __DBI_import_connect
38             {
39 4     4   29 shift;
40 4         12 my $callpkg = caller(0);
41 1     1   4 no strict;
  1         3  
  1         48  
42 4         5 my @packages = @{$callpkg."::DBIx_ROLES"};
  4         43  
43 1     1   5 use strict;
  1         2  
  1         996  
44 4 100       13 if ( @packages) {
45 3         18 return DBIx::Roles-> new( @packages)-> connect( @_);
46             } else {
47 1         5 return $DBI_connect->( 'DBI', @_);
48             }
49             }
50              
51             # prepare new instance, do not connect to DB
52             sub new
53             {
54 5     5 1 1622 my ( $class, @packages) = @_;
55              
56             # load the necessary packages
57 5         14 for my $p ( @packages) {
58 25 50       516 $p = "DBIx::Roles::$p" unless $p =~ /:/;
59 25 100       81 next if exists $loaded_packages{$p};
60 1     1   816 eval "use $p;";
  1     1   2  
  1     1   15  
  1     1   757  
  1     1   3  
  1     1   18  
  1     1   564  
  1     1   2  
  1         16  
  1         667  
  1         3  
  1         16  
  1         726  
  1         2  
  1         19  
  1         655  
  1         3  
  1         17  
  1         631  
  1         2  
  1         14  
  1         613  
  1         4  
  1         26  
  8         436  
61 8 50       33 die $@ if $@;
62 8         30 $loaded_packages{$p} = 1;
63             }
64 5         17 push @packages, 'DBIx::Roles::Default';
65              
66             ## create the object:
67             # internal data instance
68 30         154 my $instance = {
69             dbh => undef, # DBI handle
70              
71             packages=> \@packages, # array of DBIx::Roles::* packages to use
72             private => { # packages' private data - all separated
73 5         15 map { $_ => undef } @packages
74             },
75             defaults=> {}, # default values and source packages for attributes
76             disabled=> {}, # dynamically disabled packages
77             attr => {}, # packages' public data - all mixed, and
78             vmt => {}, # packages' public methods - also all mixed
79             # name clashes in public and vmt will be explicitly fatal
80              
81             loops => [],
82             };
83              
84             # populate package info
85 5         18 for my $p ( @packages) {
86 30         217 my $ref = $p->can('initialize');
87 30 100       80 next unless $ref;
88 19         75 my ( $storage, $data, @vmt) = $ref->( $instance);
89 19         155 $instance-> {private}-> {$p} = $storage;
90              
91             # store default data
92 19 100       45 if ( $data) {
93 13         20 my $dst = $instance->{attr};
94 13         21 my $def = $instance->{defaults};
95 13         326 while ( my ( $key, $value) = each %$data) {
96 40 50       79 die
97             "Fatal: package '$p' defines attribute '$key' ".
98             "that conflicts with package '$def->{$key}->[0]'"
99             if exists $dst->{$key};
100 40         551 $def->{$key} = [$p, $value];
101 40         303 $dst->{$key} = $value;
102             }
103             }
104              
105             # store public methods
106 19         1172 my $dst = $instance->{vmt};
107 19         45 for my $key ( @vmt) {
108 18 50       40 die
109             "Fatal: package '$p' defines method '$key' ".
110             "that conflicts with package '$dst->{$key}'"
111             if exists $dst->{$key};
112 18         58 $dst->{$key} = $p;
113             }
114             }
115             # DBIx::Roles::Instance provides API for the packages
116 5         17 bless $instance, 'DBIx::Roles::Instance';
117              
118             # DBI attributes
119 5         11 my $self = {};
120 5         9 tie %{$self}, 'DBIx::Roles::Instance', $instance;
  5         29  
121 5         9 bless $self, $class;
122              
123             # use this trick for cheap self-referencing ( otherwise the object is never destroyed )
124 5         22 $instance->{self} = $self;
125 5         53 weaken( $instance->{self});
126              
127 5         22 return $self;
128             }
129              
130             # connect to DB
131             sub connect
132             {
133 4     4 1 14 my $self = shift;
134              
135 4 50       18 unless ( ref($self)) {
136             # called as DBIx::Roles-> connect(), packages provided
137 0         0 $self = $self-> new( @{shift()});
  0         0  
138             } # else the object is just being reconnected
139              
140 4         12 my $inst = $self-> instance;
141              
142 4 50       14 $self-> disconnect if $inst->{dbh};
143              
144 4         13 my @p = @_;
145              
146             # ask each package what do they think about params to connect
147 4         16 $inst-> dispatch( 'rewrite', 'connect', \@p);
148              
149             # now, @p can be assumed to be in DBI-compatible format
150 4         10 my ( $dsn, $user, $password, $attr) = @p;
151 4   100     20 $attr ||= {};
152              
153             # validate each package's individual parameters
154 4         29 for my $k ( keys %$attr) {
155 2 50       8 next unless exists $inst->{defaults}->{$k};
156 2         15 $inst-> dispatch( 'STORE', $k, $attr->{$k});
157             }
158              
159             # apply eventual attributes passed from outside,
160             # override with defaults those that have survived disconnect()
161 4         8 for my $k ( keys %{$inst->{defaults}}) {
  4         20  
162 40 100       78 if ( exists $attr-> {$k}) {
163 2         5 $inst-> {attr}-> {$k} = $attr-> {$k};
164 2         7 delete $attr-> {$k};
165             } else {
166 38         86 $inst-> {attr}-> {$k} = $inst->{defaults}->{$k}->[1];
167             };
168             }
169              
170             # try to connect
171 4 50       44 return $self
172             if $inst-> {dbh} = $inst-> connect( $dsn, $user, $password, $attr);
173 0 0       0 die "Unable to connect: no suitable roles found\n"
174             if $attr->{RaiseError};
175 0         0 return undef;
176             }
177              
178             # access object data instance
179 26     26 0 50 sub instance { tied %{ $_[0] } }
  26         81  
180              
181             # disconnect from DB, but retain the object
182             sub disconnect
183             {
184 0     0 0 0 my $self = $_[0];
185 0         0 my $inst = $self-> instance;
186            
187 0 0       0 $inst-> disconnect if $inst->{dbh};
188             }
189              
190             sub AUTOLOAD
191             {
192 12     12   3083 my @p = @_;
193              
194 1     1   11 use vars qw($AUTOLOAD);
  1         3  
  1         7069  
195 12         31 my $method = $AUTOLOAD;
196 12         223 $method =~ s/^.*:://;
197              
198 12         24 my $self = shift @p;
199 12         38 my $inst = $self-> instance;
200              
201 12         18 my $package;
202              
203 12 100 66     124 if (
    100          
204             exists( $DBI::DBI_methods{common}->{$method}) or
205             exists( $DBI::DBI_methods{db}->{$method})
206             ) {
207             # is it a DBI native method?
208             # rewrite
209 9         36 $inst-> dispatch( 'rewrite', $method, \@p);
210              
211             # dispatch
212 9         236 @_ = ( $inst, $method, @p);
213 9         59 goto $inst-> can('dispatch_dbi_method');
214             } elsif ( exists $inst->{vmt}->{$method}) {
215             # is it an exported method for outside usage?
216 1         4 my $package = $inst->{vmt}->{$method};
217 1         9 my $ref = $package-> can( $method);
218 1 50       15 die "Package '$package' declared method '$method' as available, but it is not"
219             unless $ref; # XXX AUTOLOAD cases are not handled
220 1         5 @_ = ( $inst, $inst->{private}->{$package}, @p);
221 1         6 goto $ref;
222             } else {
223             # none of the above, try wildcards
224 2         11 @_ = ( $inst, 'any', $method, @p);
225 2         11 goto $inst-> can('dispatch');
226             }
227             }
228              
229             sub DESTROY
230             {
231 5     5   2008 my $self = $_[0];
232 5         13 my $inst = $self-> instance;
233 5 100       34 $inst-> disconnect if $inst->{dbh};
234              
235 5         21 untie %$inst;
236             }
237              
238             # internal API
239             package DBIx::Roles::Instance;
240              
241             # since DBI::connect can be overloaded, call the connect method by reference
242 0     0   0 sub DBI_connect { shift; $DBIx::Roles::DBI_connect->('DBI', @_ ) }
  0         0  
243              
244             # iterate through each package in the recursive way
245             sub get_super
246             {
247 236     236   284 my ( $self) = @_;
248              
249 236         228 my $ref;
250 236         339 my $ctx = $self->{loops}->[-1];
251 236         247 while ( 1) {
252 724 100       1036 if ( $ctx->[0] < scalar @{$self-> {packages}}) {
  724 100       1694  
253             # next package
254 695         1275 my $package = $self-> {packages}->[ $ctx->[0]++];
255 695 50       3156 next if $self->{disabled}->{$package};
256 695 100       5186 next unless $ref = $package-> can( $ctx->[1]);
257 207 50       383 print STDERR (' 'x @{$self->{loops}}), "-> $package\n" if $DBIx::Roles::debug;
  0         0  
258 207         700 return ( $ref, $self-> {private}-> {$package});
259             } elsif ( $ctx->[2]) {
260             # signal end of list
261 11         36 return $ctx->[2]->( $self, $ctx);
262             } else {
263 18         41 return;
264             }
265             }
266             }
267              
268             # iterate through each package in the recursive way
269             sub super
270             {
271 229     229   340 my $self = shift;
272 229         505 my ( $ref, $private) = $self-> get_super;
273 229 100       526 return unless $ref;
274 211         459 unshift @_, $self, $private;
275 211         785 goto $ref;
276             }
277              
278             # saves and restores context of dispatch calls - needed if underlying roles
279             # are needed to be restarted
280             sub context
281             {
282 19 100   19   46 if ( $#_) {
283 6         48 @{$_[0]->{loops}->[-1]} = @{$_[1]};
  6         37  
  6         12  
284             } else {
285 13         14 return [ @{$_[0]->{loops}->[-1]} ];
  13         73  
286             }
287             }
288              
289             # call $method in all packages, where available, returns the result of the call
290             sub dispatch
291             {
292 74     74   107 my $self = shift;
293 74 100 66     360 my $eol_handler = shift if $_[0] and ref($_[0]);
294 74         119 my $method = shift;
295              
296 74         81 my @ret;
297 74         90 my $wa = wantarray;
298 74         691 push @{$self->{loops}}, [ 0, $method, $eol_handler, 0];
  74         262  
299 0 0       0 print STDERR (' 'x @{$self->{loops}}), "dispatch(",
  0         0  
300 74 50       156 ( join ',', map { defined($_) ? $_ : "undef"} $method,@_), ")\n"
301             if $DBIx::Roles::debug;
302 74         106 eval {
303 74 100       134 if ( $wa) {
304 4         15 @ret = $self-> super( @_);
305             } else {
306 70         177 $ret[0] = $self-> super( @_);
307             }
308             };
309 74 50       1543 print STDERR (' 'x @{$self->{loops}}), "done $method\n" if $DBIx::Roles::debug;
  0         0  
310 74         75 pop @{$self->{loops}};
  74         134  
311 74 50       184 die $@ if $@;
312 74 100       423 return wantarray ? @ret : $ret[0];
313             }
314              
315             # if called, then that means that all $method hooks were called,
316             # and now 'dbi_method' round must be run
317             sub _dispatch_dbi_eol
318             {
319 11     11   17 my ( $self, $ctx, $params) = @_;
320              
321 11         16 $ctx->[0] = 0; # reset the counter
322 11         25 my $method = $ctx->[1];
323 11         20 $ctx->[1] = 'dbi_method'; # call that hook instead
324 11         15 $ctx->[2] = undef; # clear the eol handler
325 11 50       27 print STDERR (' 'x @{$self->{loops}}), "done($method),dispatch(dbi_method)\n" if $DBIx::Roles::debug;
  0         0  
326 11     11   52 return sub { $_[0]-> super( $method, @_[2..$#_]) }
327 11         78 }
328              
329             # dispatch a native DBI method - first $method, then dbi_method hooks
330             sub dispatch_dbi_method
331             {
332 24     24   73 my ( $self, $method, @parameters) = @_;
333 24         62 splice( @_, 1, 0, \&_dispatch_dbi_eol);
334 24         69 goto &dispatch;
335             }
336              
337             sub enable_roles
338             {
339 0     0   0 my $hash = shift->{disabled};
340 0         0 for my $p (@_) {
341 0 0       0 my $g = ($p =~ /:/) ? $p : "DBIx::Roles::$p";
342 0 0       0 $hash->{$g}-- if $hash->{$g} > 0;
343             }
344             }
345              
346             sub disable_roles
347             {
348 0     0   0 my $hash = shift->{disabled};
349 0         0 for my $p (@_) {
350 0 0       0 my $g = ($p =~ /:/) ? $p : "DBIx::Roles::$p";
351 0         0 $hash->{$g}++;
352             }
353             }
354              
355             # R/W access to the underlying DBI connection handle
356             sub dbh
357             {
358 35 100   35   193 return $_[0]-> {dbh} unless $#_;
359 12         43 $_[0]-> {dbh} = $_[1];
360             }
361              
362             # access to the DBIx::Roles object
363 4     4   29 sub object { $_[0]-> {self} }
364              
365             # all unknown functions, called by roles internally, are assumed to be DBI methods
366             sub AUTOLOAD
367             {
368 1     1   18 use vars qw($AUTOLOAD);
  1         1  
  1         963  
369              
370 15     15   37 my $method = $AUTOLOAD;
371 15         114 $method =~ s/^.*:://;
372            
373 15         48 splice( @_, 1, 0, $method);
374 15         50 goto &dispatch_dbi_method;
375             }
376              
377 5     5   16 sub TIEHASH { $_[1] }
378 4     4   12 sub EXISTS { shift-> dispatch( 'EXISTS', @_) }
379 5     5   10652 sub FETCH { shift-> dispatch( 'FETCH', @_) }
380 15     15   1961 sub STORE { shift-> dispatch( 'STORE', @_) }
381 4     4   12 sub DELETE { shift-> dispatch( 'DELETE', @_) }
382              
383 5     5   539 sub DESTROY { shift-> dispatch( 'DESTROY') }
384              
385             package DBIx::Roles::Default;
386              
387             sub connect
388             {
389 9     9   21 my ( $self, $storage, $dsn, $user, $password, $attr) = @_;
390 9         36 return $DBIx::Roles::DBI_connect->( 'DBI', $dsn, $user, $password, $attr);
391             }
392              
393             sub disconnect
394             {
395 1     1   2 my $self = $_[0];
396              
397 1         29 $self-> {dbh}-> disconnect;
398 1         8 $self-> {dbh} = undef;
399             }
400              
401             sub dbi_method
402             {
403 0     0   0 my ( $self, $storage, $method, @parameters) = @_;
404 0         0 return $self-> {dbh}-> $method( @parameters);
405             }
406              
407             sub any
408             {
409 0     0   0 my ( $self, $storage, $method) = @_;
410 0         0 my @c = caller( $self-> {loops}->[-1]->[3] * 2);
411 0         0 die "Cannot locate method '$method' at $c[1] line $c[2]\n";
412             }
413              
414             sub EXISTS
415             {
416 4     4   9 my ( $self, $storage, $key) = @_;
417 4 50       12 if ( exists $self-> {attr}-> {$key}) {
418 0         0 return exists $self-> {attr}-> {$key};
419             } else {
420 4         21 return exists $self-> {dbh}-> {$key};
421             }
422             }
423              
424             sub FETCH
425             {
426 5     5   15 my ( $self, $storage, $key) = @_;
427 5 50       19 if ( exists $self-> {attr}-> {$key}) {
428 5         18 return $self-> {attr}-> {$key};
429             } else {
430 0         0 return $self-> {dbh}-> {$key};
431             }
432             }
433              
434             sub STORE
435             {
436 17     17   33 my ( $self, $storage, $key, $val) = @_;
437 17 100       42 if ( exists $self-> {attr}-> {$key}) {
438 9         68 $self-> {attr}-> {$key} = $val;
439             } else {
440 8         66 $self-> {dbh}-> {$key} = $val;
441             }
442             }
443              
444             sub DELETE
445             {
446 4     4   38 my ( $self, $storage, $key) = @_;
447 4 50       11 if ( exists $self-> {attr}-> {$key}) {
448 0         0 delete $self-> {attr}-> {$key};
449             } else {
450 4         20 delete $self-> {dbh}-> {$key};
451             }
452             }
453              
454             1;
455              
456             __DATA__