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