Use get_all_methods_with_attributes to get all possible actions.
[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
10use Catalyst::Exception;
11use Catalyst::Utils;
ae29b412 12
13with 'Catalyst::Component::ApplicationAttribute';
14
15has path_prefix =>
16 (
17 is => 'rw',
18 isa => 'Str',
19 init_arg => 'path',
20 predicate => 'has_path_prefix',
21 );
22
23has action_namespace =>
24 (
25 is => 'rw',
26 isa => 'Str',
27 init_arg => 'namespace',
28 predicate => 'has_action_namespace',
29 );
30
31has actions =>
32 (
33 is => 'rw',
34 isa => 'HashRef',
35 init_arg => undef,
36 );
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}
5ee249f2 45
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
55 sub foo : Local {
85d9fce6 56 my ($self,$c,@args) = @_;
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
ae29b412 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
234763d4 71__PACKAGE__->mk_classdata($_) for qw/_dispatch_steps _action_class/;
72
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')
53119b78 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
234763d4 124sub action_for {
125 my ( $self, $name ) = @_;
126 my $app = ($self->isa('Catalyst') ? $self : $self->_application);
127 return $app->dispatcher->get_action($name, $self->action_namespace);
128}
129
ae29b412 130#my opinion is that this whole sub really should be a builder method, not
131#something that happens on every call. Anyone else disagree?? -- groditi
132## -- apparently this is all just waiting for app/ctx split
133around action_namespace => sub {
134 my $orig = shift;
234763d4 135 my ( $self, $c ) = @_;
ae29b412 136
137 if( ref($self) ){
138 return $self->$orig if $self->has_action_namespace;
139 } else {
140 return $self->config->{namespace} if exists $self->config->{namespace};
234763d4 141 }
234763d4 142
ae29b412 143 my $case_s;
144 if( $c ){
145 $case_s = $c->config->{case_sensitive};
146 } else {
147 if ($self->isa('Catalyst')) {
148 $case_s = $self->config->{case_sensitive};
149 } else {
150 if (ref $self) {
151 $case_s = $self->_application->config->{case_sensitive};
152 } else {
153 confess("Can't figure out case_sensitive setting");
154 }
155 }
234763d4 156 }
ae29b412 157
158 my $namespace = Catalyst::Utils::class2prefix(ref($self) || $self, $case_s) || '';
159 $self->$orig($namespace) if ref($self);
160 return $namespace;
161};
162
163#Once again, this is probably better written as a builder method
164around path_prefix => sub {
165 my $orig = shift;
166 my $self = shift;
167 if( ref($self) ){
168 return $self->$orig if $self->has_path_prefix;
169 } else {
170 return $self->config->{path} if exists $self->config->{path};
171 }
172 my $namespace = $self->action_namespace(@_);
173 $self->$orig($namespace) if ref($self);
174 return $namespace;
175};
234763d4 176
177
178sub register_actions {
179 my ( $self, $c ) = @_;
180 my $class = ref $self || $self;
ae29b412 181 #this is still not correct for some reason.
234763d4 182 my $namespace = $self->action_namespace($c);
ae29b412 183 my $meta = find_meta($self);
a58af53d 184 my @methods = $meta->get_all_methods_with_attributes;
234763d4 185
ba545c13 186 foreach my $method (@methods) {
187 my $name = $method->name;
188 my $attributes = $method->attributes;
189 next unless $attributes;
190 my $attrs = $self->_parse_attrs( $c, $name, @{ $attributes } );
234763d4 191 if ( $attrs->{Private} && ( keys %$attrs > 1 ) ) {
192 $c->log->debug( 'Bad action definition "'
ba545c13 193 . join( ' ', @{ $attributes } )
194 . qq/" for "$class->$name"/ )
234763d4 195 if $c->debug;
196 next;
197 }
bc677969 198 my $reverse = $namespace ? "${namespace}/${name}" : $name;
234763d4 199 my $action = $self->create_action(
ba545c13 200 name => $name,
201 code => $method->body,
234763d4 202 reverse => $reverse,
203 namespace => $namespace,
204 class => $class,
205 attributes => $attrs,
206 );
207
208 $c->dispatcher->register( $c, $action );
209 }
210}
211
212sub create_action {
213 my $self = shift;
214 my %args = @_;
215
216 my $class = (exists $args{attributes}{ActionClass}
217 ? $args{attributes}{ActionClass}[0]
218 : $self->_action_class);
219
ae29b412 220 Class::MOP::load_class($class);
234763d4 221 return $class->new( \%args );
222}
223
224sub _parse_attrs {
225 my ( $self, $c, $name, @attrs ) = @_;
226
227 my %raw_attributes;
228
229 foreach my $attr (@attrs) {
230
231 # Parse out :Foo(bar) into Foo => bar etc (and arrayify)
232
233 if ( my ( $key, $value ) = ( $attr =~ /^(.*?)(?:\(\s*(.+?)\s*\))?$/ ) )
234 {
235
236 if ( defined $value ) {
237 ( $value =~ s/^'(.*)'$/$1/ ) || ( $value =~ s/^"(.*)"/$1/ );
238 }
239 push( @{ $raw_attributes{$key} }, $value );
240 }
241 }
242
ae29b412 243 #I know that the original behavior was to ignore action if actions was set
244 # but i actually think this may be a little more sane? we can always remove
245 # the merge behavior quite easily and go back to having actions have
246 # presedence over action by modifying the keys. i honestly think this is
247 # superior while mantaining really high degree of compat
248 my $actions;
249 if( ref($self) ) {
250 $actions = $self->actions;
251 } else {
252 my $cfg = $self->config;
253 $actions = $self->merge_config_hashes($cfg->{actions}, $cfg->{action});
234763d4 254 }
255
ae29b412 256 %raw_attributes = ((exists $actions->{'*'} ? %{$actions->{'*'}} : ()),
257 %raw_attributes,
258 (exists $actions->{$name} ? %{$actions->{$name}} : ()));
259
260
234763d4 261 my %final_attributes;
262
263 foreach my $key (keys %raw_attributes) {
264
265 my $raw = $raw_attributes{$key};
266
267 foreach my $value (ref($raw) eq 'ARRAY' ? @$raw : $raw) {
268
269 my $meth = "_parse_${key}_attr";
ae29b412 270 if ( my $code = $self->can($meth) ) {
271 ( $key, $value ) = $self->$code( $c, $name, $value );
234763d4 272 }
273 push( @{ $final_attributes{$key} }, $value );
274 }
275 }
276
277 return \%final_attributes;
278}
279
280sub _parse_Global_attr {
281 my ( $self, $c, $name, $value ) = @_;
282 return $self->_parse_Path_attr( $c, $name, "/$name" );
283}
284
285sub _parse_Absolute_attr { shift->_parse_Global_attr(@_); }
286
287sub _parse_Local_attr {
288 my ( $self, $c, $name, $value ) = @_;
289 return $self->_parse_Path_attr( $c, $name, $name );
290}
291
292sub _parse_Relative_attr { shift->_parse_Local_attr(@_); }
293
294sub _parse_Path_attr {
295 my ( $self, $c, $name, $value ) = @_;
53119b78 296 $value = '' if !defined $value;
234763d4 297 if ( $value =~ m!^/! ) {
298 return ( 'Path', $value );
299 }
300 elsif ( length $value ) {
301 return ( 'Path', join( '/', $self->path_prefix($c), $value ) );
302 }
303 else {
304 return ( 'Path', $self->path_prefix($c) );
305 }
306}
307
308sub _parse_Regex_attr {
309 my ( $self, $c, $name, $value ) = @_;
310 return ( 'Regex', $value );
311}
312
313sub _parse_Regexp_attr { shift->_parse_Regex_attr(@_); }
314
315sub _parse_LocalRegex_attr {
316 my ( $self, $c, $name, $value ) = @_;
317 unless ( $value =~ s/^\^// ) { $value = "(?:.*?)$value"; }
19c01ee1 318
319 my $prefix = $self->path_prefix( $c );
320 $prefix .= '/' if length( $prefix );
321
322 return ( 'Regex', "^${prefix}${value}" );
234763d4 323}
324
325sub _parse_LocalRegexp_attr { shift->_parse_LocalRegex_attr(@_); }
326
f3107403 327sub _parse_Chained_attr {
328 my ($self, $c, $name, $value) = @_;
329
330 if (defined($value) && length($value)) {
331 if ($value eq '.') {
332 $value = '/'.$self->action_namespace($c);
fb56008f 333 } elsif (my ($rel, $rest) = $value =~ /^((?:\.{2}\/)+)(.*)$/) {
eb270c30 334 my @parts = split '/', $self->action_namespace($c);
fb56008f 335 my @levels = split '/', $rel;
336
337 $value = '/'.join('/', @parts[0 .. $#parts - @levels], $rest);
f3107403 338 } elsif ($value !~ m/^\//) {
339 my $action_ns = $self->action_namespace($c);
340
341 if ($action_ns) {
342 $value = '/'.join('/', $action_ns, $value);
343 } else {
344 $value = '/'.$value; # special case namespace '' (root)
345 }
346 }
347 } else {
348 $value = '/'
349 }
350
351 return Chained => $value;
352}
353
9356b981 354sub _parse_ChainedParent_attr {
355 my ($self, $c, $name, $value) = @_;
356 return $self->_parse_Chained_attr($c, $name, '../'.$name);
357}
358
e5d2cfdb 359sub _parse_PathPrefix_attr {
360 my $self = shift;
361 return PathPart => $self->path_prefix;
362}
363
234763d4 364sub _parse_ActionClass_attr {
365 my ( $self, $c, $name, $value ) = @_;
366 unless ( $value =~ s/^\+// ) {
367 $value = join('::', $self->_action_class, $value );
368 }
369 return ( 'ActionClass', $value );
370}
371
9287719b 372sub _parse_MyAction_attr {
373 my ( $self, $c, $name, $value ) = @_;
374
375 my $appclass = Catalyst::Utils::class2appclass($self);
376 $value = "${appclass}::Action::${value}";
234763d4 377
9287719b 378 return ( 'ActionClass', $value );
379}
234763d4 380
ae29b412 381__PACKAGE__->meta->make_immutable;
382
234763d4 3831;
384
385__END__
386
387=head1 CONFIGURATION
388
a269e0c2 389Like any other L<Catalyst::Component>, controllers have a config hash,
390accessible through $self->config from the controller actions. Some
391settings are in use by the Catalyst framework:
234763d4 392
393=head2 namespace
394
a269e0c2 395This specifies the internal namespace the controller should be bound
396to. By default the controller is bound to the URI version of the
397controller name. For instance controller 'MyApp::Controller::Foo::Bar'
398will be bound to 'foo/bar'. The default Root controller is an example
399of setting namespace to '' (the null string).
234763d4 400
2cbd9d12 401=head2 path
234763d4 402
403Sets 'path_prefix', as described below.
404
405=head1 METHODS
406
407=head2 $class->new($app, @args)
408
409Proxies through to NEXT::new and stashes the application instance as
410$self->_application.
411
412=head2 $self->action_for('name')
413
a269e0c2 414Returns the Catalyst::Action object (if any) for a given method name
415in this component.
234763d4 416
417=head2 $self->register_actions($c)
418
a269e0c2 419Finds all applicable actions for this component, creates
420Catalyst::Action objects (using $self->create_action) for them and
421registers them with $c->dispatcher.
234763d4 422
423=head2 $self->action_namespace($c)
424
a269e0c2 425Returns the private namespace for actions in this component. Defaults
426to a value from the controller name (for
427e.g. MyApp::Controller::Foo::Bar becomes "foo/bar") or can be
428overridden from the "namespace" config key.
234763d4 429
430
431=head2 $self->path_prefix($c)
432
e5d2cfdb 433Returns the default path prefix for :PathPrefix, :Local, :LocalRegex and
434relative :Path actions in this component. Defaults to the action_namespace or
a269e0c2 435can be overridden from the "path" config key.
234763d4 436
437=head2 $self->create_action(%args)
438
a269e0c2 439Called with a hash of data to be use for construction of a new
440Catalyst::Action (or appropriate sub/alternative class) object.
234763d4 441
442Primarily designed for the use of register_actions.
443
a269e0c2 444=head2 $self->_application
234763d4 445
446=head2 $self->_app
447
448Returns the application instance stored by C<new()>
5ee249f2 449
0bf7ab71 450=head1 AUTHORS
5ee249f2 451
0bf7ab71 452Catalyst Contributors, see Catalyst.pm
5ee249f2 453
454=head1 COPYRIGHT
455
a269e0c2 456This program is free software, you can redistribute it and/or modify
457it under the same terms as Perl itself.
5ee249f2 458
459=cut