X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst%2FController.pm;h=f2ccfa809522e601ad50947c28a4496c7d77257b;hp=3b5ce45e92f8e03e2ab883c194a0db6c0e3e2b68;hb=342d21698a97962c51114b6ebc6bb8626511cfc6;hpb=67837eb659c6b7bc46c2d7efd70b555324c305c3 diff --git a/lib/Catalyst/Controller.pm b/lib/Catalyst/Controller.pm index 3b5ce45..f2ccfa8 100644 --- a/lib/Catalyst/Controller.pm +++ b/lib/Catalyst/Controller.pm @@ -1,46 +1,99 @@ package Catalyst::Controller; use Moose; +use Class::MOP; +use Class::Load ':all'; +use String::RewritePrefix; use Moose::Util qw/find_meta/; - +use List::Util qw/first/; +use List::MoreUtils qw/uniq/; use namespace::clean -except => 'meta'; -BEGIN { extends qw/Catalyst::Component MooseX::MethodAttributes::Inheritable/; } +BEGIN { + extends qw/Catalyst::Component/; + with qw/MooseX::MethodAttributes::Role::AttrContainer::Inheritable/; +} +use MooseX::MethodAttributes; use Catalyst::Exception; use Catalyst::Utils; with 'Catalyst::Component::ApplicationAttribute'; -has path_prefix => - ( - is => 'rw', - isa => 'Str', - init_arg => 'path', - predicate => 'has_path_prefix', - ); - -has action_namespace => - ( - is => 'rw', - isa => 'Str', - init_arg => 'namespace', - predicate => 'has_action_namespace', - ); - -has actions => - ( - is => 'rw', - isa => 'HashRef', - init_arg => undef, - ); +has path_prefix => ( + is => 'rw', + isa => 'Str', + init_arg => 'path', + predicate => 'has_path_prefix', +); + +has action_namespace => ( + is => 'rw', + isa => 'Str', + init_arg => 'namespace', + predicate => 'has_action_namespace', +); + +has actions => ( + accessor => '_controller_actions', + isa => 'HashRef', + init_arg => undef, +); + +has _action_role_args => ( + traits => [qw(Array)], + isa => 'ArrayRef[Str]', + init_arg => 'action_roles', + default => sub { [] }, + handles => { + _action_role_args => 'elements', + }, +); + +has _action_roles => ( + traits => [qw(Array)], + isa => 'ArrayRef[RoleName]', + init_arg => undef, + lazy => 1, + builder => '_build__action_roles', + handles => { + _action_roles => 'elements', + }, +); + +has action_args => (is => 'ro'); + +# ->config(actions => { '*' => ... +has _all_actions_attributes => ( + is => 'ro', + isa => 'HashRef', + init_arg => undef, + lazy => 1, + builder => '_build__all_actions_attributes', +); sub BUILD { my ($self, $args) = @_; my $action = delete $args->{action} || {}; my $actions = delete $args->{actions} || {}; my $attr_value = $self->merge_config_hashes($actions, $action); - $self->actions($attr_value); + $self->_controller_actions($attr_value); + + # trigger lazy builder + $self->_all_actions_attributes; + $self->_action_roles; +} + +sub _build__action_roles { + my $self = shift; + my @roles = $self->_expand_role_shortname($self->_action_role_args); + load_class($_) for @roles; + return \@roles; +} + +sub _build__all_actions_attributes { + my ($self) = @_; + delete $self->_controller_actions->{'*'} || {}; } =head1 NAME @@ -52,9 +105,9 @@ Catalyst::Controller - Catalyst Controller base class package MyApp::Controller::Search use base qw/Catalyst::Controller/; - sub foo : Local { + sub foo : Local { my ($self,$c,@args) = @_; - ... + ... } # Dispatches to /search/foo =head1 DESCRIPTION @@ -67,11 +120,12 @@ for more info about how Catalyst dispatches to actions. =cut #I think both of these could be attributes. doesn't really seem like they need -#to ble class data. i think that attributes +default would work just fine -__PACKAGE__->mk_classdata($_) for qw/_dispatch_steps _action_class/; +#to be class data. i think that attributes +default would work just fine +__PACKAGE__->mk_classdata($_) for qw/_dispatch_steps _action_class _action_role_prefix/; __PACKAGE__->_dispatch_steps( [qw/_BEGIN _AUTO _ACTION/] ); __PACKAGE__->_action_class('Catalyst::Action'); +__PACKAGE__->_action_role_prefix([ 'Catalyst::ActionRole::' ]); sub _DISPATCH : Private { @@ -127,35 +181,37 @@ sub action_for { return $app->dispatcher->get_action($name, $self->action_namespace); } -#my opinion is that this whole sub really should be a builder method, not +#my opinion is that this whole sub really should be a builder method, not #something that happens on every call. Anyone else disagree?? -- groditi ## -- apparently this is all just waiting for app/ctx split around action_namespace => sub { my $orig = shift; my ( $self, $c ) = @_; + my $class = ref($self) || $self; + my $appclass = ref($c) || $c; if( ref($self) ){ return $self->$orig if $self->has_action_namespace; } else { - return $self->config->{namespace} if exists $self->config->{namespace}; + return $class->config->{namespace} if exists $class->config->{namespace}; } my $case_s; if( $c ){ - $case_s = $c->config->{case_sensitive}; + $case_s = $appclass->config->{case_sensitive}; } else { if ($self->isa('Catalyst')) { - $case_s = $self->config->{case_sensitive}; + $case_s = $class->config->{case_sensitive}; } else { if (ref $self) { - $case_s = $self->_application->config->{case_sensitive}; + $case_s = ref($self->_application)->config->{case_sensitive}; } else { confess("Can't figure out case_sensitive setting"); } } } - my $namespace = Catalyst::Utils::class2prefix(ref($self) || $self, $case_s) || ''; + my $namespace = Catalyst::Utils::class2prefix($self->catalyst_component_name, $case_s) || ''; $self->$orig($namespace) if ref($self); return $namespace; }; @@ -174,24 +230,55 @@ around path_prefix => sub { return $namespace; }; +sub get_action_methods { + my $self = shift; + my $meta = find_meta($self) || confess("No metaclass setup for $self"); + confess( + sprintf "Metaclass %s for %s cannot support register_actions.", + ref $meta, $meta->name, + ) unless $meta->can('get_nearest_methods_with_attributes'); + my @methods = $meta->get_nearest_methods_with_attributes; + + # actions specified via config are also action_methods + push( + @methods, + map { + $meta->find_method_by_name($_) + || confess( sprintf 'Action "%s" is not available from controller %s', + $_, ref $self ) + } keys %{ $self->_controller_actions } + ) if ( ref $self ); + return uniq @methods; +} + sub register_actions { my ( $self, $c ) = @_; - my $class = ref $self || $self; + $self->register_action_methods( $c, $self->get_action_methods ); +} + +sub register_action_methods { + my ( $self, $c, @methods ) = @_; + my $class = $self->catalyst_component_name; #this is still not correct for some reason. my $namespace = $self->action_namespace($c); - my $meta = find_meta($self); - confess("Wrong metaclass $meta for $self - " . $meta->name) - unless $meta->can('get_all_methods_with_attributes'); - my @methods = $meta->get_all_methods_with_attributes; + + # FIXME - fugly + if (!blessed($self) && $self eq $c && scalar(@methods)) { + my @really_bad_methods = grep { ! /^_(DISPATCH|BEGIN|AUTO|ACTION|END)$/ } map { $_->name } @methods; + if (scalar(@really_bad_methods)) { + $c->log->warn("Action methods (" . join(', ', @really_bad_methods) . ") found defined in your application class, $self. This is deprecated, please move them into a Root controller."); + } + } foreach my $method (@methods) { my $name = $method->name; - my $attributes = $method->attributes; - next unless $attributes; + # Horrible hack! All method metaclasses should have an attributes + # method, core Moose bug - see r13354. + my $attributes = $method->can('attributes') ? $method->attributes : []; my $attrs = $self->_parse_attrs( $c, $name, @{ $attributes } ); if ( $attrs->{Private} && ( keys %$attrs > 1 ) ) { - $c->log->debug( 'Bad action definition "' + $c->log->warn( 'Bad action definition "' . join( ' ', @{ $attributes } ) . qq/" for "$class->$name"/ ) if $c->debug; @@ -211,16 +298,83 @@ sub register_actions { } } -sub create_action { +sub _apply_action_class_roles { + my ($self, $class, @roles) = @_; + + load_class($_) for @roles; + my $meta = Moose::Meta::Class->initialize($class)->create_anon_class( + superclasses => [$class], + roles => \@roles, + cache => 1, + ); + $meta->add_method(meta => sub { $meta }); + + return $meta->name; +} + +sub action_class { my $self = shift; my %args = @_; my $class = (exists $args{attributes}{ActionClass} - ? $args{attributes}{ActionClass}[0] - : $self->_action_class); + ? $args{attributes}{ActionClass}[0] + : $self->_action_class); + + load_class($class); + return $class; +} + +sub create_action { + my $self = shift; + my %args = @_; + + my $class = $self->action_class(%args); + + load_class($class); + Moose->init_meta(for_class => $class) + unless Class::MOP::does_metaclass_exist($class); + + unless ($args{name} =~ /^_(DISPATCH|BEGIN|AUTO|ACTION|END)$/) { + my @roles = $self->gather_action_roles(%args); + push @roles, $self->gather_default_action_roles(%args); + + $class = $self->_apply_action_class_roles($class, @roles) if @roles; + } + + my $action_args = ( + ref($self) + ? $self->action_args + : $self->config->{action_args} + ); + + my %extra_args = ( + %{ $action_args->{'*'} || {} }, + %{ $action_args->{ $args{name} } || {} }, + ); + + return $class->new({ %extra_args, %args }); +} + +sub gather_action_roles { + my ($self, %args) = @_; + return ( + (blessed $self ? $self->_action_roles : ()), + @{ $args{attributes}->{Does} || [] }, + ); +} + +sub gather_default_action_roles { + my ($self, %args) = @_; + my @roles = (); + push @roles, 'Catalyst::ActionRole::HTTPMethods' + if $args{attributes}->{Method}; - Class::MOP::load_class($class); - return $class->new( \%args ); + push @roles, 'Catalyst::ActionRole::ConsumesContent' + if $args{attributes}->{Consumes}; + + push @roles, 'Catalyst::ActionRole::Scheme' + if $args{attributes}->{Scheme}; + return @roles; } sub _parse_attrs { @@ -242,36 +396,59 @@ sub _parse_attrs { } } - #I know that the original behavior was to ignore action if actions was set - # but i actually think this may be a little more sane? we can always remove - # the merge behavior quite easily and go back to having actions have - # presedence over action by modifying the keys. i honestly think this is - # superior while mantaining really high degree of compat - my $actions; + my ($actions_config, $all_actions_config); if( ref($self) ) { - $actions = $self->actions; + $actions_config = $self->_controller_actions; + # No, you're not getting actions => { '*' => ... } with actions in MyApp. + $all_actions_config = $self->_all_actions_attributes; } else { my $cfg = $self->config; - $actions = $self->merge_config_hashes($cfg->{actions}, $cfg->{action}); + $actions_config = $self->merge_config_hashes($cfg->{actions}, $cfg->{action}); + $all_actions_config = {}; } - %raw_attributes = ((exists $actions->{'*'} ? %{$actions->{'*'}} : ()), - %raw_attributes, - (exists $actions->{$name} ? %{$actions->{$name}} : ())); + %raw_attributes = ( + %raw_attributes, + # Note we deep copy array refs here to stop crapping on config + # when attributes are parsed. RT#65463 + exists $actions_config->{$name} ? map { ref($_) eq 'ARRAY' ? [ @$_ ] : $_ } %{ $actions_config->{$name } } : (), + ); + # Private actions with additional attributes will raise a warning and then + # be ignored. Adding '*' arguments to the default _DISPATCH / etc. methods, + # which are Private, will prevent those from being registered. They should + # probably be turned into :Actions instead, or we might want to otherwise + # disambiguate between those built-in internal actions and user-level + # Private ones. + %raw_attributes = (%{ $all_actions_config }, %raw_attributes) + unless $raw_attributes{Private}; my %final_attributes; - foreach my $key (keys %raw_attributes) { + while (my ($key, $value) = each %raw_attributes){ + my $new_attrs = $self->_parse_attr($c, $name, $key => $value ); + push @{ $final_attributes{$_} }, @{ $new_attrs->{$_} } for keys %$new_attrs; + } - my $raw = $raw_attributes{$key}; + return \%final_attributes; +} - foreach my $value (ref($raw) eq 'ARRAY' ? @$raw : $raw) { +sub _parse_attr { + my ($self, $c, $name, $key, $values) = @_; - my $meth = "_parse_${key}_attr"; - if ( my $code = $self->can($meth) ) { - ( $key, $value ) = $self->$code( $c, $name, $value ); + my %final_attributes; + foreach my $value (ref($values) eq 'ARRAY' ? @$values : $values) { + my $meth = "_parse_${key}_attr"; + if ( my $code = $self->can($meth) ) { + my %new_attrs = $self->$code( $c, $name, $value ); + while (my ($new_key, $value) = each %new_attrs){ + my $new_attrs = $key eq $new_key ? + { $new_key => [$value] } : + $self->_parse_attr($c, $name, $new_key => $value ); + push @{ $final_attributes{$_} }, @{ $new_attrs->{$_} } for keys %$new_attrs; } + } + else { push( @{ $final_attributes{$key} }, $value ); } } @@ -281,14 +458,16 @@ sub _parse_attrs { sub _parse_Global_attr { my ( $self, $c, $name, $value ) = @_; - return $self->_parse_Path_attr( $c, $name, "/$name" ); + # _parse_attr will call _parse_Path_attr for us + return Path => "/$name"; } sub _parse_Absolute_attr { shift->_parse_Global_attr(@_); } sub _parse_Local_attr { my ( $self, $c, $name, $value ) = @_; - return $self->_parse_Path_attr( $c, $name, $name ); + # _parse_attr will call _parse_Path_attr for us + return Path => $name; } sub _parse_Relative_attr { shift->_parse_Local_attr(@_); } @@ -307,25 +486,6 @@ sub _parse_Path_attr { } } -sub _parse_Regex_attr { - my ( $self, $c, $name, $value ) = @_; - return ( 'Regex', $value ); -} - -sub _parse_Regexp_attr { shift->_parse_Regex_attr(@_); } - -sub _parse_LocalRegex_attr { - my ( $self, $c, $name, $value ) = @_; - unless ( $value =~ s/^\^// ) { $value = "(?:.*?)$value"; } - - my $prefix = $self->path_prefix( $c ); - $prefix .= '/' if length( $prefix ); - - return ( 'Regex', "^${prefix}${value}" ); -} - -sub _parse_LocalRegexp_attr { shift->_parse_LocalRegex_attr(@_); } - sub _parse_Chained_attr { my ($self, $c, $name, $value) = @_; @@ -359,15 +519,14 @@ sub _parse_ChainedParent_attr { } sub _parse_PathPrefix_attr { - my $self = shift; - return PathPart => $self->path_prefix; + my ( $self, $c ) = @_; + return PathPart => $self->path_prefix($c); } sub _parse_ActionClass_attr { my ( $self, $c, $name, $value ) = @_; - unless ( $value =~ s/^\+// ) { - $value = join('::', $self->_action_class, $value ); - } + my $appname = $self->_application; + $value = Catalyst::Utils::resolve_namespace($appname . '::Action', $self->_action_class, $value); return ( 'ActionClass', $value ); } @@ -375,11 +534,44 @@ sub _parse_MyAction_attr { my ( $self, $c, $name, $value ) = @_; my $appclass = Catalyst::Utils::class2appclass($self); - $value = "${appclass}::Action::${value}"; + $value = "+${appclass}::Action::${value}"; return ( 'ActionClass', $value ); } +sub _parse_Does_attr { + my ($self, $app, $name, $value) = @_; + return Does => $self->_expand_role_shortname($value); +} + +sub _parse_GET_attr { Method => 'GET' } +sub _parse_POST_attr { Method => 'POST' } +sub _parse_PUT_attr { Method => 'PUT' } +sub _parse_DELETE_attr { Method => 'DELETE' } +sub _parse_OPTION_attr { Method => 'OPTION' } +sub _parse_HEAD_attr { Method => 'HEAD' } + +sub _expand_role_shortname { + my ($self, @shortnames) = @_; + my $app = $self->_application; + + my $prefix = $self->can('_action_role_prefix') ? $self->_action_role_prefix : ['Catalyst::ActionRole::']; + my @prefixes = (qq{${app}::ActionRole::}, @$prefix); + + return String::RewritePrefix->rewrite( + { '' => sub { + my $loaded = load_first_existing_class( + map { "$_$_[0]" } @prefixes + ); + return first { $loaded =~ /^$_/ } + sort { length $b <=> length $a } @prefixes; + }, + '~' => $prefixes[0], + '+' => '' }, + @shortnames, + ); +} + __PACKAGE__->meta->make_immutable; 1; @@ -400,28 +592,62 @@ controller name. For instance controller 'MyApp::Controller::Foo::Bar' will be bound to 'foo/bar'. The default Root controller is an example of setting namespace to '' (the null string). -=head2 path +=head2 path Sets 'path_prefix', as described below. +=head2 action + +Allows you to set the attributes that the dispatcher creates actions out of. +This allows you to do 'rails style routes', or override some of the +attribute definitions of actions composed from Roles. +You can set arguments globally (for all actions of the controller) and +specifically (for a single action). + + __PACKAGE__->config( + action => { + '*' => { Chained => 'base', Args => 0 }, + base => { Chained => '/', PathPart => '', CaptureArgs => 0 }, + }, + ); + +In the case above every sub in the package would be made into a Chain +endpoint with a URI the same as the sub name for each sub, chained +to the sub named C. Ergo dispatch to C would call the +C method, then the C method. + +=head2 action_args + +Allows you to set constructor arguments on your actions. You can set arguments +globally and specifically (as above). +This is particularly useful when using Cs +(L) and custom Ces. + + __PACKAGE__->config( + action_args => { + '*' => { globalarg1 => 'hello', globalarg2 => 'goodbye' }, + 'specific_action' => { customarg => 'arg1' }, + }, + ); + +In the case above the action class associated with C would get +passed the following arguments, in addition to the normal action constructor +arguments, when it is instantiated: + + (globalarg1 => 'hello', globalarg2 => 'goodbye', customarg => 'arg1') + =head1 METHODS -=head2 $class->new($app, @args) +=head2 BUILDARGS ($app, @args) -Proxies through to NEXT::new and stashes the application instance as -$self->_application. +From L, stashes the application +instance as $self->_application. =head2 $self->action_for('name') Returns the Catalyst::Action object (if any) for a given method name in this component. -=head2 $self->register_actions($c) - -Finds all applicable actions for this component, creates -Catalyst::Action objects (using $self->create_action) for them and -registers them with $c->dispatcher. - =head2 $self->action_namespace($c) Returns the private namespace for actions in this component. Defaults @@ -432,16 +658,46 @@ overridden from the "namespace" config key. =head2 $self->path_prefix($c) -Returns the default path prefix for :PathPrefix, :Local, :LocalRegex and +Returns the default path prefix for :PathPrefix, :Local and relative :Path actions in this component. Defaults to the action_namespace or can be overridden from the "path" config key. +=head2 $self->register_actions($c) + +Finds all applicable actions for this component, creates +Catalyst::Action objects (using $self->create_action) for them and +registers them with $c->dispatcher. + +=head2 $self->get_action_methods() + +Returns a list of L objects, doing the +L role, which are the set of +action methods for this package. + +=head2 $self->register_action_methods($c, @methods) + +Creates action objects for a set of action methods using C< create_action >, +and registers them with the dispatcher. + +=head2 $self->action_class(%args) + +Used when a controller is creating an action to determine the correct base +action class to use. + =head2 $self->create_action(%args) Called with a hash of data to be use for construction of a new Catalyst::Action (or appropriate sub/alternative class) object. -Primarily designed for the use of register_actions. +=head2 $self->gather_action_roles(\%action_args) + +Gathers the list of roles to apply to an action with the given %action_args. + +=head2 $self->gather_default_action_roles(\%action_args) + +returns a list of action roles to be applied based on core, builtin rules. +Currently only the L role is applied +this way. =head2 $self->_application @@ -449,13 +705,256 @@ Primarily designed for the use of register_actions. Returns the application instance stored by C +=head1 ACTION SUBROUTINE ATTRIBUTES + +Please see L for more details + +Think of action attributes as a sort of way to record metadata about an action, +similar to how annotations work in other languages you might have heard of. +Generally L uses these to influence how the dispatcher sees your +action and when it will run it in response to an incoming request. They can +also be used for other things. Here's a summary, but you should refer to the +linked manual page for additional help. + +=head2 Global + + sub homepage :Global { ... } + +A global action defined in any controller always runs relative to your root. +So the above is the same as: + + sub myaction :Path("/homepage") { ... } + +=head2 Absolute + +Status: Deprecated alias to L. + +=head2 Local + +Alias to "Path("$action_name"). The following two actions are the same: + + sub myaction :Local { ... } + sub myaction :Path('myaction') { ... } + +=head2 Relative + +Status: Deprecated alias to L + +=head2 Path + +Handle various types of paths: + + package MyApp::Controller::Baz { + + ... + + sub myaction1 :Path { ... } # -> /baz + sub myaction2 :Path('foo') { ... } # -> /baz/foo + sub myaction2 :Path('/bar') { ... } # -> /bar + } + +This is a general toolbox for attaching your action to a given path. + + +=head2 Regex + +=head2 Regexp + +B Use Chained methods or other techniques. +If you really depend on this, install the standalone +L distribution. + +A global way to match a give regular expression in the incoming request path. + +=head2 LocalRegex + +=head2 LocalRegexp + +B Use Chained methods or other techniques. +If you really depend on this, install the standalone +L distribution. + +Like L but scoped under the namespace of the containing controller + +=head2 Chained + +=head2 ChainedParent + +=head2 PathPrefix + +=head2 PathPart + +=head2 CaptureArgs + +Please see L + +=head2 ActionClass + +Set the base class for the action, defaults to L. It is now +preferred to use L. + +=head2 MyAction + +Set the ActionClass using a custom Action in your project namespace. + +The following is exactly the same: + + sub foo_action1 : Local ActionClass('+MyApp::Action::Bar') { ... } + sub foo_action2 : Local MyAction('Bar') { ... } + +=head2 Does + + package MyApp::Controller::Zoo; + + sub foo : Local Does('Moo') { ... } # Catalyst::ActionRole:: + sub bar : Local Does('~Moo') { ... } # MyApp::ActionRole::Moo + sub baz : Local Does('+MyApp::ActionRole::Moo') { ... } + +=head2 GET + +=head2 POST + +=head2 PUT + +=head2 DELETE + +=head2 OPTION + +=head2 HEAD + +=head2 PATCH + +=head2 Method('...') + +Sets the give action path to match the specified HTTP method, or via one of the +broadly accepted methods of overriding the 'true' method (see +L). + +=head2 Args + +When used with L indicates the number of arguments expected in +the path. However if no Args value is set, assumed to 'slurp' all +remaining path pars under this namespace. + +=head2 Consumes('...') + +Matches the current action against the content-type of the request. Typically +this is used when the request is a POST or PUT and you want to restrict the +submitted content type. For example, you might have an HTML for that either +returns classic url encoded form data, or JSON when Javascript is enabled. In +this case you may wish to match either incoming type to one of two different +actions, for properly processing. + +Examples: + + sub is_json : Chained('start') Consumes('application/json') { ... } + sub is_urlencoded : Chained('start') Consumes('application/x-www-form-urlencoded') { ... } + sub is_multipart : Chained('start') Consumes('multipart/form-data') { ... } + +To reduce boilerplate, we include the following content type shortcuts: + +Examples + + sub is_json : Chained('start') Consume(JSON) { ... } + sub is_urlencoded : Chained('start') Consumes(UrlEncoded) { ... } + sub is_multipart : Chained('start') Consumes(Multipart) { ... } + +You may specify more than one match: + + sub is_more_than_one + : Chained('start') + : Consumes('application/x-www-form-urlencoded') + : Consumes('multipart/form-data') + + sub is_more_than_one + : Chained('start') + : Consumes(UrlEncoded) + : Consumes(Multipart) + +Since it is a common case the shortcut C matches both +'application/x-www-form-urlencoded' and 'multipart/form-data'. Here's the full +list of available shortcuts: + + JSON => 'application/json', + JS => 'application/javascript', + PERL => 'application/perl', + HTML => 'text/html', + XML => 'text/XML', + Plain => 'text/plain', + UrlEncoded => 'application/x-www-form-urlencoded', + Multipart => 'multipart/form-data', + HTMLForm => ['application/x-www-form-urlencoded','multipart/form-data'], + +Please keep in mind that when dispatching, L will match the first most +relevant case, so if you use the C attribute, you should place your +most accurate matches early in the Chain, and your 'catchall' actions last. + +See L for more. + +=head2 Scheme(...) + +Allows you to specify a URI scheme for the action or action chain. For example +you can required that a given path be C or that it is a websocket endpoint +C or C. For an action chain you may currently only have one defined +Scheme. + + package MyApp::Controller::Root; + + use base 'Catalyst::Controller'; + + sub is_http :Path(scheme) Scheme(http) Args(0) { + my ($self, $c) = @_; + $c->response->body("is_http"); + } + + sub is_https :Path(scheme) Scheme(https) Args(0) { + my ($self, $c) = @_; + $c->response->body("is_https"); + } + +In the above example http://localhost/root/scheme would match the first +action (is_http) but https://localhost/root/scheme would match the second. + +As an added benefit, if an action or action chain defines a Scheme, when using +$c->uri_for the scheme of the generated URL will use what you define in the action +or action chain (the current behavior is to set the scheme based on the current +incoming request). This makes it easier to use uri_for on websites where some +paths are secure and others are not. You may also use this to other schemes +like websockets. + +See L for more. + +=head1 OPTIONAL METHODS + +=head2 _parse_[$name]_attr + +Allows you to customize parsing of subroutine attributes. + + sub myaction1 :Path TwoArgs { ... } + + sub _parse_TwoArgs_attr { + my ( $self, $c, $name, $value ) = @_; + # $self -> controller instance + # + return(Args => 2); + } + +Please note that this feature does not let you actually assign new functions +to actions via subroutine attributes, but is really more for creating useful +aliases to existing core and extended attributes, and transforms based on +existing information (like from configuration). Code for actually doing +something meaningful with the subroutine attributes will be located in the +L classes (or your subclasses), L and +in subclasses of L. Remember these methods only get +called basically once when the application is starting, not per request! + =head1 AUTHORS Catalyst Contributors, see Catalyst.pm =head1 COPYRIGHT -This program is free software, you can redistribute it and/or modify +This library is free software. You can redistribute it and/or modify it under the same terms as Perl itself. =cut