Stop using Class::Inspector. Class::MOP::load_class does all we need.
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Controller.pm
CommitLineData
5ee249f2 1package Catalyst::Controller;
2
0bc4c3ab 3#switch to BEGIN { extends qw/ ... /; } ?
a7caa492 4use base qw/Catalyst::Component Catalyst::AttrContainer/;
6e58f383 5use Moose;
e8b9f2a9 6
5fb67d52 7use Scalar::Util qw/blessed/;
234763d4 8use Catalyst::Exception;
9use Catalyst::Utils;
5ee249f2 10
0bc4c3ab 11has path_prefix =>
12 (
84ff88cf 13 is => 'rw',
0bc4c3ab 14 isa => 'Str',
15 init_arg => 'path',
16 predicate => 'has_path_prefix',
17 );
18
19has action_namespace =>
20 (
84ff88cf 21 is => 'rw',
0bc4c3ab 22 isa => 'Str',
23 init_arg => 'namespace',
24 predicate => 'has_action_namespace',
25 );
26
27has actions =>
28 (
29 is => 'rw',
30 isa => 'HashRef',
31 init_arg => undef,
32 );
33
34# isa => 'ClassName|Catalyst' ?
35has _application => (is => 'rw');
0fc2d522 36sub _app{ shift->_application(@_) }
0bc4c3ab 37
38sub BUILD {
39 my ($self, $args) = @_;
40 my $action = delete $args->{action} || {};
41 my $actions = delete $args->{actions} || {};
42 my $attr_value = $self->merge_config_hashes($actions, $action);
43 $self->actions($attr_value);
44}
45
5ee249f2 46=head1 NAME
47
48Catalyst::Controller - Catalyst Controller base class
49
50=head1 SYNOPSIS
51
234763d4 52 package MyApp::Controller::Search
a269e0c2 53 use base qw/Catalyst::Controller/;
234763d4 54
ac5c933b 55 sub foo : Local {
85d9fce6 56 my ($self,$c,@args) = @_;
ac5c933b 57 ...
234763d4 58 } # Dispatches to /search/foo
5ee249f2 59
60=head1 DESCRIPTION
61
a269e0c2 62Controllers are where the actions in the Catalyst framework
63reside. Each action is represented by a function with an attribute to
64identify what kind of action it is. See the L<Catalyst::Dispatcher>
65for more info about how Catalyst dispatches to actions.
234763d4 66
67=cut
68
2361ed67 69#I think both of these could be attributes. doesn't really seem like they need
70#to ble class data. i think that attributes +default would work just fine
e8b9f2a9 71__PACKAGE__->mk_classdata($_) for qw/_dispatch_steps _action_class/;
234763d4 72
e8b9f2a9 73__PACKAGE__->_dispatch_steps( [qw/_BEGIN _AUTO _ACTION/] );
74__PACKAGE__->_action_class('Catalyst::Action');
75
234763d4 76
77sub _DISPATCH : Private {
78 my ( $self, $c ) = @_;
79
80 foreach my $disp ( @{ $self->_dispatch_steps } ) {
81 last unless $c->forward($disp);
82 }
83
84 $c->forward('_END');
85}
86
87sub _BEGIN : Private {
88 my ( $self, $c ) = @_;
89 my $begin = ( $c->get_actions( 'begin', $c->namespace ) )[-1];
90 return 1 unless $begin;
91 $begin->dispatch( $c );
92 return !@{ $c->error };
93}
94
95sub _AUTO : Private {
96 my ( $self, $c ) = @_;
97 my @auto = $c->get_actions( 'auto', $c->namespace );
98 foreach my $auto (@auto) {
99 $auto->dispatch( $c );
100 return 0 unless $c->state;
101 }
102 return 1;
103}
104
105sub _ACTION : Private {
106 my ( $self, $c ) = @_;
107 if ( ref $c->action
108 && $c->action->can('execute')
2f381252 109 && defined $c->req->action )
234763d4 110 {
111 $c->action->dispatch( $c );
112 }
113 return !@{ $c->error };
114}
115
116sub _END : Private {
117 my ( $self, $c ) = @_;
118 my $end = ( $c->get_actions( 'end', $c->namespace ) )[-1];
119 return 1 unless $end;
120 $end->dispatch( $c );
121 return !@{ $c->error };
122}
123
4090e3bb 124around new => sub {
125 my $orig = shift;
e8b9f2a9 126 my $self = shift;
127 my $app = $_[0];
4090e3bb 128 my $new = $self->$orig(@_);
e8b9f2a9 129 $new->_application( $app );
130 return $new;
4090e3bb 131};
e8b9f2a9 132
234763d4 133sub action_for {
134 my ( $self, $name ) = @_;
135 my $app = ($self->isa('Catalyst') ? $self : $self->_application);
136 return $app->dispatcher->get_action($name, $self->action_namespace);
137}
138
0fc2d522 139#my opinion is that this whole sub really should be a builder method, not
140#something that happens on every call. Anyone else disagree?? -- groditi
96d8d513 141## -- apparently this is all just waiting for app/ctx split
0bc4c3ab 142around action_namespace => sub {
0fc2d522 143 my $orig = shift;
144 my ( $self, $c ) = @_;
0bc4c3ab 145
5fb67d52 146 if( ref($self) ){
0bc4c3ab 147 return $self->$orig if $self->has_action_namespace;
4090e3bb 148 } else {
0bc4c3ab 149 return $self->config->{namespace} if exists $self->config->{namespace};
150 }
234763d4 151
0bc4c3ab 152 my $case_s;
153 if( $c ){
154 $case_s = $c->config->{case_sensitive};
155 } else {
156 if ($self->isa('Catalyst')) {
157 $case_s = $self->config->{case_sensitive};
158 } else {
159 if (ref $self) {
160 $case_s = $self->_application->config->{case_sensitive};
161 } else {
162 confess("Can't figure out case_sensitive setting");
163 }
164 }
234763d4 165 }
0bc4c3ab 166
84ff88cf 167 my $namespace = Catalyst::Utils::class2prefix(ref($self) || $self, $case_s) || '';
168 $self->$orig($namespace) if ref($self);
169 return $namespace;
0bc4c3ab 170};
171
0fc2d522 172#Once again, this is probably better written as a builder method
0bc4c3ab 173around path_prefix => sub {
174 my $orig = shift;
175 my $self = shift;
5fb67d52 176 if( ref($self) ){
0bc4c3ab 177 return $self->$orig if $self->has_path_prefix;
178 } else {
5fb67d52 179 return $self->config->{path} if exists $self->config->{path};
0bc4c3ab 180 }
84ff88cf 181 my $namespace = $self->action_namespace(@_);
182 $self->$orig($namespace) if ref($self);
183 return $namespace;
0bc4c3ab 184};
234763d4 185
186
187sub register_actions {
188 my ( $self, $c ) = @_;
189 my $class = ref $self || $self;
5fb67d52 190 #this is still not correct for some reason.
234763d4 191 my $namespace = $self->action_namespace($c);
84ff88cf 192 my $meta = $self->meta;
86b3c7aa 193 my %methods = map { $_->body => $_->name }
194 grep { $_->package_name ne 'Moose::Object' } #ignore Moose::Object methods
195 $meta->get_all_methods;
234763d4 196
197 # Advanced inheritance support for plugins and the like
0bc4c3ab 198 #moose todo: migrate to eliminate CDI compat
234763d4 199 my @action_cache;
84ff88cf 200 for my $isa ( $meta->superclasses, $class ) {
201 if(my $coderef = $isa->can('_action_cache')){
202 push(@action_cache, @{ $isa->$coderef });
234763d4 203 }
204 }
205
206 foreach my $cache (@action_cache) {
207 my $code = $cache->[0];
208 my $method = delete $methods{$code}; # avoid dupe registers
209 next unless $method;
210 my $attrs = $self->_parse_attrs( $c, $method, @{ $cache->[1] } );
211 if ( $attrs->{Private} && ( keys %$attrs > 1 ) ) {
212 $c->log->debug( 'Bad action definition "'
213 . join( ' ', @{ $cache->[1] } )
214 . qq/" for "$class->$method"/ )
215 if $c->debug;
216 next;
217 }
5fb67d52 218 my $reverse = $namespace ? "${namespace}/${method}" : $method;
234763d4 219 my $action = $self->create_action(
220 name => $method,
221 code => $code,
222 reverse => $reverse,
223 namespace => $namespace,
224 class => $class,
225 attributes => $attrs,
226 );
227
228 $c->dispatcher->register( $c, $action );
229 }
230}
231
232sub create_action {
233 my $self = shift;
234 my %args = @_;
235
236 my $class = (exists $args{attributes}{ActionClass}
237 ? $args{attributes}{ActionClass}[0]
238 : $self->_action_class);
239
2361ed67 240 Class::MOP::load_class($class);
234763d4 241 return $class->new( \%args );
242}
243
244sub _parse_attrs {
245 my ( $self, $c, $name, @attrs ) = @_;
246
247 my %raw_attributes;
248
249 foreach my $attr (@attrs) {
250
251 # Parse out :Foo(bar) into Foo => bar etc (and arrayify)
252
253 if ( my ( $key, $value ) = ( $attr =~ /^(.*?)(?:\(\s*(.+?)\s*\))?$/ ) )
254 {
255
256 if ( defined $value ) {
257 ( $value =~ s/^'(.*)'$/$1/ ) || ( $value =~ s/^"(.*)"/$1/ );
258 }
259 push( @{ $raw_attributes{$key} }, $value );
260 }
261 }
262
0bc4c3ab 263 #I know that the original behavior was to ignore action if actions was set
264 # but i actually think this may be a little more sane? we can always remove
265 # the merge behavior quite easily and go back to having actions have
266 # presedence over action by modifying the keys. i honestly think this is
267 # superior while mantaining really high degree of compat
268 my $actions;
269 if( ref($self) ) {
270 $actions = $self->actions;
271 } else {
272 my $cfg = $self->config;
273 $actions = $self->merge_config_hashes($cfg->{actions}, $cfg->{action});
234763d4 274 }
275
0bc4c3ab 276 %raw_attributes = ((exists $actions->{'*'} ? %{$actions->{'*'}} : ()),
277 %raw_attributes,
278 (exists $actions->{$name} ? %{$actions->{$name}} : ()));
279
280
234763d4 281 my %final_attributes;
282
283 foreach my $key (keys %raw_attributes) {
284
285 my $raw = $raw_attributes{$key};
286
287 foreach my $value (ref($raw) eq 'ARRAY' ? @$raw : $raw) {
288
289 my $meth = "_parse_${key}_attr";
0bc4c3ab 290 if ( my $code = $self->can($meth) ) {
291 ( $key, $value ) = $self->$code( $c, $name, $value );
234763d4 292 }
293 push( @{ $final_attributes{$key} }, $value );
294 }
295 }
296
297 return \%final_attributes;
298}
299
300sub _parse_Global_attr {
301 my ( $self, $c, $name, $value ) = @_;
302 return $self->_parse_Path_attr( $c, $name, "/$name" );
303}
304
305sub _parse_Absolute_attr { shift->_parse_Global_attr(@_); }
306
307sub _parse_Local_attr {
308 my ( $self, $c, $name, $value ) = @_;
309 return $self->_parse_Path_attr( $c, $name, $name );
310}
311
312sub _parse_Relative_attr { shift->_parse_Local_attr(@_); }
313
314sub _parse_Path_attr {
315 my ( $self, $c, $name, $value ) = @_;
2f381252 316 $value = '' if !defined $value;
234763d4 317 if ( $value =~ m!^/! ) {
318 return ( 'Path', $value );
319 }
320 elsif ( length $value ) {
321 return ( 'Path', join( '/', $self->path_prefix($c), $value ) );
322 }
323 else {
324 return ( 'Path', $self->path_prefix($c) );
325 }
326}
327
328sub _parse_Regex_attr {
329 my ( $self, $c, $name, $value ) = @_;
330 return ( 'Regex', $value );
331}
332
333sub _parse_Regexp_attr { shift->_parse_Regex_attr(@_); }
334
335sub _parse_LocalRegex_attr {
336 my ( $self, $c, $name, $value ) = @_;
337 unless ( $value =~ s/^\^// ) { $value = "(?:.*?)$value"; }
2f381252 338
339 my $prefix = $self->path_prefix( $c );
340 $prefix .= '/' if length( $prefix );
341
342 return ( 'Regex', "^${prefix}${value}" );
234763d4 343}
344
345sub _parse_LocalRegexp_attr { shift->_parse_LocalRegex_attr(@_); }
346
2f381252 347sub _parse_Chained_attr {
348 my ($self, $c, $name, $value) = @_;
349
350 if (defined($value) && length($value)) {
351 if ($value eq '.') {
352 $value = '/'.$self->action_namespace($c);
353 } elsif (my ($rel, $rest) = $value =~ /^((?:\.{2}\/)+)(.*)$/) {
354 my @parts = split '/', $self->action_namespace($c);
355 my @levels = split '/', $rel;
356
357 $value = '/'.join('/', @parts[0 .. $#parts - @levels], $rest);
358 } elsif ($value !~ m/^\//) {
359 my $action_ns = $self->action_namespace($c);
360
361 if ($action_ns) {
362 $value = '/'.join('/', $action_ns, $value);
363 } else {
364 $value = '/'.$value; # special case namespace '' (root)
365 }
366 }
367 } else {
368 $value = '/'
369 }
370
371 return Chained => $value;
372}
373
374sub _parse_ChainedParent_attr {
375 my ($self, $c, $name, $value) = @_;
376 return $self->_parse_Chained_attr($c, $name, '../'.$name);
377}
378
379sub _parse_PathPrefix_attr {
380 my $self = shift;
381 return PathPart => $self->path_prefix;
382}
383
234763d4 384sub _parse_ActionClass_attr {
385 my ( $self, $c, $name, $value ) = @_;
386 unless ( $value =~ s/^\+// ) {
387 $value = join('::', $self->_action_class, $value );
388 }
389 return ( 'ActionClass', $value );
390}
391
9287719b 392sub _parse_MyAction_attr {
393 my ( $self, $c, $name, $value ) = @_;
394
395 my $appclass = Catalyst::Utils::class2appclass($self);
396 $value = "${appclass}::Action::${value}";
234763d4 397
9287719b 398 return ( 'ActionClass', $value );
399}
234763d4 400
0fc2d522 401no Moose;
402
46d0346d 403__PACKAGE__->meta->make_immutable;
404
234763d4 4051;
406
407__END__
408
409=head1 CONFIGURATION
410
a269e0c2 411Like any other L<Catalyst::Component>, controllers have a config hash,
412accessible through $self->config from the controller actions. Some
413settings are in use by the Catalyst framework:
234763d4 414
415=head2 namespace
416
a269e0c2 417This specifies the internal namespace the controller should be bound
418to. By default the controller is bound to the URI version of the
419controller name. For instance controller 'MyApp::Controller::Foo::Bar'
420will be bound to 'foo/bar'. The default Root controller is an example
421of setting namespace to '' (the null string).
234763d4 422
ac5c933b 423=head2 path
234763d4 424
425Sets 'path_prefix', as described below.
426
427=head1 METHODS
428
429=head2 $class->new($app, @args)
430
431Proxies through to NEXT::new and stashes the application instance as
432$self->_application.
433
434=head2 $self->action_for('name')
435
a269e0c2 436Returns the Catalyst::Action object (if any) for a given method name
437in this component.
234763d4 438
439=head2 $self->register_actions($c)
440
a269e0c2 441Finds all applicable actions for this component, creates
442Catalyst::Action objects (using $self->create_action) for them and
443registers them with $c->dispatcher.
234763d4 444
445=head2 $self->action_namespace($c)
446
a269e0c2 447Returns the private namespace for actions in this component. Defaults
448to a value from the controller name (for
449e.g. MyApp::Controller::Foo::Bar becomes "foo/bar") or can be
450overridden from the "namespace" config key.
234763d4 451
452
453=head2 $self->path_prefix($c)
454
2f381252 455Returns the default path prefix for :PathPrefix, :Local, :LocalRegex and
456relative :Path actions in this component. Defaults to the action_namespace or
a269e0c2 457can be overridden from the "path" config key.
234763d4 458
459=head2 $self->create_action(%args)
460
a269e0c2 461Called with a hash of data to be use for construction of a new
462Catalyst::Action (or appropriate sub/alternative class) object.
234763d4 463
464Primarily designed for the use of register_actions.
465
a269e0c2 466=head2 $self->_application
234763d4 467
468=head2 $self->_app
469
470Returns the application instance stored by C<new()>
5ee249f2 471
2f381252 472=head1 AUTHORS
5ee249f2 473
2f381252 474Catalyst Contributors, see Catalyst.pm
5ee249f2 475
476=head1 COPYRIGHT
477
a269e0c2 478This program is free software, you can redistribute it and/or modify
479it under the same terms as Perl itself.
5ee249f2 480
481=cut