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   67362 use Moose::Role;
  4         9  
  4         34  
4 4     4   20440 use Catalyst::Utils;
  4         10  
  4         108  
5 4     4   1406 use Catalyst::Plugin::MapComponentDependencies::Utils;
  4         8  
  4         1982  
6              
7             requires 'config_for';
8              
9             our $VERSION = '0.007';
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 2 my ($self, $name, $args) = @_;
24 1 50       2 die "Component $name exists" if
25             $self->$plugin_config->{$name};
26 1         3 $self->$plugin_config->{$name} = $args;
27             }
28            
29             sub map_dependencies {
30 13     13 1 4401 my $self = shift;
31 13         45 while(@_) {
32 1         3 $self->map_dependency(shift, shift);
33             }
34              
35 13         30 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             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             =head1 METHODS
198              
199             This plugin defines the following methods
200              
201             =head2 map_dependencies
202              
203             Example:
204              
205             MyApp->map_dependencies(
206             'Model::AnotherModel' => { aaa => 'Model::Foo' },
207             'Model::Foo' => {
208             bar => 'Model::Bar',
209             baz => sub {
210             my ($app_or_ctx, $component_name, $config) = @_;
211             return ...;
212             },
213             },
214             );
215              
216             Maps a list of components and dependencies.
217              
218             =head1 map_dependency
219              
220             Maps a single component to a hashref of dependencies.
221              
222             =head1 CONFIGURATION
223              
224             This plugin defines the configuration namespace 'Plugin::MapComponentDependencies'
225             and defines the following keys:
226              
227             =head2 map_dependencies
228              
229             A Hashref where the key is a target component and the value is a hashref of arguments
230             that will be sent to it during initializion.
231              
232             MyApp->config(
233             'Plugin::MapComponentDependencies' => {
234             map_dependencies => {
235             'Model::Foo' => {
236             bar => 'Model::Bar',
237             baz => sub {
238             my ($app_or_ctx, $component_name, $from_config_args) = @_;
239             return ...;
240             },
241             },
242             },
243             },
244             );
245              
246             B<NOTE:> for simplicity you can place your hashref of configuration directly under the
247             plugin namespace.
248              
249             MyApp->config(
250             'Plugin::MapComponentDependencies' => {
251             'Model::Foo' => {
252             bar => 'Model::Bar',
253             baz => sub {
254             my ($app_or_ctx, $component_name, $from_config_args) = @_;
255             return ...;
256             },
257             },
258             },
259             );
260              
261             =head1 SEE ALSO
262              
263             L<Catalyst>, L<Catalyst::Plugin::InjectionHelpers>.
264              
265             =head1 AUTHOR
266            
267             John Napiorkowski L<email:jjnapiork@cpan.org>
268            
269             =head1 COPYRIGHT & LICENSE
270            
271             Copyright 2015, John Napiorkowski L<email:jjnapiork@cpan.org>
272            
273             This library is free software; you can redistribute it and/or modify it under
274             the same terms as Perl itself.
275              
276             =cut