File Coverage

blib/lib/Bread/Board/Service/WithDependencies.pm
Criterion Covered Total %
statement 40 42 95.2
branch 9 10 90.0
condition 10 12 83.3
subroutine 10 11 90.9
pod 1 1 100.0
total 70 76 92.1


line stmt bran cond sub pod time code
1             package Bread::Board::Service::WithDependencies;
2             our $AUTHORITY = 'cpan:STEVAN';
3             # ABSTRACT: Services with dependencies
4             $Bread::Board::Service::WithDependencies::VERSION = '0.37';
5 63     63   35346 use Moose::Role;
  63         161  
  63         480  
6              
7 63     63   328618 use Try::Tiny;
  63         1568  
  63         3950  
8              
9 63     63   416 use Bread::Board::Types;
  63         116  
  63         1594  
10 63     63   28794 use Bread::Board::Service::Deferred;
  63         164  
  63         2055  
11 63     63   25779 use Bread::Board::Service::Deferred::Thunk;
  63         243  
  63         37520  
12              
13             with 'Bread::Board::Service';
14              
15             has 'dependencies' => (
16             traits => [ 'Hash', 'Clone' ],
17             is => 'rw',
18             isa => 'Bread::Board::Service::Dependencies',
19             lazy => 1,
20             coerce => 1,
21             default => sub { +{} },
22             trigger => sub {
23             my $self = shift;
24             $_->parent($self) foreach values %{$self->dependencies};
25             },
26             handles => {
27             'add_dependency' => 'set',
28             'get_dependency' => 'get',
29             'has_dependency' => 'exists',
30             'has_dependencies' => 'count',
31             'get_all_dependencies' => 'kv',
32             }
33             );
34              
35             around 'init_params' => sub {
36             my $next = shift;
37             my $self = shift;
38             +{ %{ $self->$next() }, $self->resolve_dependencies }
39             };
40              
41             after 'get' => sub { (shift)->clear_params };
42              
43             sub resolve_dependencies {
44 224     224 1 474 my $self = shift;
45 224         408 my %deps;
46 224 100       9461 if ($self->has_dependencies) {
47 159         6312 foreach my $dep ($self->get_all_dependencies) {
48 215         1286 my ($key, $dependency) = @$dep;
49              
50 215         6043 my $service = $dependency->service;
51              
52             # NOTE:
53             # this is what checks for
54             # circular dependencies
55 215 100       5888 if ($service->is_locked) {
56              
57 2 50 33     7 confess "You cannot defer a parameterized service"
58             if $service->does('Bread::Board::Service::WithParameters')
59             && $service->has_parameters;
60              
61 2         16 $deps{$key} = Bread::Board::Service::Deferred->new(service => $service);
62             }
63             else {
64             # since we can't pass in parameters here,
65             # we return a deferred thunk and you can do
66             # with it what you will.
67 213 100 100     775 if (
      100        
      100        
68             $service->does('Bread::Board::Service::WithParameters')
69             &&
70             $service->has_required_parameters
71             &&
72             (not $service->has_parameter_defaults)
73             &&
74             (not $dependency->has_service_params)
75             ) {
76             $deps{$key} = Bread::Board::Service::Deferred::Thunk->new(
77             thunk => sub {
78 1     1   3 my %params = @_;
79 1         5 $service->lock;
80 1         75 return try { $service->get( %params ) }
81 1         32 finally { $service->unlock }
82 0         0 catch { die $_ }
83 1         8 }
84 1         15 );
85             }
86             else {
87 212         6235 $service->lock;
88             try {
89             $deps{$key} = $dependency->has_service_params
90 212 100   212   21609 ? $service->get( %{ $dependency->service_params })
  3         90  
91             : $service->get;
92             } finally {
93 212     212   5072 $service->unlock
94             } catch {
95 0     0   0 die $_
96 212         2225 };
97             }
98             }
99             }
100             }
101 224         9649 return %deps;
102             }
103              
104 63     63   690 no Moose::Role; 1;
  63         173  
  63         419  
105              
106             __END__
107              
108             =pod
109              
110             =encoding UTF-8
111              
112             =head1 NAME
113              
114             Bread::Board::Service::WithDependencies - Services with dependencies
115              
116             =head1 VERSION
117              
118             version 0.37
119              
120             =head1 DESCRIPTION
121              
122             This is a sub-role of L<Bread::Board::Service>, for services with
123             dependencies. It provides the mechanism to recursively resolve
124             dependencies.
125              
126             =head1 ATTRIBUTES
127              
128             =head2 C<dependencies>
129              
130             Hashref, constrained by L<<
131             C<Bread::Board::Service::Dependencies>|Bread::Board::Types/Bread::Board::Service::Dependencies
132             >>. Values must be instances of L<Bread::Board::Dependency>, but can
133             be coerced from various other types, see L<the type's
134             docs|Bread::Board::Types/Bread::Board::Service::Dependencies>.
135              
136             =head1 METHODS
137              
138             =head2 C<add_dependency>
139              
140             $service->add_dependency(name=>$dep);
141              
142             Adds a new dependency.
143              
144             =head2 C<get_dependency>
145              
146             my $dep = $service->get_dependency('name');
147              
148             Gets a dependency by name.
149              
150             =head2 C<has_dependency>
151              
152             if ($service->has_dependency('name')) { ... }
153              
154             Returns true if this service has a dependency with the given name.
155              
156             =head2 C<has_dependencies>
157              
158             if ($service->has_dependencies) { ... }
159              
160             Returns true if this service has any dependency.
161              
162             =head2 C<get_all_dependencies>
163              
164             my %deps = $service->get_all_dependencies;
165              
166             Returns all the dependencies for this service, as a key-value list.
167              
168             =head2 C<init_params>
169              
170             Builder for the service parameters, augmented to inject all the
171             L<resolved dependencies|/resolve_dependencies> into the L<<
172             C<params>|Bread::Board::Service/params >> attribute, so that C<get>
173             can use them.
174              
175             =head2 C<get>
176              
177             I<After> the C<get> method, the L<<
178             C<params>|Bread::Board::Service/params >> attribute is cleared, to
179             make sure that dependencies will be resolved again on the next call (of
180             course, if the service is using a L<singleton
181             lifecycle|Bread::Board::LifeCycle::Singleton>, the whole "getting"
182             only happens once).
183              
184             =head2 C<resolve_dependencies>
185              
186             my %name_object_map = $self->resolve_dependencies;
187              
188             For each element of L</dependencies>, calls its L<<
189             C<service>|Bread::Board::Dependency/service >> method to retrieve the
190             service we're dependent on, then tries to instantiate the value of the
191             service. This can happen in a few different ways:
192              
193             =over 4
194              
195             =item the service is not locked, and does not require any parameter
196              
197             just call C<get> on it
198              
199             =item the service is not locked, requires parameters, but the dependency has values for them
200              
201             call C<< $service->get(%{$dependency->service_params}) >>
202              
203             =item the service is not locked, requires parameters, and we don't have values for them
204              
205             we can't instantiate anything at this point, so we use a
206             L<Bread::Board::Service::Deferred::Thunk> instance, on which you can
207             call the C<inflate> method, passing it all the needed parameters, to
208             get the actual instance
209              
210             =item the service is locked
211              
212             we return a L<Bread::Board::Service::Deferred> that will proxy to the
213             instance that the service will eventually return; yes, this means that
214             in many cases circular dependencies can be resolved, at the cost of a
215             proxy object
216              
217             =back
218              
219             =head1 AUTHOR
220              
221             Stevan Little <stevan@iinteractive.com>
222              
223             =head1 BUGS
224              
225             Please report any bugs or feature requests on the bugtracker website
226             https://github.com/stevan/BreadBoard/issues
227              
228             When submitting a bug or request, please include a test-file or a
229             patch to an existing test-file that illustrates the bug or desired
230             feature.
231              
232             =head1 COPYRIGHT AND LICENSE
233              
234             This software is copyright (c) 2019, 2017, 2016, 2015, 2014, 2013, 2011, 2009 by Infinity Interactive.
235              
236             This is free software; you can redistribute it and/or modify it under
237             the same terms as the Perl 5 programming language system itself.
238              
239             =cut