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