a couple more details
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Controller.pm
CommitLineData
5ee249f2 1package Catalyst::Controller;
2
5fb67d52 3use Moose;
2361ed67 4use Class::MOP;
a7caa492 5use base qw/Catalyst::Component Catalyst::AttrContainer/;
e8b9f2a9 6
5fb67d52 7#Why does the following blow up?
8#extends qw/Catalyst::Component Catalyst::AttrContainer/;
9
10has path => (
11 is => 'ro',
12 isa => 'Str',
13 predicate => 'has_path',
14 );
15
16#this are frefixed like this because _namespace clases with something in
17#Catalyst.pm . to be fixed later.
18has _namespace => (
19 is => 'ro',
20 isa => 'Str',
21 init_arg => 'namespace',
22 predicate => '_has_namespace',
23 );
24
2361ed67 25__PACKAGE__->mk_accessors( qw/_application/ );
26
27has _application => (is => 'rw');
28sub _app{ shift->_application(@_) } # eww
29
5fb67d52 30use Scalar::Util qw/blessed/;
234763d4 31use Catalyst::Exception;
32use Catalyst::Utils;
33use Class::Inspector;
5ee249f2 34
35=head1 NAME
36
37Catalyst::Controller - Catalyst Controller base class
38
39=head1 SYNOPSIS
40
234763d4 41 package MyApp::Controller::Search
a269e0c2 42 use base qw/Catalyst::Controller/;
234763d4 43
a7caa492 44 sub foo : Local {
85d9fce6 45 my ($self,$c,@args) = @_;
a7caa492 46 ...
234763d4 47 } # Dispatches to /search/foo
5ee249f2 48
49=head1 DESCRIPTION
50
a269e0c2 51Controllers are where the actions in the Catalyst framework
52reside. Each action is represented by a function with an attribute to
53identify what kind of action it is. See the L<Catalyst::Dispatcher>
54for more info about how Catalyst dispatches to actions.
234763d4 55
56=cut
57
2361ed67 58#I think both of these could be attributes. doesn't really seem like they need
59#to ble class data. i think that attributes +default would work just fine
e8b9f2a9 60__PACKAGE__->mk_classdata($_) for qw/_dispatch_steps _action_class/;
234763d4 61
e8b9f2a9 62__PACKAGE__->_dispatch_steps( [qw/_BEGIN _AUTO _ACTION/] );
63__PACKAGE__->_action_class('Catalyst::Action');
64
234763d4 65
66sub _DISPATCH : Private {
67 my ( $self, $c ) = @_;
68
69 foreach my $disp ( @{ $self->_dispatch_steps } ) {
70 last unless $c->forward($disp);
71 }
72
73 $c->forward('_END');
74}
75
76sub _BEGIN : Private {
77 my ( $self, $c ) = @_;
78 my $begin = ( $c->get_actions( 'begin', $c->namespace ) )[-1];
79 return 1 unless $begin;
80 $begin->dispatch( $c );
81 return !@{ $c->error };
82}
83
84sub _AUTO : Private {
85 my ( $self, $c ) = @_;
86 my @auto = $c->get_actions( 'auto', $c->namespace );
87 foreach my $auto (@auto) {
88 $auto->dispatch( $c );
89 return 0 unless $c->state;
90 }
91 return 1;
92}
93
94sub _ACTION : Private {
95 my ( $self, $c ) = @_;
96 if ( ref $c->action
97 && $c->action->can('execute')
98 && $c->req->action )
99 {
100 $c->action->dispatch( $c );
101 }
102 return !@{ $c->error };
103}
104
105sub _END : Private {
106 my ( $self, $c ) = @_;
107 my $end = ( $c->get_actions( 'end', $c->namespace ) )[-1];
108 return 1 unless $end;
109 $end->dispatch( $c );
110 return !@{ $c->error };
111}
112
5fb67d52 113around new => sub {
114 my $orig = shift;
e8b9f2a9 115 my $self = shift;
116 my $app = $_[0];
5fb67d52 117 my $new = $self->$orig(@_);
e8b9f2a9 118 $new->_application( $app );
119 return $new;
5fb67d52 120};
e8b9f2a9 121
122
234763d4 123sub action_for {
124 my ( $self, $name ) = @_;
125 my $app = ($self->isa('Catalyst') ? $self : $self->_application);
126 return $app->dispatcher->get_action($name, $self->action_namespace);
127}
128
129sub action_namespace {
130 my ( $self, $c ) = @_;
131 unless ( $c ) {
132 $c = ($self->isa('Catalyst') ? $self : $self->_application);
133 }
5fb67d52 134 if( ref($self) ){
135 return $self->_namespace if $self->_has_namespace;
2361ed67 136 } # else { #i think this is hacky and it should work with the else enabled
5fb67d52 137 return $self->config->{namespace} if exists $self->config->{namespace};
138 #}
234763d4 139 return Catalyst::Utils::class2prefix( ref($self) || $self,
140 $c->config->{case_sensitive} )
141 || '';
142}
143
144sub path_prefix {
145 my ( $self, $c ) = @_;
146 unless ( $c ) {
147 $c = ($self->isa('Catalyst') ? $self : $self->_application);
148 }
5fb67d52 149 if( ref($self) ){
150 return $self->path if $self->has_path;
151 } # else {
152 return $self->config->{path} if exists $self->config->{path};
153 #}
234763d4 154 return shift->action_namespace(@_);
155}
156
157
158sub register_actions {
159 my ( $self, $c ) = @_;
160 my $class = ref $self || $self;
5fb67d52 161 #this is still not correct for some reason.
234763d4 162 my $namespace = $self->action_namespace($c);
163 my %methods;
5fb67d52 164 if( $self->can('meta') ){
165 my $meta = $self->meta;
166 %methods = map{ $_->{code}->body => $_->{name} }
2361ed67 167 grep {$_->{class} ne 'Moose::Object'} #ignore Moose::Object methods
5fb67d52 168 $meta->compute_all_applicable_methods;
169 } else { #until we are sure there's no moose stuff left...
170 $methods{ $self->can($_) } = $_
171 for @{ Class::Inspector->methods($class) || [] };
172 }
234763d4 173
174 # Advanced inheritance support for plugins and the like
5fb67d52 175 #to be modified to use meta->superclasses
234763d4 176 my @action_cache;
177 {
178 no strict 'refs';
179 for my $isa ( @{"$class\::ISA"}, $class ) {
180 push @action_cache, @{ $isa->_action_cache }
181 if $isa->can('_action_cache');
182 }
183 }
184
185 foreach my $cache (@action_cache) {
186 my $code = $cache->[0];
187 my $method = delete $methods{$code}; # avoid dupe registers
188 next unless $method;
189 my $attrs = $self->_parse_attrs( $c, $method, @{ $cache->[1] } );
190 if ( $attrs->{Private} && ( keys %$attrs > 1 ) ) {
191 $c->log->debug( 'Bad action definition "'
192 . join( ' ', @{ $cache->[1] } )
193 . qq/" for "$class->$method"/ )
194 if $c->debug;
195 next;
196 }
5fb67d52 197 my $reverse = $namespace ? "${namespace}/${method}" : $method;
234763d4 198 my $action = $self->create_action(
199 name => $method,
200 code => $code,
201 reverse => $reverse,
202 namespace => $namespace,
203 class => $class,
204 attributes => $attrs,
205 );
206
207 $c->dispatcher->register( $c, $action );
208 }
209}
210
211sub create_action {
212 my $self = shift;
213 my %args = @_;
214
215 my $class = (exists $args{attributes}{ActionClass}
216 ? $args{attributes}{ActionClass}[0]
217 : $self->_action_class);
218
2361ed67 219 Class::MOP::load_class($class);
220 #unless ( Class::Inspector->loaded($class) ) {
221 # require Class::Inspector->filename($class);
222 #}
a7caa492 223
234763d4 224 return $class->new( \%args );
225}
226
227sub _parse_attrs {
228 my ( $self, $c, $name, @attrs ) = @_;
229
230 my %raw_attributes;
231
232 foreach my $attr (@attrs) {
233
234 # Parse out :Foo(bar) into Foo => bar etc (and arrayify)
235
236 if ( my ( $key, $value ) = ( $attr =~ /^(.*?)(?:\(\s*(.+?)\s*\))?$/ ) )
237 {
238
239 if ( defined $value ) {
240 ( $value =~ s/^'(.*)'$/$1/ ) || ( $value =~ s/^"(.*)"/$1/ );
241 }
242 push( @{ $raw_attributes{$key} }, $value );
243 }
244 }
245
5fb67d52 246 #this will not work under moose
247 #my $hash = (ref $self ? $self : $self->config); # hate app-is-class
2361ed67 248 #action / actions should be an attribute of Controller
5fb67d52 249 my $hash = $self->config;
234763d4 250
251 if (exists $hash->{actions} || exists $hash->{action}) {
252 my $a = $hash->{actions} || $hash->{action};
253 %raw_attributes = ((exists $a->{'*'} ? %{$a->{'*'}} : ()),
254 %raw_attributes,
255 (exists $a->{$name} ? %{$a->{$name}} : ()));
256 }
257
258 my %final_attributes;
259
260 foreach my $key (keys %raw_attributes) {
261
262 my $raw = $raw_attributes{$key};
263
264 foreach my $value (ref($raw) eq 'ARRAY' ? @$raw : $raw) {
265
266 my $meth = "_parse_${key}_attr";
267 if ( $self->can($meth) ) {
268 ( $key, $value ) = $self->$meth( $c, $name, $value );
269 }
270 push( @{ $final_attributes{$key} }, $value );
271 }
272 }
273
274 return \%final_attributes;
275}
276
277sub _parse_Global_attr {
278 my ( $self, $c, $name, $value ) = @_;
279 return $self->_parse_Path_attr( $c, $name, "/$name" );
280}
281
282sub _parse_Absolute_attr { shift->_parse_Global_attr(@_); }
283
284sub _parse_Local_attr {
285 my ( $self, $c, $name, $value ) = @_;
286 return $self->_parse_Path_attr( $c, $name, $name );
287}
288
289sub _parse_Relative_attr { shift->_parse_Local_attr(@_); }
290
291sub _parse_Path_attr {
292 my ( $self, $c, $name, $value ) = @_;
293 $value ||= '';
294 if ( $value =~ m!^/! ) {
295 return ( 'Path', $value );
296 }
297 elsif ( length $value ) {
298 return ( 'Path', join( '/', $self->path_prefix($c), $value ) );
299 }
300 else {
301 return ( 'Path', $self->path_prefix($c) );
302 }
303}
304
305sub _parse_Regex_attr {
306 my ( $self, $c, $name, $value ) = @_;
307 return ( 'Regex', $value );
308}
309
310sub _parse_Regexp_attr { shift->_parse_Regex_attr(@_); }
311
312sub _parse_LocalRegex_attr {
313 my ( $self, $c, $name, $value ) = @_;
314 unless ( $value =~ s/^\^// ) { $value = "(?:.*?)$value"; }
315 return ( 'Regex', '^' . $self->path_prefix($c) . "/${value}" );
316}
317
318sub _parse_LocalRegexp_attr { shift->_parse_LocalRegex_attr(@_); }
319
320sub _parse_ActionClass_attr {
321 my ( $self, $c, $name, $value ) = @_;
322 unless ( $value =~ s/^\+// ) {
323 $value = join('::', $self->_action_class, $value );
324 }
325 return ( 'ActionClass', $value );
326}
327
9287719b 328sub _parse_MyAction_attr {
329 my ( $self, $c, $name, $value ) = @_;
330
331 my $appclass = Catalyst::Utils::class2appclass($self);
332 $value = "${appclass}::Action::${value}";
234763d4 333
9287719b 334 return ( 'ActionClass', $value );
335}
234763d4 336
3371;
338
339__END__
340
341=head1 CONFIGURATION
342
a269e0c2 343Like any other L<Catalyst::Component>, controllers have a config hash,
344accessible through $self->config from the controller actions. Some
345settings are in use by the Catalyst framework:
234763d4 346
347=head2 namespace
348
a269e0c2 349This specifies the internal namespace the controller should be bound
350to. By default the controller is bound to the URI version of the
351controller name. For instance controller 'MyApp::Controller::Foo::Bar'
352will be bound to 'foo/bar'. The default Root controller is an example
353of setting namespace to '' (the null string).
234763d4 354
a7caa492 355=head2 path
234763d4 356
357Sets 'path_prefix', as described below.
358
359=head1 METHODS
360
361=head2 $class->new($app, @args)
362
363Proxies through to NEXT::new and stashes the application instance as
364$self->_application.
365
366=head2 $self->action_for('name')
367
a269e0c2 368Returns the Catalyst::Action object (if any) for a given method name
369in this component.
234763d4 370
371=head2 $self->register_actions($c)
372
a269e0c2 373Finds all applicable actions for this component, creates
374Catalyst::Action objects (using $self->create_action) for them and
375registers them with $c->dispatcher.
234763d4 376
377=head2 $self->action_namespace($c)
378
a269e0c2 379Returns the private namespace for actions in this component. Defaults
380to a value from the controller name (for
381e.g. MyApp::Controller::Foo::Bar becomes "foo/bar") or can be
382overridden from the "namespace" config key.
234763d4 383
384
385=head2 $self->path_prefix($c)
386
a269e0c2 387Returns the default path prefix for :Local, :LocalRegex and relative
388:Path actions in this component. Defaults to the action_namespace or
389can be overridden from the "path" config key.
234763d4 390
391=head2 $self->create_action(%args)
392
a269e0c2 393Called with a hash of data to be use for construction of a new
394Catalyst::Action (or appropriate sub/alternative class) object.
234763d4 395
396Primarily designed for the use of register_actions.
397
a269e0c2 398=head2 $self->_application
234763d4 399
400=head2 $self->_app
401
402Returns the application instance stored by C<new()>
5ee249f2 403
404=head1 AUTHOR
405
406Sebastian Riedel, C<sri@oook.de>
234763d4 407Marcus Ramberg C<mramberg@cpan.org>
5ee249f2 408
409=head1 COPYRIGHT
410
a269e0c2 411This program is free software, you can redistribute it and/or modify
412it under the same terms as Perl itself.
5ee249f2 413
414=cut