And fix it properly.
[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);
67837eb6 184 confess("Wrong metaclass $meta for $self - " . $meta->name)
185 unless $meta->can('get_all_methods_with_attributes');
a58af53d 186 my @methods = $meta->get_all_methods_with_attributes;
234763d4 187
ba545c13 188 foreach my $method (@methods) {
189 my $name = $method->name;
190 my $attributes = $method->attributes;
191 next unless $attributes;
192 my $attrs = $self->_parse_attrs( $c, $name, @{ $attributes } );
234763d4 193 if ( $attrs->{Private} && ( keys %$attrs > 1 ) ) {
194 $c->log->debug( 'Bad action definition "'
ba545c13 195 . join( ' ', @{ $attributes } )
196 . qq/" for "$class->$name"/ )
234763d4 197 if $c->debug;
198 next;
199 }
bc677969 200 my $reverse = $namespace ? "${namespace}/${name}" : $name;
234763d4 201 my $action = $self->create_action(
ba545c13 202 name => $name,
203 code => $method->body,
234763d4 204 reverse => $reverse,
205 namespace => $namespace,
206 class => $class,
207 attributes => $attrs,
208 );
209
210 $c->dispatcher->register( $c, $action );
211 }
212}
213
214sub create_action {
215 my $self = shift;
216 my %args = @_;
217
218 my $class = (exists $args{attributes}{ActionClass}
219 ? $args{attributes}{ActionClass}[0]
220 : $self->_action_class);
221
ae29b412 222 Class::MOP::load_class($class);
234763d4 223 return $class->new( \%args );
224}
225
226sub _parse_attrs {
227 my ( $self, $c, $name, @attrs ) = @_;
228
229 my %raw_attributes;
230
231 foreach my $attr (@attrs) {
232
233 # Parse out :Foo(bar) into Foo => bar etc (and arrayify)
234
235 if ( my ( $key, $value ) = ( $attr =~ /^(.*?)(?:\(\s*(.+?)\s*\))?$/ ) )
236 {
237
238 if ( defined $value ) {
239 ( $value =~ s/^'(.*)'$/$1/ ) || ( $value =~ s/^"(.*)"/$1/ );
240 }
241 push( @{ $raw_attributes{$key} }, $value );
242 }
243 }
244
ae29b412 245 #I know that the original behavior was to ignore action if actions was set
246 # but i actually think this may be a little more sane? we can always remove
247 # the merge behavior quite easily and go back to having actions have
248 # presedence over action by modifying the keys. i honestly think this is
249 # superior while mantaining really high degree of compat
250 my $actions;
251 if( ref($self) ) {
252 $actions = $self->actions;
253 } else {
254 my $cfg = $self->config;
255 $actions = $self->merge_config_hashes($cfg->{actions}, $cfg->{action});
234763d4 256 }
257
ae29b412 258 %raw_attributes = ((exists $actions->{'*'} ? %{$actions->{'*'}} : ()),
259 %raw_attributes,
260 (exists $actions->{$name} ? %{$actions->{$name}} : ()));
261
262
234763d4 263 my %final_attributes;
264
265 foreach my $key (keys %raw_attributes) {
266
267 my $raw = $raw_attributes{$key};
268
269 foreach my $value (ref($raw) eq 'ARRAY' ? @$raw : $raw) {
270
271 my $meth = "_parse_${key}_attr";
ae29b412 272 if ( my $code = $self->can($meth) ) {
273 ( $key, $value ) = $self->$code( $c, $name, $value );
234763d4 274 }
275 push( @{ $final_attributes{$key} }, $value );
276 }
277 }
278
279 return \%final_attributes;
280}
281
282sub _parse_Global_attr {
283 my ( $self, $c, $name, $value ) = @_;
284 return $self->_parse_Path_attr( $c, $name, "/$name" );
285}
286
287sub _parse_Absolute_attr { shift->_parse_Global_attr(@_); }
288
289sub _parse_Local_attr {
290 my ( $self, $c, $name, $value ) = @_;
291 return $self->_parse_Path_attr( $c, $name, $name );
292}
293
294sub _parse_Relative_attr { shift->_parse_Local_attr(@_); }
295
296sub _parse_Path_attr {
297 my ( $self, $c, $name, $value ) = @_;
53119b78 298 $value = '' if !defined $value;
234763d4 299 if ( $value =~ m!^/! ) {
300 return ( 'Path', $value );
301 }
302 elsif ( length $value ) {
303 return ( 'Path', join( '/', $self->path_prefix($c), $value ) );
304 }
305 else {
306 return ( 'Path', $self->path_prefix($c) );
307 }
308}
309
310sub _parse_Regex_attr {
311 my ( $self, $c, $name, $value ) = @_;
312 return ( 'Regex', $value );
313}
314
315sub _parse_Regexp_attr { shift->_parse_Regex_attr(@_); }
316
317sub _parse_LocalRegex_attr {
318 my ( $self, $c, $name, $value ) = @_;
319 unless ( $value =~ s/^\^// ) { $value = "(?:.*?)$value"; }
19c01ee1 320
321 my $prefix = $self->path_prefix( $c );
322 $prefix .= '/' if length( $prefix );
323
324 return ( 'Regex', "^${prefix}${value}" );
234763d4 325}
326
327sub _parse_LocalRegexp_attr { shift->_parse_LocalRegex_attr(@_); }
328
f3107403 329sub _parse_Chained_attr {
330 my ($self, $c, $name, $value) = @_;
331
332 if (defined($value) && length($value)) {
333 if ($value eq '.') {
334 $value = '/'.$self->action_namespace($c);
fb56008f 335 } elsif (my ($rel, $rest) = $value =~ /^((?:\.{2}\/)+)(.*)$/) {
eb270c30 336 my @parts = split '/', $self->action_namespace($c);
fb56008f 337 my @levels = split '/', $rel;
338
339 $value = '/'.join('/', @parts[0 .. $#parts - @levels], $rest);
f3107403 340 } elsif ($value !~ m/^\//) {
341 my $action_ns = $self->action_namespace($c);
342
343 if ($action_ns) {
344 $value = '/'.join('/', $action_ns, $value);
345 } else {
346 $value = '/'.$value; # special case namespace '' (root)
347 }
348 }
349 } else {
350 $value = '/'
351 }
352
353 return Chained => $value;
354}
355
9356b981 356sub _parse_ChainedParent_attr {
357 my ($self, $c, $name, $value) = @_;
358 return $self->_parse_Chained_attr($c, $name, '../'.$name);
359}
360
e5d2cfdb 361sub _parse_PathPrefix_attr {
362 my $self = shift;
363 return PathPart => $self->path_prefix;
364}
365
234763d4 366sub _parse_ActionClass_attr {
367 my ( $self, $c, $name, $value ) = @_;
368 unless ( $value =~ s/^\+// ) {
369 $value = join('::', $self->_action_class, $value );
370 }
371 return ( 'ActionClass', $value );
372}
373
9287719b 374sub _parse_MyAction_attr {
375 my ( $self, $c, $name, $value ) = @_;
376
377 my $appclass = Catalyst::Utils::class2appclass($self);
378 $value = "${appclass}::Action::${value}";
234763d4 379
9287719b 380 return ( 'ActionClass', $value );
381}
234763d4 382
ae29b412 383__PACKAGE__->meta->make_immutable;
384
234763d4 3851;
386
387__END__
388
389=head1 CONFIGURATION
390
a269e0c2 391Like any other L<Catalyst::Component>, controllers have a config hash,
392accessible through $self->config from the controller actions. Some
393settings are in use by the Catalyst framework:
234763d4 394
395=head2 namespace
396
a269e0c2 397This specifies the internal namespace the controller should be bound
398to. By default the controller is bound to the URI version of the
399controller name. For instance controller 'MyApp::Controller::Foo::Bar'
400will be bound to 'foo/bar'. The default Root controller is an example
401of setting namespace to '' (the null string).
234763d4 402
2cbd9d12 403=head2 path
234763d4 404
405Sets 'path_prefix', as described below.
406
407=head1 METHODS
408
409=head2 $class->new($app, @args)
410
411Proxies through to NEXT::new and stashes the application instance as
412$self->_application.
413
414=head2 $self->action_for('name')
415
a269e0c2 416Returns the Catalyst::Action object (if any) for a given method name
417in this component.
234763d4 418
419=head2 $self->register_actions($c)
420
a269e0c2 421Finds all applicable actions for this component, creates
422Catalyst::Action objects (using $self->create_action) for them and
423registers them with $c->dispatcher.
234763d4 424
425=head2 $self->action_namespace($c)
426
a269e0c2 427Returns the private namespace for actions in this component. Defaults
428to a value from the controller name (for
429e.g. MyApp::Controller::Foo::Bar becomes "foo/bar") or can be
430overridden from the "namespace" config key.
234763d4 431
432
433=head2 $self->path_prefix($c)
434
e5d2cfdb 435Returns the default path prefix for :PathPrefix, :Local, :LocalRegex and
436relative :Path actions in this component. Defaults to the action_namespace or
a269e0c2 437can be overridden from the "path" config key.
234763d4 438
439=head2 $self->create_action(%args)
440
a269e0c2 441Called with a hash of data to be use for construction of a new
442Catalyst::Action (or appropriate sub/alternative class) object.
234763d4 443
444Primarily designed for the use of register_actions.
445
a269e0c2 446=head2 $self->_application
234763d4 447
448=head2 $self->_app
449
450Returns the application instance stored by C<new()>
5ee249f2 451
0bf7ab71 452=head1 AUTHORS
5ee249f2 453
0bf7ab71 454Catalyst Contributors, see Catalyst.pm
5ee249f2 455
456=head1 COPYRIGHT
457
a269e0c2 458This program is free software, you can redistribute it and/or modify
459it under the same terms as Perl itself.
5ee249f2 460
461=cut