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.142930';
3             # ABSTRACT: Tools for resolving an artifact value
4              
5 8     8   32 use Moose;
  8         8  
  8         40  
6              
7             with 'Bolts::Role::Artifact';
8              
9 8     8   35928 use Bolts::Util qw( locator_for meta_locator_for );
  8         16  
  8         56  
10 8     8   3156 use Carp ();
  8         9  
  8         145  
11 8     8   30 use List::MoreUtils qw( all );
  8         12  
  8         429  
12 8     8   42 use Moose::Util::TypeConstraints;
  8         12  
  8         70  
13 8     8   11638 use Safe::Isa;
  8         14  
  8         900  
14 8     8   41 use Scalar::Util qw( weaken reftype );
  8         12  
  8         1884  
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 8     8   39 no Moose::Util::TypeConstraints;
  8         12  
  8         37  
96              
97              
98             sub infer_injectors {
99 251     251 1 275 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 251 100       5686 if ($self->infer ne 'none') {
107 31         115 my $loc = locator_for($bag);
108 31         97 my $meta_loc = meta_locator_for($bag);
109              
110 31         720 my $inference_type = $self->infer;
111              
112 31         108 my $inferences = $meta_loc->acquire_all('inference');
113 31         991 my %injectors = map { $_->key => $_ } $self->all_injectors;
  121         2812  
114              
115 31         50 my @inferred_parameters;
116 31         62 for my $inference (@$inferences) {
117 31         698 push @inferred_parameters,
118             $inference->infer($self->blueprint);
119             }
120              
121             # use Data::Dumper;
122             # warn 'INFERRED: ', Dumper(\@inferred_parameters);
123              
124 31         55 PARAMETER: for my $inferred (@inferred_parameters) {
125 172         232 my $key = $inferred->{key};
126              
127 172 100       459 next PARAMETER if defined $injectors{ $key };
128              
129 51         203 my %params = %$inferred;
130 51         100 my $required = delete $params{required};
131 51         69 my $via = delete $params{inject_via};
132              
133 51         51 my $blueprint;
134 51 100       104 if ($inference_type eq 'options') {
135 48         237 $blueprint = $meta_loc->acquire('blueprint', 'given', {
136             required => $required,
137             });
138             }
139             else {
140 3         18 $blueprint = $meta_loc->acquire('blueprint', 'acquired', {
141             locator => $loc,
142             path => [ $key ],
143             });
144             }
145              
146 51         118 $params{blueprint} = $blueprint;
147              
148 51         153 my $injector = $meta_loc->acquire(@$via, \%params);
149 51 50       118 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         1863 $self->add_injector($injector);
155             }
156             }
157             }
158              
159              
160             sub such_that {
161 81     81 1 108 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 81 100       2577 $self->does_type($such_that->{does}) if defined $such_that->{does};
168 81 100       323 $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 251     251 1 9835 my ($self, $bag, %input_params) = @_;
190              
191 251 50       8382 $self->infer_injectors($bag) unless $self->is_inference_done;
192              
193 251         5617 my $name = $self->name;
194 251         5707 my $blueprint = $self->blueprint;
195 251         5492 my $scope = $self->scope;
196              
197 251         238 my $artifact;
198              
199             # Load the artifact from the scope unless the blueprint implies scope
200 251 100       736 $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 251 100       470 if (not defined $artifact) {
205              
206 225         201 my @bp_params;
207 225         7423 for my $injector ($self->all_injectors) {
208 540         1335 $injector->pre_inject($bag, \%input_params, \@bp_params);
209             }
210              
211 224         749 $artifact = $blueprint->get($bag, $name, @bp_params);
212              
213 224         12972 for my $injector ($self->all_injectors) {
214 539         976 $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 224 100       657 $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 250         6649 my $isa = $self->isa_type;
231 250         6486 my $does = $self->does_type;
232              
233 250         247 my $msg;
234 250 100       419 $msg = $isa->validate($artifact) if defined $isa;
235 250 100 33     1290 $msg //= $does->validate($artifact) if defined $does;
236              
237 250 100       20996 Carp::croak(qq[Constructed artifact named "$name" has the wrong type: $msg]) if $msg;
238              
239 249         977 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.142930
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