File Coverage

blib/lib/Catalyst/Plugin/MapComponentDependencies.pm
Criterion Covered Total %
statement 16 16 100.0
branch 1 2 50.0
condition n/a
subroutine 5 5 100.0
pod 1 2 50.0
total 23 25 92.0


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::MapComponentDependencies;
2              
3 4     4   101129 use Moose::Role;
  4         11  
  4         39  
4 4     4   24023 use Catalyst::Utils;
  4         10  
  4         126  
5 4     4   2152 use Catalyst::Plugin::MapComponentDependencies::Utils;
  4         11  
  4         1782  
6              
7             requires 'config_for', 'component';
8              
9             our $VERSION = '0.009';
10              
11             # Allow a shorthand version of config, should make it a bit
12             # more tidy and I doubt we will ever need configuration for
13             # the plugin itself.
14              
15             my $plugin_config = sub {
16             my $config_namespace = shift->config->{'Plugin::MapComponentDependencies'} ||= +{};
17             return exists $config_namespace->{map_dependencies} ?
18             $config_namespace->{map_dependencies} :
19             $config_namespace;
20             };
21              
22             sub map_dependency {
23 1     1 0 3 my ($self, $name, $args) = @_;
24 1 50       5 die "Component $name exists" if
25             $self->$plugin_config->{$name};
26 1         6 $self->$plugin_config->{$name} = $args;
27             }
28            
29             sub map_dependencies {
30 15     15 1 7535 my $self = shift;
31 15         58 while(@_) {
32 1         6 $self->map_dependency(shift, shift);
33             }
34              
35 15         73 return $self->$plugin_config;
36             }
37              
38             around 'config_for', sub {
39             my ($orig, $app_or_ctx, $component_name, @args) = @_;
40             my $config = $app_or_ctx->$orig($component_name, @args);
41             my $component_suffix = Catalyst::Utils::class2classsuffix($component_name);
42              
43             # Walk the values and expand as needed
44             $config = Catalyst::Plugin::MapComponentDependencies::Utils::_expand_config($app_or_ctx, $component_name, $config);
45              
46             if(my $dependencies = $app_or_ctx->map_dependencies->{$component_suffix}) {
47             # walk the value tree for $dependencies.
48             my $mapped_config = +{}; # shallow clone... might need something better than all this later
49             foreach my $key (keys %$dependencies) {
50             if((ref($dependencies->{$key}) ||'') eq 'CODE') {
51             $mapped_config->{$key} = $dependencies->{$key}->($app_or_ctx, $component_name, $config);
52             } else {
53             $mapped_config->{$key} = $app_or_ctx->component($dependencies->{$key}) ||
54             die "'${\$dependencies->{$key}}' is not a component...";
55             }
56             }
57              
58             return my $merged = Catalyst::Utils::merge_hashes($config, $mapped_config);
59             } else {
60             return $config;
61             }
62             };
63              
64             1;
65              
66             =head1 NAME
67              
68             Catalyst::Plugin::MapComponentDependencies - Allow components to depend on other components
69              
70             =head1 SYNOPSIS
71              
72             package MyApp;
73              
74             use Moose;
75             use Catalyst;
76              
77             with 'Catalyst::Plugin::MapComponentDependencies';
78              
79             MyApp->map_dependencies(
80             'Model::Foo' => {
81             bar => 'Model::Bar',
82             baz => sub {
83             my ($app_or_ctx, $component_name) = @_;
84             return ...;
85             },
86             },
87             );
88              
89             MyApp->config(
90             'Model::Foo' => { another_param => 'value' }
91             )
92              
93             MyApp->setup;
94              
95             During setup when 'Model::Foo' is created it will get all three key / value pairs
96             send to ->new.
97              
98             B<NOTE:> You need to compose this plugin via the L<Moose> 'with' subroutine if you
99             want to get the handy class methods 'map_dependencies' and 'map_dependency'. If you
100             prefer you may setup you dependencies via configuration:
101              
102             package MyApp;
103              
104             use Catalyst 'MapComponentDependencies';
105              
106             MyApp->config(
107             'Model::Foo' => { another_param => 'value' },
108             'Plugin::MapComponentDependencies' => {
109             map_dependencies => {
110             'Model::Foo' => {
111             bar => 'Model::Bar',
112             baz => sub {
113             my ($app_or_ctx, $component_name, $from_config_args) = @_;
114             return ...;
115             },
116             },
117             },
118             },
119             );
120              
121             MyApp->setup;
122              
123             Alternatively you may choose a 'shorthand' version of the configuration based
124             approach:
125              
126             package MyApp;
127              
128             use Catalyst 'MapComponentDependencies';
129              
130             MyApp->config(
131             'Model::Foo' => { another_param => 'value' },
132             'Plugin::MapComponentDependencies' => {
133             'Model::Foo' => {
134             bar => 'Model::Bar',
135             baz => sub {
136             my ($app_or_ctx, $component_name, $from_config_args) = @_;
137             return ...;
138             },
139             },
140             },
141             );
142              
143             MyApp->setup;
144              
145             You may prefer this if your dependencies will map differently based on environment
146             and configuration settings.
147              
148             Lastly you may use the helper utilities to create a single merged configuration
149             for your dependencies:
150              
151             package MyApp;
152              
153             use Moose;
154             use Catalyst 'MapComponentDependencies;
155             use Catalyst::Plugin::MapComponentDependencies::Utils ':All';
156              
157             MyApp->config(
158             'Model::Bar' => { key => 'value' },
159             'Model::Foo' => {
160             bar => FromModel 'Bar',
161             baz => FromCode {
162             my ($app_or_ctx, $component_name) = @_;
163             return ...;
164             },
165             another_param => 'value',
166             },
167             );
168              
169             MyApp->setup;
170              
171             See L<Catalyst::Plugin::MapComponentDependencies::Utils> for more.
172              
173             =head1 DESCRIPTION
174              
175             Sometimes you would like a L<Catalyst> component to depend on the value of an
176             existing component. Since components are resolved during application setup (or
177             at request time, in the cause of a component that does ACCEPT_CONTEXT) you cannot
178             specify this dependency mapping in the 'normal' L<Catalyst> configuration hash.
179              
180             This plugin, which requires a recent L<Catalyst> of version 5.90090+, allows you to
181             define components which depend on each other. You can also set the value of an
182             initial argument to the value of a coderef, for added dynamic flexibility.
183              
184             You may define dependencies in one of two ways. The first way is to use a key/value
185             pair to map a configuation key attribute to the value of an existing L<Catalyst>
186             model. When the depending model is called, we get the value of that model in the
187             same way as if we called '$c->model($name)'. You can by the way use any type of
188             L<Catalys> component as a value (models, views and even controllers).
189              
190             The second way is to use a coderef, which is expected to return a value suitable for
191             the depending model. This gives you a little more flexibility for crafting very
192             custom types of dependencies. If you use a coderef you will get three values,
193             the application (or context depending on if the depending model does ACCEPT_CONTEXT),
194             the component name and a reference to any static configuration for the model (from
195             the global configuration, for example).
196              
197             B<NOTE>: Currently we only map dependencies 'one level' into the configuration
198             hash. Which means the following works as expected:
199              
200             MyApp->map_dependencies(
201             'Model::Foo' => {
202             baz => sub {
203             my ($app_or_ctx, $component_name, $config) = @_;
204             return ...;
205             },
206             },
207             );
208              
209             But not this:
210              
211             MyApp->map_dependencies(
212             'Model::Foo' => {
213             baz => {
214             bar => sub {
215             my ($app_or_ctx, $component_name, $config) = @_;
216             return ...;
217             },
218             },
219             },
220             );
221              
222             I'm not sure if I consider this limitation a feature or not... If you have good
223             use cases let me know and I'll consider a fix.
224              
225             =head1 METHODS
226              
227             This plugin defines the following methods
228              
229             =head2 map_dependencies
230              
231             Example:
232              
233             MyApp->map_dependencies(
234             'Model::AnotherModel' => { aaa => 'Model::Foo' },
235             'Model::Foo' => {
236             bar => 'Model::Bar',
237             baz => sub {
238             my ($app_or_ctx, $component_name, $config) = @_;
239             return ...;
240             },
241             },
242             );
243              
244             Maps a list of components and dependencies.
245              
246             =head1 map_dependency
247              
248             Maps a single component to a hashref of dependencies.
249              
250             =head1 CONFIGURATION
251              
252             This plugin defines the configuration namespace 'Plugin::MapComponentDependencies'
253             and defines the following keys:
254              
255             =head2 map_dependencies
256              
257             A Hashref where the key is a target component and the value is a hashref of arguments
258             that will be sent to it during initializion.
259              
260             MyApp->config(
261             'Plugin::MapComponentDependencies' => {
262             map_dependencies => {
263             'Model::Foo' => {
264             bar => 'Model::Bar',
265             baz => sub {
266             my ($app_or_ctx, $component_name, $from_config_args) = @_;
267             return ...;
268             },
269             },
270             },
271             },
272             );
273              
274             B<NOTE:> for simplicity you can place your hashref of configuration directly under the
275             plugin namespace.
276              
277             MyApp->config(
278             'Plugin::MapComponentDependencies' => {
279             'Model::Foo' => {
280             bar => 'Model::Bar',
281             baz => sub {
282             my ($app_or_ctx, $component_name, $from_config_args) = @_;
283             return ...;
284             },
285             },
286             },
287             );
288              
289             =head1 SEE ALSO
290              
291             L<Catalyst>, L<Catalyst::Plugin::InjectionHelpers>.
292              
293             =head1 AUTHOR
294            
295             John Napiorkowski L<email:jjnapiork@cpan.org>
296            
297             =head1 COPYRIGHT & LICENSE
298            
299             Copyright 2015, John Napiorkowski L<email:jjnapiork@cpan.org>
300            
301             This library is free software; you can redistribute it and/or modify it under
302             the same terms as Perl itself.
303              
304             =cut