File Coverage

blib/lib/Bolts/Artifact.pm
Criterion Covered Total %
statement 74 76 97.3
branch 24 26 92.3
condition 1 3 33.3
subroutine 11 11 100.0
pod 3 3 100.0
total 113 119 94.9


line stmt bran cond sub pod time code
1             package Bolts::Artifact;
2             $Bolts::Artifact::VERSION = '0.143171';
3             # ABSTRACT: Tools for resolving an artifact value
4              
5 11     11   84 use Moose;
  11         15  
  11         84  
6              
7             with 'Bolts::Role::Artifact';
8              
9 11     11   48575 use Bolts::Util qw( locator_for meta_locator_for );
  11         21  
  11         73  
10 11     11   4073 use Carp ();
  11         18  
  11         187  
11 11     11   40 use List::MoreUtils qw( all );
  11         14  
  11         586  
12 11     11   52 use Moose::Util::TypeConstraints;
  11         13  
  11         248  
13 11     11   15733 use Safe::Isa;
  11         19  
  11         1253  
14 11     11   55 use Scalar::Util qw( weaken reftype );
  11         17  
  11         2432  
15              
16              
17             has init_locator => (
18             is => 'ro',
19             does => 'Bolts::Role::Locator',
20             weak_ref => 1,
21             );
22              
23             with 'Bolts::Role::Initializer';
24              
25              
26             has name => (
27             is => 'ro',
28             isa => 'Str',
29             required => 1,
30             );
31              
32              
33             has blueprint => (
34             is => 'ro',
35             does => 'Bolts::Blueprint',
36             required => 1,
37             traits => [ 'Bolts::Initializer' ],
38             );
39              
40              
41             has scope => (
42             is => 'ro',
43             does => 'Bolts::Scope',
44             required => 1,
45             traits => [ 'Bolts::Initializer' ],
46             );
47              
48              
49             has infer => (
50             is => 'ro',
51             isa => enum([qw( none options acquisition )]),
52             required => 1,
53             default => 'none',
54             );
55              
56              
57             has inference_done => (
58             reader => 'is_inference_done',
59             writer => 'inference_is_done',
60             isa => 'Bool',
61             required => 1,
62             default => 0,
63             init_arg => undef,
64             );
65              
66              
67             subtype 'Bolts::Injector::List',
68             as 'ArrayRef',
69             where { all { $_->$_does('Bolts::Injector') } @$_ };
70              
71             has injectors => (
72             is => 'ro',
73             isa => 'Bolts::Injector::List',
74             required => 1,
75             traits => [ 'Array', 'Bolts::Initializer' ],
76             handles => {
77             all_injectors => 'elements',
78             add_injector => 'push',
79             },
80             default => sub { [] },
81             );
82              
83              
84             has does => (
85             accessor => 'does_type',
86             isa => 'Moose::Meta::TypeConstraint',
87             );
88              
89              
90             has isa => (
91             accessor => 'isa_type',
92             isa => 'Moose::Meta::TypeConstraint',
93             );
94              
95 11     11   58 no Moose::Util::TypeConstraints;
  11         17  
  11         52  
96              
97              
98             sub infer_injectors {
99 764     764 1 836 my ($self, $bag) = @_;
100              
101             # use Data::Dumper;
102             # warn Dumper($self);
103             # $self->inference_is_done(1);
104              
105             # Use inferences to collect the list of injectors
106 764 100       16451 if ($self->infer ne 'none') {
107 57         170 my $loc = locator_for($bag);
108 57         144 my $meta_loc = meta_locator_for($bag);
109              
110 57         1279 my $inference_type = $self->infer;
111              
112 57         170 my $inferences = $meta_loc->acquire_all('inference');
113 57         1724 my %injectors = map { $_->key => $_ } $self->all_injectors;
  274         6209  
114              
115 57         94 my @inferred_parameters;
116 57         97 for my $inference (@$inferences) {
117 57         1242 push @inferred_parameters,
118             $inference->infer($self->blueprint);
119             }
120              
121             # use Data::Dumper;
122             # warn 'INFERRED: ', Dumper(\@inferred_parameters);
123              
124 57         100 PARAMETER: for my $inferred (@inferred_parameters) {
125 325         341 my $key = $inferred->{key};
126              
127 325 100       819 next PARAMETER if defined $injectors{ $key };
128              
129 51         202 my %params = %$inferred;
130 51         110 my $required = delete $params{required};
131 51         62 my $via = delete $params{inject_via};
132              
133 51         51 my $blueprint;
134 51 100       102 if ($inference_type eq 'options') {
135 48         196 $blueprint = $meta_loc->acquire('blueprint', 'given', {
136             required => $required,
137             });
138             }
139             else {
140 3         20 $blueprint = $meta_loc->acquire('blueprint', 'acquired', {
141             locator => $loc,
142             path => [ $key ],
143             });
144             }
145              
146 51         122 $params{blueprint} = $blueprint;
147              
148 51         148 my $injector = $meta_loc->acquire(@$via, \%params);
149 51 50       110 unless (defined $injector) {
150 0         0 Carp::carp(qq[Unable to acquire an injector for "$via".]);
151 0         0 next PARAMETER;
152             }
153            
154 51         1915 $self->add_injector($injector);
155             }
156             }
157             }
158              
159              
160             sub such_that {
161 90     90 1 106 my ($self, $such_that) = @_;
162              
163             # TODO Should probably do something special if on of the must_* are already
164             # set. Maybe make sure the new things are compatible with the old? Maybe
165             # setup a type union? Maybe croak? Maybe just carp? I don't know.
166              
167 90 100       2648 $self->does_type($such_that->{does}) if defined $such_that->{does};
168 90 100       321 $self->isa_type($such_that->{isa}) if defined $such_that->{isa};
169             }
170              
171             # sub init_meta {
172             # my ($self, $meta, $name) = @_;
173             #
174             # $self->blueprint->init_meta($meta, $name);
175             # $self->scope->init_meta($meta, $name);
176             #
177             # # Add the actual artifact factory method
178             # $meta->add_method($name => sub { $self });
179             #
180             # # # Add the actual artifact factory method
181             # # $meta->add_method($name => sub {
182             # # my ($bag, %params) = @_;
183             # # return $self->get($bag, %params);
184             # # });
185             # }
186              
187              
188             sub get {
189 764     764 1 28795 my ($self, $bag, %input_params) = @_;
190              
191 764 50       24982 $self->infer_injectors($bag) unless $self->is_inference_done;
192              
193 764         16386 my $name = $self->name;
194 764         17160 my $blueprint = $self->blueprint;
195 764         16246 my $scope = $self->scope;
196              
197 764         661 my $artifact;
198              
199             # Load the artifact from the scope unless the blueprint implies scope
200 764 100       2235 $artifact = $scope->get($bag, $name)
201             unless $blueprint->implied_scope;
202              
203             # The scope does not have it, so load it again from blueprints
204 764 100       1285 if (not defined $artifact) {
205              
206 712         597 my @bp_params;
207 712         22007 for my $injector ($self->all_injectors) {
208 1051         2474 $injector->pre_inject($bag, \%input_params, \@bp_params);
209             }
210              
211 711         1976 $artifact = $blueprint->get($bag, $name, @bp_params);
212              
213 711         31904 for my $injector ($self->all_injectors) {
214 1049         1811 $injector->post_inject($bag, \%input_params, $artifact);
215             }
216              
217             # Carp::croak("unable to build artifact $name from blueprint")
218             # unless defined $artifact;
219              
220             # Add the item into the scope for possible reuse from cache
221 711 100       1785 $scope->put($bag, $name, $artifact)
222             unless $blueprint->implied_scope;
223             }
224              
225             # TODO This would be a much more helpful check to apply ahead of time in
226             # cases where we can. Possibly some sort of such_that check on the
227             # blueprints to be handled when such checks can be sensibly handled
228             # ahead of time.
229              
230 763         19937 my $isa = $self->isa_type;
231 763         19060 my $does = $self->does_type;
232              
233 763         636 my $msg;
234 763 100       1291 $msg = $isa->validate($artifact) if defined $isa;
235 763 100 33     2615 $msg //= $does->validate($artifact) if defined $does;
236              
237 763 100       37948 Carp::croak(qq[Constructed artifact named "$name" has the wrong type: $msg]) if $msg;
238              
239 762         2705 return $artifact;
240             }
241              
242             # sub inline_get {
243             # my $blueprint_inline = $self->blueprint->inline_get;
244             # my $scope_inline = $self->scope->inline_scope;
245             #
246             # return q[
247             # my ($self, $bag, %params) = @_;
248             # my $artifact;
249             #
250             # ].$scope_inline.q[
251             #
252             # if (not defined $artifact) {
253             # ].$blueprint_inline.q[
254             # }
255             #
256             # return $artifact;
257             # ];
258             # }
259              
260             __PACKAGE__->meta->make_immutable;
261              
262             __END__
263              
264             =pod
265              
266             =encoding UTF-8
267              
268             =head1 NAME
269              
270             Bolts::Artifact - Tools for resolving an artifact value
271              
272             =head1 VERSION
273              
274             version 0.143171
275              
276             =head1 SYNOPSIS
277              
278             use Bolts;
279             my $meta = Bolts::Bag->start_bag;
280              
281             my $artifact = Bolts::Artifact->new(
282             meta_locator => $meta,
283             name => 'key',
284             blueprint => [ 'blueprint', 'factory', {
285             class => 'MyApp::Thing',
286             } ],
287             scope => [ 'scope', 'singleton' ],
288             infer => 'acquisition',
289             parameters => {
290             foo => [ 'blueprint', 'given', {
291             isa => 'Str',
292             } ],
293             bar => value 42,
294             },
295             );
296              
297             =head1 DESCRIPTION
298              
299             This is the primary implementation of L<Bolts::Role::Artifact> with all the features described in L<Bolts>, including blueprint, scope, inferrence, injection, etc.
300              
301             =head1 ROLES
302              
303             =over
304              
305             =item *
306              
307             L<Bolts::Role::Artifact>
308              
309             =item *
310              
311             L<Bolts::Role::Initializer>
312              
313             =back
314              
315             =head1 ATTRIBUTES
316              
317             =head2 init_locator
318              
319             If provided with a references to the meta-locator for the bag to which the artifact is going to be attached, the L</blueprint>, L</scope>, and L</injectors> attributes may be given as initializers rather than as objects.
320              
321             =head2 name
322              
323             B<Required.> This sets the name of the artifact that is being created. This is passed through as part of scope resolution (L<Bolts::Scope>) and blueprint construction (L<Bolts::Blueprint>).
324              
325             =head2 blueprint
326              
327             B<Required.> This sets the L<Bolts::Blueprint> used to construct the artifact.
328              
329             Instead of passing the blueprint object in directly, you can provide an initializer in an array reference, similar to what you would pass to C<acquire> to get the blueprint from the meta-locator, e.g.:
330              
331             blueprint => bolts_init('blueprint', 'acquire', {
332             path => [ 'foo' ],
333             }),
334              
335             If so, you must provide an L</init_locator>.
336              
337             =head2 scope
338              
339             B<Required.> This sets the L<Bolts::Scope> used to manage the object's lifecycle.
340              
341             Instead of passing the scope object in directly, you can provide an initializer in an array reference, similar to what you would pass to C<acquire> to get the scope from the meta-locator, e.g.:
342              
343             scope => bolts_init('scope', 'singleton'),
344              
345             If so, you must provide a L</init_locator>.
346              
347             =head2 infer
348              
349             This is a setting that tells the artifact what kind of inferrence to perform when inferring injectors from the blueprint. This may e set to one of the following:
350              
351             =over
352              
353             =item none
354              
355             B<Default.> When this is set, no inferrence is performed. The injectors will be defined according to L</dependencies> only.
356              
357             =item options
358              
359             This tells the artifact to infer the injection using the parameters passed to the call to L<Bolts::Role::Locator/acquire>. When the object is acquired and resolved, the caller will need to pass through any options needed for building the object.
360              
361             =item acquisition
362              
363             This tells the artifact to infer the injection using automatically acquired artifacts. The acquisition will happen from the bag containing the artifact with paths matching the name of the parameter.
364              
365             B<Caution:> The way this work is likely to be customizeable in the future and the default behavior may differ.
366              
367             =back
368              
369             =head2 inference_done
370              
371             This is an internal setting, which has a reader method named C<is_inference_done> and a writer named C<inference_is_done>. Do not use the writer directly unless you know what you are doing. You cannot set this attribute during construction.
372              
373             Normally, this is a true value after the automatic inference of injectors has been completed and false before.
374              
375             =head2 injectors
376              
377             This is an array of L<Bolts::Injector>s, which are used to inject values into or after the construction process. Anything set here will take precedent over inferrence.
378              
379             Instead of passing the array of injector objects in directly, you can provide an array of initializers, each as an array reference, similar to what you would pass to C<acquire> for each to get each injector from the meta-locator, e.g.:
380              
381             injector => [
382             bolts_init('injector', 'parameter_name', {
383             key => 'foo',
384             blueprint => bolts_init('blueprint', 'literal', {
385             value => 42,
386             }),
387             }),
388             ]
389              
390             If so, you must provide a L</init_locator>.
391              
392             =head2 does
393              
394             This is used to control the role the artifact constructed must impement. Usually, this is not set directly, but set by the bag instead as an additional control on bag contents.
395              
396             =head2 isa
397              
398             This is used to control the type of the constructed artifact. Usually, this is not set directly, but set by the bag instead as an additional control on bag contents.
399              
400             =head1 METHODS
401              
402             =head2 infer_injectors
403              
404             This performs the inference of L</injectors> based upon the L</infer> setting. This is called automatically when the artifact is resolved.
405              
406             =head2 such_that
407              
408             This is a helper for setting L</does> and L</isa>. The bag that contains the artifact normally calls this to enforce type constriants on the artifact.
409              
410             =head2 get
411              
412             This is called during the resolution phase of L<Bolts::Role::Locator> to either retrieve the object from the L</scope> or construct a new object according to the L</blueprint>.
413              
414             =head1 AUTHOR
415              
416             Andrew Sterling Hanenkamp <hanenkamp@cpan.org>
417              
418             =head1 COPYRIGHT AND LICENSE
419              
420             This software is copyright (c) 2014 by Qubling Software LLC.
421              
422             This is free software; you can redistribute it and/or modify it under
423             the same terms as the Perl 5 programming language system itself.
424              
425             =cut