File Coverage

blib/lib/Simple/Factory.pm
Criterion Covered Total %
statement 101 101 100.0
branch 25 26 96.1
condition 10 12 83.3
subroutine 20 20 100.0
pod 1 3 33.3
total 157 162 96.9


line stmt bran cond sub pod time code
1             package Simple::Factory;
2             {
3             $Simple::Factory::VERSION = '0.09';
4             }
5 6     6   666469 use strict;
  6         14  
  6         267  
6 6     6   28 use warnings;
  6         13  
  6         153  
7              
8             #ABSTRACT: simple factory
9              
10 6     6   28 use feature 'switch';
  6         13  
  6         644  
11 6     6   29 use Carp qw(carp croak confess);
  6         9  
  6         404  
12 6     6   1563 use Module::Runtime qw(use_module);
  6         3223  
  6         45  
13 6     6   1957 use Try::Tiny;
  6         2763  
  6         346  
14 6     6   29 use Scalar::Util qw(blessed);
  6         12  
  6         333  
15              
16 6     6   1733 use Moo;
  6         25506  
  6         52  
17 6     6   8427 use MooX::HandlesVia;
  6         21151  
  6         33  
18 6     6   5673 use MooX::Types::MooseLike::Base qw(HasMethods HashRef Any Bool CodeRef);
  6         38503  
  6         563  
19 6     6   4437 use namespace::autoclean;
  6         73718  
  6         29  
20              
21             has build_class => (
22             is => 'ro',
23             required => 1,
24             coerce => sub {
25             my ($class) = @_;
26             use_module($class);
27             }
28             );
29              
30             has build_conf => (
31             is => 'ro',
32             isa => HashRef [Any],
33             required => 1,
34             handles_via => 'Hash',
35             handles => {
36             has_build_conf_for => 'exists',
37             get_build_conf_for => 'get',
38             get_supported_keys => 'keys',
39             _add_build_conf_for => 'set',
40             }
41             );
42              
43             has fallback => ( is => 'ro', predicate => 1 );
44             has build_method => ( is => 'ro', default => sub { "new" } );
45             has autoderef => ( is => 'ro', isa => Bool, default => sub { 1 } );
46             has silence => ( is => 'ro', isa => Bool, default => sub { 0 } );
47             has cache =>
48             ( is => 'ro', isa => HasMethods [qw(get set remove)], predicate => 1 );
49              
50             has inline => ( is => 'ro', isa => Bool, default => sub { 0 } );
51             has eager => ( is => 'ro', isa => Bool, default => sub { 0 } );
52             has on_error => (
53             is => 'ro',
54             isa => CodeRef,
55             default => sub { "croak" },
56             coerce => sub {
57             my ($on_error) = @_;
58              
59             return $on_error if ref($on_error) eq 'CODE';
60              
61             given ($on_error) {
62             when ("croak") {
63             return sub {
64             my $key = $_[0]->{key};
65             croak "cant resolve instance for key '$key': "
66             . $_[0]->{exception};
67             }
68             }
69             when ("confess") {
70             return sub {
71             my $key = $_[0]->{key};
72             confess "cant resolve instance for key '$key': "
73             . $_[0]->{exception};
74             }
75             }
76             when ("carp") {
77             return sub {
78             my $key = $_[0]->{key};
79             carp "cant resolve instance for key '$key': "
80             . $_[0]->{exception};
81             return;
82             }
83             }
84             when ("fallback") {
85             return sub {
86             return $_[0]->{factory}
87             ->get_fallback_for_key( $_[0]->{key} );
88             }
89             }
90             when ("undef") {
91             return sub { undef }
92             }
93             default {
94             croak
95             "can't coerce on_error '$on_error', please use: carp, confess, croak, fallback or undef";
96             }
97             }
98             }
99             );
100              
101             sub BUILDARGS {
102 32     32 0 753855 my ( $self, @args ) = @_;
103              
104 32 100       123 if ( scalar(@args) == 1 ) {
105 1         3 unshift @args, "build_class";
106             }
107 32         118 my (%hash_args) = @args;
108              
109 32 100 100     308 if ( scalar(@args) >= 2
      66        
110             && !exists $hash_args{build_class}
111             && !exists $hash_args{build_conf} )
112             {
113 25         47 my $build_class = $args[0];
114 25         37 my $build_conf = $args[1];
115              
116 25         56 $hash_args{build_class} = $build_class;
117 25         44 $hash_args{build_conf} = $build_conf;
118              
119 25 100       80 if ( $hash_args{inline} ) {
120             $hash_args{build_conf} =
121 1         3 { map { $_ => { $_ => $build_conf->{$_} } } keys %{$build_conf},
  2         9  
  1         5  
122             };
123             }
124             }
125              
126 32         685 return \%hash_args;
127             }
128              
129             sub BUILD {
130 29     29 0 1205 my ($self) = @_;
131              
132 29         84 $self->_coerce_build_method;
133              
134 28 100       87 if ( $self->eager ) {
135 2         25 $self->resolve($_) for $self->get_supported_keys;
136             }
137              
138 27         506 return;
139             }
140              
141             sub _coerce_build_method {
142 76     76   113 my ($self) = @_;
143              
144 76         158 my $class = $self->build_class;
145 76         144 my $build_method = $self->build_method;
146              
147 76 100       493 my $method = $class->can( $self->build_method )
148             or croak
149             "Error: class '$class' does not support build method: $build_method";
150              
151 75         150 return $method;
152             }
153              
154             sub _build_object_from_args {
155 47     47   6599 my ( $self, $args, $key ) = @_;
156              
157 47         104 my $class = $self->build_class;
158 47         105 my $method = $self->_coerce_build_method;
159              
160 47 100 100     394 if ( $self->autoderef && ref($args) ) {
161 38         66 given ( ref($args) ) {
162 38         85 when ('ARRAY') { return $class->$method( @{$args} ); }
  6         10  
  6         55  
163 32         57 when ('HASH') { return $class->$method( %{$args} ); }
  17         21  
  17         384  
164 15         24 when ('SCALAR') { return $class->$method( ${$args} ); }
  1         2  
  1         20  
165 14         22 when ('REF') { return $class->$method( ${$args} ); }
  1         2  
  1         21  
166 13         18 when ('GLOB') { return $class->$method( *{$args} ); }
  1         2  
  1         20  
167 12         19 when ('CODE') { return $class->$method( $args->($key) ); }
  11         41  
168 1         3 default {
169 1 50       22 carp( "cant autoderef argument ref('"
170             . ref($args)
171             . "') for class '$class'" )
172             if !$self->silence;
173             }
174             }
175             }
176              
177 10         1304 return $class->$method($args);
178             }
179              
180             sub get_fallback_for_key {
181             my ( $self, $key ) = @_;
182              
183             return $self->_build_object_from_args( $self->fallback, $key );
184             }
185              
186             sub resolve {
187             my ( $self, $key ) = @_;
188              
189             my $class = $self->build_class;
190             if ( $self->has_build_conf_for($key) ) {
191             return try {
192             $self->_build_object_from_args( $self->get_build_conf_for($key),
193             $key );
194             }
195             catch {
196             $self->on_error->(
197             { exception => $_, factory => $self, key => $key } );
198             };
199             }
200             elsif ( $self->has_fallback ) {
201             return $self->get_fallback_for_key($key);
202             }
203              
204             confess("instance of '$class' named '$key' not found");
205             }
206              
207             sub add_build_conf_for {
208 6     6 1 4264 my ( $self, $key, $conf, %conf ) = @_;
209              
210 6 100 66     134 if ( $self->has_build_conf_for($key) && $conf{not_override} ) {
    100          
211 1         67 croak("cannot override exiting configuration for key '$key'");
212             }
213             elsif ( $self->has_build_conf_for($key) ) {
214              
215             # if we are using cache
216             # and we substitute the configuration for some reason
217             # we should first remove the cache for this particular key
218 3         360 $self->_cache_remove($key);
219             }
220              
221 5         489 return $self->_add_build_conf_for( $key => $conf );
222             }
223              
224             sub _get_urn_for_cache {
225 13     13   22 my ( $self, $key ) = @_;
226              
227 13         91 join q<:>, $self->build_class, $key;
228             }
229              
230             sub _cache_remove {
231 3     3   7 my ( $self, $key ) = @_;
232              
233 3 100       19 return if !$self->has_cache;
234              
235 1         6 $self->cache->remove( $self->_get_urn_for_cache($key) );
236             }
237              
238             sub _cache_set {
239 38     38   69 my ( $self, $key, $value ) = @_;
240              
241 38 100       142 return if !$self->has_cache;
242              
243 5         13 my $urn = $self->_get_urn_for_cache($key);
244              
245 5         34 $self->cache->set( $urn => $value );
246             }
247              
248             sub _cache_get {
249 48     48   75 my ( $self, $key ) = @_;
250              
251 48 100       212 return if !$self->has_cache;
252              
253 7         27 my $urn = $self->_get_urn_for_cache($key);
254              
255 7         45 my $cached = $self->cache->get($urn);
256              
257 7 100       660 return $cached if $cached;
258             }
259              
260             around [qw(resolve get_fallback_for_key)] => sub {
261             my $orig = shift;
262             my ( $self, $key, @keys ) = @_;
263              
264             my $cached_value = $self->_cache_get($key);
265              
266             return $cached_value->[0] if $cached_value;
267              
268             my $instance = $self->$orig($key);
269              
270             $self->_cache_set( $key => [$instance] );
271              
272             if ( scalar(@keys) && $instance->can('resolve') ) {
273             return $instance->$orig(@keys);
274             }
275              
276             return $instance;
277             };
278              
279             1;
280              
281             __END__