File Coverage

blib/lib/CatalystX/Imports.pm
Criterion Covered Total %
statement 63 102 61.7
branch 5 22 22.7
condition n/a
subroutine 17 20 85.0
pod 4 4 100.0
total 89 148 60.1


line stmt bran cond sub pod time code
1             package CatalystX::Imports;
2              
3             =head1 NAME
4              
5             CatalystX::Imports - Shortcut functions for L<Catalyst> controllers
6              
7             =cut
8              
9 4     4   1479751 use warnings;
  4         6  
  4         104  
10 4     4   13 use strict;
  4         3  
  4         86  
11              
12 4         253 use vars qw(
13             $VERSION
14             $STORE_CONTROLLER $STORE_CONTEXT $STORE_ARGUMENTS
15             $ACTION_WRAPPER_VAR
16 4     4   11 );
  4         9  
17              
18 4     4   1707 use Class::Inspector;
  4         9626  
  4         110  
19 4     4   1203 use Carp::Clan qw{ ^CatalystX::Imports(?:::|$) };
  4         7016  
  4         24  
20 4     4   1934 use Filter::EOF;
  4         5993  
  4         15  
21              
22             =head1 VERSION
23              
24             0.01_01
25              
26             =cut
27              
28             $VERSION = '0.01_01';
29             $VERSION = eval $VERSION;
30              
31             =head1 SYNOPSIS
32              
33             package MyApp::Controller::User;
34             use base 'Catalyst::Controller';
35              
36             use CatalystX::Imports
37             Context => { Default => [qw( :all )],
38             Config => [{model => 'model_name'}, 'template'] },
39             Vars => { Stash => [qw( $user $user_rs $template )],
40             Session => [qw( @shown_users )] };
41              
42             sub list: Chained {
43             $user_rs = model(model_name)->search_rs;
44             }
45              
46             sub load: Chained PathPart('') CaptureArgs(1) {
47             $user = model(model_name)->find($args[0]);
48             }
49              
50             sub view: Chained('load') {
51             push @shown_users, $user->id;
52             $template = template;
53             }
54              
55             1;
56              
57             =head1 BIG FAT WARNING
58              
59             This is B<ALPHA SOFTWARE>! You're strongly advised against using this
60             module in a production environment. Use at your own risk.
61              
62             =head1 DESCRIPTION
63              
64             This module exports commonly used functionality and shortcuts to
65             L<Catalyst>s own feature set into your controller. Currently, these
66             groups of exports are available:
67              
68             =head2 Context Exports
69              
70             See also L<CatalystX::Imports::Context>. This will export functions
71             into your namespace that will allow you to access common methods and
72             values easier. As an example see the uses of
73             L<stash|CatalystX::Imports::Context::Default/stash>,
74             L<model|CatalystX::Imports::Context::Default/model> and
75             L<args|CatalystX::Imports::Context::Default/args> in the L</SYNOPSIS>.
76              
77             You can ask for these imports by specifying a C<Context> argument on
78             the C<use> line:
79              
80             use CatalystX::Imports Context => ...
81              
82             The C<Config> library is a special case that has no predefined
83             exports, but allows you to import accessors to your local controller
84             configuration.
85              
86             =head2 Variable Exports
87              
88             See also L<CatalystX::Imports::Vars>. With this module, you can import
89             the C<$self>, C<$ctx> and C<@args> variables as if you'd have done
90              
91             my ($self, $ctx, @args) = @_;
92              
93             in one of your actions. It also allows you to import variables bound to
94             values in the stash or session stores, like shown in the L</SYNOPSIS>.
95              
96             You can use this functionality via the C<Vars> argument on the C<use>
97             line:
98              
99             use CatalystX::Imports Vars => ...
100              
101             =cut
102              
103             # names of the localized stores in the controllers
104             $STORE_CONTROLLER = 'CATALYSTX_IMPORTS_STORE_CONTROLLER';
105             $STORE_CONTEXT = 'CATALYSTX_IMPORTS_STORE_CONTEXT';
106             $STORE_ARGUMENTS = 'CATALYSTX_IMPORTS_STORE_ARGUMENTS';
107              
108             # where the wrappers for action calls will be sitting
109             $ACTION_WRAPPER_VAR = 'CATALYSTX_IMPORTS_ACTION_WRAPPERS';
110              
111             =head1 METHODS
112              
113             =cut
114              
115             =head2 import
116              
117             This is a method used by all subclasses. When called, it fetches the
118             caller as target (the C<use>ing class) and passes it to the
119             C<export_into> method that must be implemented by a C<use>able class.
120              
121             It also makes sure that L</install_action_wrap_into> is called after
122             the initial runtime of your controller.
123              
124             =cut
125              
126             sub import {
127 4     4   1512340 my ($class, @args) = @_;
128              
129             # the class that 'use'd us
130 4         8 my $caller = scalar caller;
131              
132             # call install_action_wrap_into after package runtime
133             Filter::EOF->on_eof_call( sub {
134 1     1   199 my $eof = shift;
135 1         4 $$eof = "; ${class}->install_action_wrap_into('${caller}'); 1;";
136 4         25 });
137              
138             # call current export mechanism
139 4         76 return $class->export_into($caller, @args);
140             }
141              
142             =head2 register_action_wrap_in
143              
144             Takes a code reference and a target and registers the reference to
145             be a wrapper for action code. As an example, without any functionality:
146              
147             CatalystX::Imports->register_action_wrap_in($class, sub {
148             my $code = shift;
149             my @wrappers = @{ shift(@_) };
150              
151             # ... put your code here ...
152              
153             if (my $wrapper = shift @wrappers) {
154             return $wrapper->($code, [@wrappers], @_);
155             }
156             else {
157             return $code->(@_);
158             }
159             });
160              
161             =cut
162              
163             sub register_action_wrap_in {
164 0     0 1 0 my ($class, $target, $code) = @_;
165 4     4   530 no strict 'refs';
  4         5  
  4         81  
166 4     4   11 no warnings 'once';
  4         5  
  4         632  
167 0         0 push @{ "${target}::${ACTION_WRAPPER_VAR}" }, $code;
  0         0  
168 0         0 return 1;
169             }
170              
171             =head2 install_action_wrap_into
172              
173             This module needs a few parts of data to provide it's functionality.
174             Namely, the current controller and context object, as well as the
175             arguments to the last called action. To get to these, it will simply
176             wrap all action code in your controller. This is what this function
177             does, essentially.
178              
179             =cut
180              
181             sub install_action_wrap_into {
182 1     1 1 808 my ($class, $target) = @_;
183              
184             # get all action cache entries
185 1         2 my @actions = @{ $target->_action_cache };
  1         5  
186              
187             # lookup map with all the names of the methods
188             my %methods
189 31         542 = map { ($target->can($_) => $_) }
190 1 50       4 @{ Class::Inspector->functions($target) || [] };
  1         4  
191              
192             # replace every action code with a wrapper
193 1         5 for my $action (@actions) {
194 0         0 my $original = $action->[0];
195              
196             # only methods in that package are wanted
197 0 0       0 next unless exists $methods{ $original };
198              
199             # the wrapper fetches controller, context and args and stores
200             # them for other parts of the CX:I module
201             my $wrapped = sub {
202 0     0   0 my ($self, $c, @args) = @_;
203              
204             # fetch registered action call wrappers
205 0         0 my @wrappers = do {
206 4     4   19 no strict 'refs';
  4         5  
  4         223  
207 0         0 @{ "${target}::${ACTION_WRAPPER_VAR}" };
  0         0  
208             };
209              
210             # defines where the needed object will be stored
211 0         0 my %mapping = (
212             CONTROLLER => $self,
213             CONTEXT => $c,
214             ARGUMENTS => \@args,
215             );
216              
217             # store the objects
218 4     4   26 { no strict 'refs';
  4         5  
  4         456  
  0         0  
219 0         0 ${ "${target}::CATALYSTX_IMPORTS_STORE_${_}" }
220             = $mapping{ $_ }
221 0         0 for keys %mapping;
222             }
223              
224             # call original code with original arguments
225 0 0       0 unless (@wrappers) {
226 0         0 return $original->(@_);
227             }
228              
229             # delegate to wrapper
230             else {
231 0         0 my $wrapper = shift @wrappers;
232 0         0 return $wrapper->($original, [@wrappers], @_);
233             }
234 0         0 };
235              
236             # set the new code
237 0         0 $action->[0] = $wrapped;
238              
239             # replace name in attribute cache
240 0         0 my $attrs = delete $target->_attr_cache->{ $original };
241 0         0 $target->_attr_cache->{ $wrapped } = $attrs;
242              
243             # replace code reference in package
244 4     4   13 { no strict 'refs';
  4         4  
  4         88  
  0         0  
245 4     4   12 no warnings 'redefine';
  4         3  
  4         1376  
246 0         0 *{ "${target}::$methods{$original}" } = $wrapped;
  0         0  
247             }
248             }
249              
250 1         4 $target->_action_cache( \@actions );
251              
252 1         5 return 1;
253             }
254              
255             =head2 export_into
256              
257             Tells every specified exporter class (C<Context>, etc.) to export
258             themselves and passes their respective arguments.
259              
260             =cut
261              
262             sub export_into {
263 10     10 1 6482 my ($class, $target, @args) = @_;
264              
265             # we need exporter => options pairs
266 10 100       36 croak 'CatalystX::Imports expects a key/value list as argument'
267             if @args % 2;
268 7         18 my %exporters = @args;
269              
270             # walk the exporters list and let every one export itself
271             # to the target class
272 7         16 for my $exporter (keys %exporters) {
273 6         15 my $exporter_class = __PACKAGE__ . "::$exporter";
274 6         14 $class->_ensure_class_loaded($exporter_class);
275 3         18 $exporter_class->export_into($target, $exporters{ $exporter });
276             }
277              
278 2         13 return 1;
279             }
280              
281             =head2 _ensure_class_loaded
282              
283             Convenience method that tries to requires the passed package if it isn't
284             already loaded. Will return -1 if it had to be required, and 1 if it was
285             already loaded.
286              
287             =cut
288              
289             sub _ensure_class_loaded {
290 7     7   9 my ($class, $target) = @_;
291              
292             # load the class only once
293 7 100       33 unless (Class::Inspector->loaded($target)) {
294 3         180 require Class::Inspector->filename($target);
295 0         0 return -1;
296             }
297              
298 4         101 return 1;
299             }
300              
301             =head2 resolve_component
302              
303             Some functionality will allow you to prefix used components with a
304             configurable string. They will use this method to find a component
305             according to the current configuration.
306              
307             =cut
308              
309             sub resolve_component {
310 0     0 1   my ($class, $controller, $c, $type, $name, $args) = @_;
311              
312             # just use the name if nothing is configured at all
313 0           my $config = $controller->config->{component_prefix};
314              
315             # a hashref means per-type configuration
316 0 0         if (ref($config) eq 'HASH') {
317             $config = exists($config->{ $type }) ? $config->{ $type }
318             : exists($config->{-default}) ? $config->{-default}
319 0 0         : return $name;
    0          
320             }
321              
322             # if the result of the above is not an arrayref, make it one
323             # for convenience reasons
324 0 0         unless (ref($config) eq 'ARRAY') {
325 0           $config = [$config];
326             }
327              
328             # try to find a component under that prefix and return it if found
329 0           for my $prefix (@$config) {
330 0           my $comp_name = join('::', grep { $_ } $prefix, $name);
  0            
331 0 0         my $comp = $c->$type($comp_name, @{ $args || [] });
  0            
332 0 0         return $comp if defined($comp);
333             }
334              
335             # nothing found
336 0           return;
337             }
338              
339             =head1 DIAGNOSTICS
340              
341             See also L<CatalystX::Imports::Context/DIAGNOSTICS> and
342             L<CatalystX::Imports::Vars/DIAGNOSTICS> for further messages.
343              
344             =head2 CatalystX::Imports expects a key/value list as argument
345              
346             The use line expects a set of key/value pairs as arguments, but you gave
347             it a list with an odd number of elements.
348              
349             =head1 SEE ALSO
350              
351             L<Catalyst>,
352             L<CatalystX::Imports::Context>,
353             L<CatalystX::Imports::Vars>
354              
355             =head1 AUTHOR AND COPYRIGHT
356              
357             Robert 'phaylon' Sedlacek C<E<lt>rs@474.atE<gt>>
358              
359             =head1 LICENSE
360              
361             This program is free software; you can redistribute it and/or modify
362             it under the same terms as perl itself.
363              
364             =cut
365              
366             1;