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