File Coverage

blib/lib/Bread/Board/Container.pm
Criterion Covered Total %
statement 54 56 96.4
branch 19 30 63.3
condition 7 15 46.6
subroutine 12 12 100.0
pod 6 6 100.0
total 98 119 82.3


line stmt bran cond sub pod time code
1             package Bread::Board::Container;
2             our $AUTHORITY = 'cpan:STEVAN';
3             # ABSTRACT: A container for services and other containers
4             $Bread::Board::Container::VERSION = '0.37';
5 57     57   558596 use Moose;
  57         924101  
  57         472  
6 57     57   277128 use Moose::Util::TypeConstraints 'find_type_constraint';
  57         140  
  57         525  
7 57     57   27753 use MooseX::Params::Validate 0.14;
  57         242595  
  57         458  
8              
9 57     57   27955 use Bread::Board::Types;
  57         149  
  57         55074  
10              
11             with 'Bread::Board::Traversable';
12              
13             has 'name' => (
14             is => 'rw',
15             isa => 'Str',
16             required => 1
17             );
18              
19             has 'services' => (
20             traits => [ 'Hash', 'Clone' ],
21             is => 'rw',
22             isa => 'Bread::Board::Container::ServiceList',
23             coerce => 1,
24             lazy => 1,
25             default => sub{ +{} },
26             trigger => sub {
27             my $self = shift;
28             $_->parent($self) foreach values %{$self->services};
29             },
30             handles => {
31             'get_service' => 'get',
32             'has_service' => 'exists',
33             'get_service_list' => 'keys',
34             'has_services' => 'count',
35             }
36             );
37              
38             has 'sub_containers' => (
39             traits => [ 'Hash', 'Clone' ],
40             is => 'rw',
41             isa => 'Bread::Board::Container::SubContainerList',
42             coerce => 1,
43             lazy => 1,
44             default => sub{ +{} },
45             trigger => sub {
46             my $self = shift;
47             $_->parent($self) foreach values %{$self->sub_containers};
48             },
49             handles => {
50             'get_sub_container' => 'get',
51             'has_sub_container' => 'exists',
52             'get_sub_container_list' => 'keys',
53             'has_sub_containers' => 'count',
54             }
55             );
56              
57             has 'type_mappings' => (
58             traits => [ 'Hash' ],
59             is => 'rw',
60             isa => 'Bread::Board::Container::ServiceList',
61             lazy => 1,
62             default => sub{ +{} },
63             handles => {
64             '_get_type_mapping_for' => 'get',
65             '_has_type_mapping_for' => 'exists',
66             '_mapped_types' => 'keys',
67             }
68             );
69              
70             sub get_type_mapping_for {
71 49     49 1 118 my $self = shift;
72 49         109 my ($type) = @_;
73              
74 49 100       1999 return $self->_get_type_mapping_for($type)
75             if $self->_has_type_mapping_for($type);
76              
77 1         56 for my $possible ($self->_mapped_types) {
78 1 50       44 return $self->_get_type_mapping_for($possible)
79             if $possible->isa($type);
80             }
81              
82 0         0 return;
83             }
84              
85             sub has_type_mapping_for {
86 66     66 1 649 my $self = shift;
87 66         149 my ($type) = @_;
88              
89 66 100       2629 return 1
90             if $self->_has_type_mapping_for($type);
91              
92 21         809 for my $possible ($self->_mapped_types) {
93 26 100       147 return 1
94             if $possible->isa($type);
95             }
96              
97 20         101 return;
98             }
99              
100             sub add_service {
101 228     228 1 1415 my ($self, $service) = @_;
102 228 50 33     1738 (blessed $service && $service->does('Bread::Board::Service'))
103             || confess "You must pass in a Bread::Board::Service instance, not $service";
104 228         50173 $service->parent($self);
105 228         6163 $self->services->{$service->name} = $service;
106             }
107              
108             sub add_sub_container {
109 33     33 1 3268 my ($self, $container) = @_;
110             (
111 33 50 66     336 blessed $container &&
      66        
112             (
113             $container->isa('Bread::Board::Container')
114             ||
115             $container->isa('Bread::Board::Container::Parameterized')
116             )
117             ) || confess "You must pass in a Bread::Board::Container instance, not $container";
118 33         1074 $container->parent($self);
119 33         954 $self->sub_containers->{$container->name} = $container;
120             }
121              
122             sub add_type_mapping_for {
123 29     29 1 83 my ($self, $type, $service) = @_;
124              
125 29         107 my $type_constraint = find_type_constraint( $type );
126              
127 29 50       2831 (defined $type_constraint)
128             || confess "You must pass a valid Moose type, and it must exist already";
129              
130 29 50 33     209 (blessed $service && $service->does('Bread::Board::Service'))
131             || confess "You must pass in a Bread::Board::Service instance, not $service";
132              
133 29         2806 $self->type_mappings->{ $type_constraint->name } = $service;
134             }
135              
136             sub resolve {
137 135     135 1 91026 my ($self, %params) = validated_hash(\@_,
138             service => { isa => 'Str', optional => 1 },
139             type => { isa => 'Str', optional => 1 },
140             parameters => { isa => 'HashRef', optional => 1 },
141             );
142              
143             my %parameters = exists $params{'parameters'}
144 135 100       56423 ? %{ $params{'parameters'} }
  24         99  
145             : ();
146              
147 135 100       475 if (my $service_path = $params{'service'}) {
    50          
148 113         549 my $service = $self->fetch( $service_path );
149             # NOTE:
150             # we might want to allow Bread::Board::Service::Deferred::Thunk
151             # objects as well, but I am not sure that is a valid use case
152             # for this, so for now we just don't go there.
153             # - SL
154 109 0 33     884 (blessed $service && $service->does('Bread::Board::Service'))
    50          
155             || confess "You can only resolve services, "
156             . (defined $service ? $service : 'undef')
157             . " is not a Bread::Board::Service";
158 109         16407 return $service->get( %parameters );
159             }
160             elsif (my $type = $params{'type'}) {
161              
162 22 50       65 ($self->has_type_mapping_for( $type ))
163             || confess "Could not find a mapped service for type ($type)";
164              
165 22         121 my $service = $self->get_type_mapping_for( $type );
166 22         177 my $result = $service->get( %parameters );
167              
168 22 50       207 (find_type_constraint( $type )->check( $result ))
169             || confess "The result of the service for type ($type) did not"
170             . " pass the type constraint with $result";
171              
172 22         4304 return $result;
173             }
174             else {
175 0           confess "Cannot call resolve without telling it what to resolve.";
176             }
177              
178             }
179              
180             __PACKAGE__->meta->make_immutable;
181              
182 57     57   528 no Moose::Util::TypeConstraints;
  57         170  
  57         407  
183 57     57   9247 no Moose;
  57         154  
  57         342  
184              
185             1;
186              
187             __END__
188              
189             =pod
190              
191             =encoding UTF-8
192              
193             =head1 NAME
194              
195             Bread::Board::Container - A container for services and other containers
196              
197             =head1 VERSION
198              
199             version 0.37
200              
201             =head1 SYNOPSIS
202              
203             use Bread::Board;
204             my $c = container MCP => as {
205             container Users => as {
206             service flynn => ...;
207             service bradley => ...;
208             service dillinger => ...;
209             };
210              
211             container Programs => as {
212             container Rebels => as {
213             service tron => ...;
214             service yori => ...;
215             alias flynn => '/Users/flynn';
216             };
217              
218             # nested container
219             container Slaves => as {
220             service sark => ...;
221             service crom => ...;
222             };
223             };
224             };
225              
226             # OR directly...
227             my $guardians => Bread::Board::Container->new( name => 'Guardians' );
228             $guardians->add_service(
229             Bread::Board::ConstructorInjection->new(
230             name => 'dumont',
231             ...,
232             )
233             );
234             $c->get_sub_container('Programs')->add_sub_container($guardians);
235              
236             =head1 DESCRIPTION
237              
238             This class implements the container for L<Bread::Board>: a container
239             is a thing that contains services and other containers. Each container
240             and service has a name, so you end up with a tree of named nodes, just
241             like files and directories in a filesystem: each item can be
242             referenced using a path (see L<Bread::Board::Traversable> for the
243             details).
244              
245             =head1 ATTRIBUTES
246              
247             =head2 C<name>
248              
249             Read/write string, required. Every container needs a name, by which it
250             can be referenced when L<fetching it|Bread::Board::Traversable/fetch>.
251              
252             =head2 C<services>
253              
254             Hashref, constrained by L<<
255             C<Bread::Board::Container::ServiceList>|Bread::Board::Types/Bread::Board::Container::ServiceList
256             >>, mapping names to services directly contained in this
257             container. Every service added here will have its L<<
258             C<parent>|Bread::Board::Traversable/parent >> set to this container.
259              
260             You can pass an arrayref of services instead of a hashref, the keys
261             will be the names of the services.
262              
263             You should probably use L</add_service> and L</get_service> to
264             manipulate this attribute, instead of modifying it directly.
265              
266             =head2 C<sub_containers>
267              
268             Hashref, constrained by L<<
269             C<Bread::Board::Container::SubContainerList>|Bread::Board::Types/Bread::Board::Container::SubContainerList
270             >>, mapping names to containers directly contained in this
271             container. Every container added here will have its L<<
272             C<parent>|Bread::Board::Traversable/parent >> set to this container.
273              
274             You can pass an arrayref of containers instead of a hashref, the keys
275             will be the names of the containers.
276              
277             You should probably use L</add_sub_container> and
278             L</get_sub_container> to manipulate this attribute, instead of
279             modifying it directly.
280              
281             Containers added here can either be normal L<Bread::Board::Container>
282             or L<Bread::Board::Container::Parameterized>.
283              
284             =head1 METHODS
285              
286             =head2 C<add_service>
287              
288             $container->add_service($service);
289              
290             Adds a service into the L</services> map, using its name as the key.
291              
292             =head2 C<get_service>
293              
294             my $service = $container->get_service($name);
295              
296             Returns a service by name, or C<undef> if there's no such service in
297             the L</services> map.
298              
299             =head2 C<has_service>
300              
301             if ($container->has_service($name)) { ... }
302              
303             Returns true if a service with the given name name exists in the
304             L</services> map, false otherwise.
305              
306             =head2 C<has_services>
307              
308             if ($container->has_services) { ... }
309              
310             Returns true if the L</services> map contains any services, false if
311             it's empty.
312              
313             =head2 C<get_service_list>
314              
315             my @service_names = $container->get_service_list();
316              
317             Returns the names off all services present in the L</services> map.
318              
319             =head2 C<add_sub_container>
320              
321             $container->add_sub_container($container);
322              
323             Adds a container into the L</sub_containers> map, using its name as
324             the key.
325              
326             =head2 C<get_sub_container>
327              
328             my $container = $container->get_sub_container($name);
329              
330             Returns a container by name, or C<undef> if there's no such container
331             in the L</sub_containers> map.
332              
333             =head2 C<has_sub_container>
334              
335             if ($container->has_sub_container($name)) { ... }
336              
337             Returns true if a container with the given name name exists in the
338             L</sub_containers> map, false otherwise.
339              
340             =head2 C<has_sub_containers>
341              
342             if ($container->has_sub_containers) { ... }
343              
344             Returns true if the L</sub_containers> map contains any contains,
345             false if it's empty.
346              
347             =head2 C<get_sub_container_list>
348              
349             my @container_names = $container->get_sub_container_list();
350              
351             Returns the names off all containers present in the L</sub_containers>
352             map.
353              
354             =head2 C<add_type_mapping_for>
355              
356             $containers->add_type_mapping_for( $type_name, $service );
357              
358             Adds a mapping from a L<Moose type|Moose::Util::TypeConstraints> to a
359             service: whenever we try to L</resolve> that type,
360             we'll use that service to instantiate it.
361              
362             =head2 C<get_type_mapping_for>
363              
364             my $service = $container->get_type_mapping_for( $type_name );
365              
366             Returns the service to use to instantiate the given type name.
367              
368             Important: if a mapping for the exact type can't be found, but a
369             mapping for a I<subtype> of it can, you'll get the latter instead:
370              
371             package Superclass { use Moose };
372             package Subclass { use Moose; exends 'Superclass' };
373              
374             $c->add_type_mapping_for(
375             'Subclass',
376             Bread::Board::ConstructorInjection->new(name=>'sc',class=>'Subclass'),
377             );
378             my $o = $c->get_type_mapping_for('Superclass')->get;
379              
380             C<$o> is an instance of C<Subclass>. If there are more than one
381             sub-type mapped, you get a random one. This is probably a bad idea.
382              
383             =head2 C<has_type_mapping_for>
384              
385             if ($container->has_type_mapping_for( $type_name )) { ... }
386              
387             Returns true if we have a service defined to instantiate the given
388             type name, but see the note on
389             L</get_type_mapping_for> about
390             subtype mapping.
391              
392             =head2 C<resolve>
393              
394             my $object = $container->resolve(service=>$service_name);
395             my $object = $container->resolve(service=>$service_name,parameters=>\%p);
396              
397             When given a service name, this method will
398             L<fetch|Bread::Board::Traversable/fetch> the service, then call L<<
399             C<get>|Bread::Board::Service/get >> on it, optionally passing the
400             given parameters.
401              
402             my $object = $container->resolve(type=>$type);
403             my $object = $container->resolve(type=>$type,parameters=>\%p);
404              
405             When given a type name, this method will use
406             L</get_type_mapping_for> to get
407             the service, then call L<< C<get>|Bread::Board::Service/get >> on it,
408             optionally passing the given parameters. If the instance is not of the
409             expected type, the method will die.
410              
411             =head1 AUTHOR
412              
413             Stevan Little <stevan@iinteractive.com>
414              
415             =head1 BUGS
416              
417             Please report any bugs or feature requests on the bugtracker website
418             https://github.com/stevan/BreadBoard/issues
419              
420             When submitting a bug or request, please include a test-file or a
421             patch to an existing test-file that illustrates the bug or desired
422             feature.
423              
424             =head1 COPYRIGHT AND LICENSE
425              
426             This software is copyright (c) 2019, 2017, 2016, 2015, 2014, 2013, 2011, 2009 by Infinity Interactive.
427              
428             This is free software; you can redistribute it and/or modify it under
429             the same terms as the Perl 5 programming language system itself.
430              
431             =cut