File Coverage

blib/lib/Kaiten/Container.pm
Criterion Covered Total %
statement 62 62 100.0
branch 28 30 93.3
condition 12 17 70.5
subroutine 11 11 100.0
pod 5 5 100.0
total 118 125 94.4


line stmt bran cond sub pod time code
1             package Kaiten::Container;
2              
3 5     5   238354 use v5.10;
  5         19  
  5         227  
4 5     5   26 use strict;
  5         10  
  5         175  
5 5     5   24 use warnings FATAL => 'recursion';
  5         15  
  5         395  
6              
7             =head1 NAME
8              
9             Kaiten::Container - Simples dependency-injection (DI) container, distant relation of IoC.
10              
11             =head1 VERSION
12              
13             Version 0.37
14              
15             =cut
16              
17             our $VERSION = '0.37';
18              
19 5     5   4446 use Moo;
  5         59896  
  5         32  
20              
21 5     5   6289 use Carp qw(croak carp);
  5         9  
  5         367  
22 5     5   27 use Scalar::Util qw(reftype);
  5         10  
  5         5506  
23              
24             #======== DEVELOP THINGS ===========>
25             # develop mode
26             #use Smart::Comments;
27             #use Data::Printer;
28             #======== DEVELOP THINGS ===========<
29              
30             my $error = [
31             'Error: handler [%s] not defined at [init], die ',
32             'Error: handler [%s] init wrong, [probe] sub not defined, die ',
33             'Warning: handler [%s] don`t pass [probe] check on reuse with message [ %s ], try to create new one, working ',
34             'Error: handler [%s] don`t pass [probe] check on create, with message [ %s ], die ',
35             'Error: [init] value must be HASHREF only, die ',
36             'Error: [add] method REQUIRE handlers at args, die',
37             'Error: handler [%s] exists, to rewrite handler remove it at first, die ',
38             'Error: [remove] method REQUIRE handlers at args, die',
39             'Error: handler [%s] NOT exists, nothing to remove, die ',
40             ];
41              
42             has 'init' => (
43             is => 'rw',
44             required => 1,
45             isa => sub {
46             croak sprintf $error->[4] unless ( defined $_[0] && ( reftype $_[0] || '' ) eq 'HASH' );
47             },
48             default => sub { {} },
49             );
50              
51             has 'DEBUG' => (
52             is => 'rw',
53             default => sub { 0 },
54             );
55              
56             has 'CANCEL_REUSE' => (
57             is => 'rw',
58             default => sub { 0 },
59             );
60              
61             has '_cache' => (
62             is => 'rw',
63             default => sub { {} },
64             );
65              
66             =head1 SYNOPSIS
67              
68             This module resolve dependency injection conception in easiest way ever.
69             You are just create some code first and put it on kaiten in named container.
70             Later you take it by name and got yours code result fresh and crispy.
71              
72             No more humongous multi-level dependency configuration, service provider and etc.
73              
74             You got what you put on, no more, no less.
75              
76             Ok, a little bit more - L run I sub every time when you want to take something to ensure all working properly.
77              
78             And another one - KC try to re-use I return if it requested.
79              
80             Ah, last but not least - KC MAY resolve deep dependencies, if you need it. Really. A piece of cake!
81              
82             use Kaiten::Container;
83              
84             my $config = {
85             ExampleP => {
86             handler => sub {
87             return DBI->connect( "dbi:ExampleP:", "", "", { RaiseError => 1 } ) or die $DBI::errstr;
88             },
89             probe => sub { shift->ping() },
90             settings => { reusable => 1 }
91             },
92             };
93              
94             my $container = Kaiten::Container->new( init => $config, DEBUG => 1 );
95             my $dbh = $container->get_by_name('ExampleP');
96              
97             All done, now we are have container and may get DB handler on call.
98             Simple!
99              
100             =head1 SETTINGS
101              
102             =head2 C
103              
104             This settings to show some debug information. To turn on set it to 1, by default disabled.
105              
106             =head2 C
107              
108             This settings suppress C properties for all handlers in container. To turn on set it to 1, by default disabled.
109              
110             May be helpfully in test mode, when you need replace some method with mock, but suppose its already may be cached in descendant handlers.
111              
112             =head1 SUBROUTINES/METHODS
113              
114             =head2 C
115              
116             This method create container with entities as I configuration hash values, also may called without config.
117             Its possible add all entities later, with C method.
118              
119             my $config = {
120             examplep_config => {
121             handler => sub { { RaiseError => 1 } },
122             probe => sub { 1 },
123             settings => { reusable => 1 },
124             },
125             examplep_dbd => {
126             handler => sub { "dbi:ExampleP:" },
127             probe => sub { 1 },
128             settings => { reusable => 1 },
129             },
130             # yap! this is deep dependency example.
131             ExampleP => {
132             handler => sub {
133             my $c = shift;
134            
135             my $dbd = $c->get_by_name('examplep_dbd');
136             my $conf = $c->get_by_name('examplep_config');
137            
138             return DBI->connect( $dbd, "", "", $conf ) or die $DBI::errstr;
139             },
140             probe => sub { shift->ping() },
141             settings => { reusable => 1 }
142             },
143             test => {
144             handler => sub { return 'Hello world!' },
145             probe => sub { return 1 },
146             },
147             };
148              
149             my $container = Kaiten::Container->new( init => $config );
150              
151             Entity have next stucture:
152              
153             =over
154              
155             =item * unique name (REQUIRED)
156              
157             This name used at C method.
158              
159             =over
160              
161             =item - C (REQUIRED)
162              
163             This sub will be executed on C method, at first argument its got I itself.
164              
165             =item - C (REQUIRED)
166              
167             This sub must return true, as first arguments this sub got I sub result.
168              
169             =item - C (OPTIONAL)
170              
171             - C (OPTIONAL)
172              
173             If it setted to true - KC try to use cache. If cached handler DONT pass I KC try to create new one instance.
174              
175             =back
176              
177             =back
178              
179             NB. New instance always be tested by call I.
180             If you dont want test handler - just cheat with
181              
182             probe => sub { 1 }
183              
184             but its sharp things, handle with care.
185              
186             =head3 Something about deep dependencies
187              
188             Its here, its worked.
189              
190             handler => sub {
191             # any handler sub get container as first arg
192             my $container = shift;
193            
194             my $dbd = $container->get_by_name('examplep_dbd');
195             my $conf = $container->get_by_name('examplep_config');
196            
197             return DBI->connect( $dbd, "", "", $conf ) or die $DBI::errstr;
198             },
199              
200             Warning! Its been worked predictably only at ONE container scope.
201             Mixing deep dependencies from different containers seems... hm, you know, very strange.
202             And dangerous.
203              
204             What about circular dependencies? Its cause 'die'. Don`t do that.
205              
206             =head2 C
207              
208             Use this method to execute I sub and get it as result.
209              
210             my $dbh = $container->get_by_name('ExampleP');
211             # now $dbh contain normal handler to ExampleP DB
212              
213             =cut
214              
215             sub get_by_name {
216 947     947 1 17522 my $self = shift;
217 947         1512 my $handler_name = shift;
218              
219 947         21902 my $handler_config = $self->init->{$handler_name};
220              
221 947 100       12641 croak sprintf( $error->[0], $handler_name ) unless defined $handler_config;
222 945 100 50     6759 croak sprintf( $error->[1], $handler_name ) unless defined $handler_config->{probe} && ( reftype $handler_config->{probe} || '' ) eq 'CODE';
      66        
223              
224 944         988 my $result;
225              
226 944   66     3580 my $reusable = defined $handler_config->{settings} && $handler_config->{settings}{reusable};
227              
228 944 100 66     6358 if ( !$self->CANCEL_REUSE && $reusable && defined $self->_cache->{$handler_name} ) {
      100        
229 12         26 $result = $self->_cache->{$handler_name};
230              
231             # checkout handler and wipe it if it don`t pass [probe]
232 12 100       34 unless ( eval { $handler_config->{probe}->($result) } ) {
  12         38  
233 1 50       28 carp sprintf( $error->[2], $handler_name, $@ ) if $self->DEBUG;
234 1         675 $result = undef;
235             }
236             }
237              
238 944 100       2504 unless ($result) {
239 933         22798 $result = $self->init->{$handler_name}{handler}->($self);
240              
241             # checkout handler and die it if dont pass [probe]
242 39 100       1813 unless ( eval { $handler_config->{probe}->($result) } ) {
  39         135  
243 1         16 croak sprintf( $error->[3], $handler_name, $@ );
244             }
245             }
246              
247             # put it to cache if it used
248 49 100 66     470 $self->_cache->{$handler_name} = $result if ( !$self->CANCEL_REUSE && $reusable );
249              
250 49         181 return $result;
251             }
252              
253             =pod
254              
255             =head2 C
256              
257             Use this method to add some more entities to container.
258              
259             my $configutarion_explodable = {
260             explode => {
261             handler => sub { return 'ExplodeSQL there!' },
262             probe => sub { state $a= [ 1, 0, 0 ]; return shift @$a; },
263             settings => { reusable => 1 }
264             },
265             explode_now => {
266             handler => sub { return 'ExplodeNowSQL there!' },
267             probe => sub { 0 },
268             settings => { reusable => 1 }
269             },
270             };
271              
272             $container->add(%$configutarion_explodable); # list, NOT hashref!!!
273            
274             =cut
275              
276             sub add {
277 15     15 1 1525 my $self = shift;
278 15         45 my %handlers = @_;
279              
280 15 100       68 croak sprintf $error->[5] unless scalar keys %handlers;
281              
282 14         48 while ( my ( $handler_name, $handler_config ) = each %handlers ) {
283              
284 15 100       345 croak sprintf( $error->[6], $handler_name ) if exists $self->init->{$handler_name};
285              
286 14         1622 $self->init->{$handler_name} = $handler_config;
287              
288             }
289              
290 13         150 return $self;
291             }
292              
293             =pod
294              
295             =head2 C
296              
297             This method remove some entities from container
298              
299             $container->remove('explode_now','ExampleP'); # list, NOT arayref!!!
300              
301             =cut
302              
303             sub remove {
304 6     6 1 1546 my $self = shift;
305 6         34 my @handlers = @_;
306              
307 6 100       35 croak sprintf $error->[7] unless scalar @handlers;
308              
309 5         11 foreach my $handler_name (@handlers) {
310              
311 6 100       153 croak sprintf( $error->[8], $handler_name ) if !exists $self->init->{$handler_name};
312              
313 5         140 delete $self->init->{$handler_name};
314              
315             # clear cache if it exists too
316 5 100       70 delete $self->_cache->{$handler_name} if exists $self->_cache->{$handler_name};
317              
318             }
319              
320 4         18 return $self;
321             }
322              
323             =pod
324              
325             =head2 C
326              
327             Use this method to view list of available handler in container
328              
329             my @handler_list = $container->show_list;
330            
331             # @handler_list == ( 'examplep_config', 'examplep_dbd', 'explode', 'test' )
332              
333             NB. Entities sorted with perl C function
334              
335             =cut
336              
337             sub show_list {
338 3     3 1 192 my $self = shift;
339              
340 3         6 my @result = sort keys %{ $self->init };
  3         94  
341 3 50       63 return wantarray ? @result : \@result;
342              
343             }
344              
345             =pod
346              
347             =head2 C
348              
349             Use this method to test handlers works correctly.
350             If no handlers name given - will be tested ALL.
351              
352             my $test_result = $container->test();
353              
354             Method return 1 if it seems all ok, or die.
355              
356             I
357             Using this method at production are may helpfully too, but may couse overhead.>
358              
359             =cut
360              
361             sub test {
362 5     5 1 1364 my $self = shift;
363 5         12 my @handlers = @_;
364              
365 5 100       21 @handlers = $self->show_list unless scalar @handlers;
366              
367 5         20 $self->get_by_name($_) foreach @handlers;
368              
369 3         15 return 1;
370             }
371              
372             =head1 AUTHOR
373              
374             Meettya, C<< >>
375              
376             =head1 BUGS
377              
378             Please report any bugs or feature requests to C, or through
379             the web interface at L. I will be notified, and then you'll
380             automatically be notified of progress on your bug as I make changes.
381              
382             =head1 DEVELOPMENT
383              
384             =head2 Repository
385              
386             https://github.com/Meettya/Kaiten-Container
387              
388              
389             =head1 SUPPORT
390              
391             You can find documentation for this module with the perldoc command.
392              
393             perldoc Kaiten::Container
394              
395              
396             You can also look for information at:
397              
398             =over 4
399              
400             =item * RT: CPAN's request tracker (report bugs here)
401              
402             L
403              
404             =item * AnnoCPAN: Annotated CPAN documentation
405              
406             L
407              
408             =item * CPAN Ratings
409              
410             L
411              
412             =item * Search CPAN
413              
414             L
415              
416             =back
417              
418             =head1 SEE ALSO
419              
420             L - a Moose-based DI framework
421              
422             L - the ancestor of L
423              
424             L - another DI container
425              
426             L - an alternative DI container
427              
428             =head1 ACKNOWLEDGEMENTS
429              
430              
431             =head1 LICENSE AND COPYRIGHT
432              
433             Copyright 2011 Meettya.
434              
435             This program is free software; you can redistribute it and/or modify it
436             under the terms of either: the GNU General Public License as published
437             by the Free Software Foundation; or the Artistic License.
438              
439             See http://dev.perl.org/licenses/ for more information.
440              
441              
442             =cut
443              
444             1; # End of Kaiten::Container