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.35';
5 57     57   623462 use Moose;
  57         1177896  
  57         429  
6 57     57   291005 use Moose::Util::TypeConstraints 'find_type_constraint';
  57         142  
  57         483  
7 57     57   27712 use MooseX::Params::Validate 0.14;
  57         293983  
  57         400  
8              
9 57     57   27964 use Bread::Board::Types;
  57         139  
  57         48942  
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 98 my $self = shift;
72 49         111 my ($type) = @_;
73              
74 49 100       1946 return $self->_get_type_mapping_for($type)
75             if $self->_has_type_mapping_for($type);
76              
77 1         32 for my $possible ($self->_mapped_types) {
78 1 50       73 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 65     65 1 284 my $self = shift;
87 65         136 my ($type) = @_;
88              
89 65 100       2394 return 1
90             if $self->_has_type_mapping_for($type);
91              
92 20         622 for my $possible ($self->_mapped_types) {
93 26 100       131 return 1
94             if $possible->isa($type);
95             }
96              
97 19         86 return;
98             }
99              
100             sub add_service {
101 227     227 1 1091 my ($self, $service) = @_;
102 227 50 33     1866 (blessed $service && $service->does('Bread::Board::Service'))
103             || confess "You must pass in a Bread::Board::Service instance, not $service";
104 227         56177 $service->parent($self);
105 227         6918 $self->services->{$service->name} = $service;
106             }
107              
108             sub add_sub_container {
109 33     33 1 2539 my ($self, $container) = @_;
110             (
111 33 50 66     378 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         1249 $container->parent($self);
119 33         1077 $self->sub_containers->{$container->name} = $container;
120             }
121              
122             sub add_type_mapping_for {
123 29     29 1 85 my ($self, $type, $service) = @_;
124              
125 29         115 my $type_constraint = find_type_constraint( $type );
126              
127 29 50       2782 (defined $type_constraint)
128             || confess "You must pass a valid Moose type, and it must exist already";
129              
130 29 50 33     192 (blessed $service && $service->does('Bread::Board::Service'))
131             || confess "You must pass in a Bread::Board::Service instance, not $service";
132              
133 29         2546 $self->type_mappings->{ $type_constraint->name } = $service;
134             }
135              
136             sub resolve {
137 135     135 1 112704 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       63222 ? %{ $params{'parameters'} }
  24         123  
145             : ();
146              
147 135 100       537 if (my $service_path = $params{'service'}) {
    50          
148 113         612 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     841 (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         19585 return $service->get( %parameters );
159             }
160             elsif (my $type = $params{'type'}) {
161              
162 22 50       83 ($self->has_type_mapping_for( $type ))
163             || confess "Could not find a mapped service for type ($type)";
164              
165 22         82 my $service = $self->get_type_mapping_for( $type );
166 22         135 my $result = $service->get( %parameters );
167              
168 22 50       242 (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         4396 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   476 no Moose::Util::TypeConstraints;
  57         129  
  57         349  
183 57     57   8849 no Moose;
  57         154  
  57         306  
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.35
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|/resolve ( ?service =>
360             $service_name, ?type => $type, ?parameters => { ... } ) >> that type,
361             we'll use that service to instantiate it.
362              
363             =head2 C<get_type_mapping_for>
364              
365             my $service = $container->get_type_mapping_for( $type_name );
366              
367             Returns the service to use to instantiate the given type name.
368              
369             Important: if a mapping for the exact type can't be found, but a
370             mapping for a I<subtype> of it can, you'll get the latter instead:
371              
372             package Superclass { use Moose };
373             package Subclass { use Moose; exends 'Superclass' };
374              
375             $c->add_type_mapping_for(
376             'Subclass',
377             Bread::Board::ConstructorInjection->new(name=>'sc',class=>'Subclass'),
378             );
379             my $o = $c->get_type_mapping_for('Superclass')->get;
380              
381             C<$o> is an instance of C<Subclass>. If there are more than one
382             sub-type mapped, you get a random one. This is probably a bad idea.
383              
384             =head2 C<has_type_mapping_for>
385              
386             if ($container->has_type_mapping_for( $type_name )) { ... }
387              
388             Returns true if we have a service defined to instantiate the given
389             type name, but see the note on
390             L<get_type_mapping_for|/get_type_mapping_for ( $type_name )> about
391             subtype mapping.
392              
393             =head2 C<resolve>
394              
395             my $object = $container->resolve(service=>$service_name);
396             my $object = $container->resolve(service=>$service_name,parameters=>\%p);
397              
398             When given a service name, this method will
399             L<fetch|Bread::Board::Traversable/fetch> the service, then call L<<
400             C<get>|Bread::Board::Service/get >> on it, optionally passing the
401             given parameters.
402              
403             my $object = $container->resolve(type=>$type);
404             my $object = $container->resolve(type=>$type,parameters=>\%p);
405              
406             When given a type name, this method will use
407             L<get_type_mapping_for|/get_type_mapping_for ( $type_name )> to get
408             the service, then call L<< C<get>|Bread::Board::Service/get >> on it,
409             optionally passing the given parameters. If the instance is not of the
410             expected type, the method will die.
411              
412             =head1 AUTHOR
413              
414             Stevan Little <stevan@iinteractive.com>
415              
416             =head1 BUGS
417              
418             Please report any bugs or feature requests on the bugtracker website
419             https://github.com/stevan/BreadBoard/issues
420              
421             When submitting a bug or request, please include a test-file or a
422             patch to an existing test-file that illustrates the bug or desired
423             feature.
424              
425             =head1 COPYRIGHT AND LICENSE
426              
427             This software is copyright (c) 2017, 2016, 2015, 2014, 2013, 2011, 2009 by Infinity Interactive.
428              
429             This is free software; you can redistribute it and/or modify it under
430             the same terms as the Perl 5 programming language system itself.
431              
432             =cut