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=d0a8570d4227c4814f895a21b767aa5af1e191a3;hp=cea72349940fff7285b7d1fa91b21f6d1c4ae4bf;hb=1d00b2ffb8806bd0a8190ee8580a85895e48f8e4;hpb=c6392ad5e874d642cea7f774aba922f3540044db diff --git a/lib/Catalyst/Controller.pm b/lib/Catalyst/Controller.pm index cea7234..d0a8570 100644 --- a/lib/Catalyst/Controller.pm +++ b/lib/Catalyst/Controller.pm @@ -9,7 +9,10 @@ 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; @@ -117,7 +120,7 @@ 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 +#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/] ); @@ -140,7 +143,12 @@ sub _BEGIN : Private { my $begin = ( $c->get_actions( 'begin', $c->namespace ) )[-1]; return 1 unless $begin; $begin->dispatch( $c ); - return !@{ $c->error }; + #If there is an error, all bets off + if( @{ $c->error }) { + return !@{ $c->error }; + } else { + return $c->state || 1; + } } sub _AUTO : Private { @@ -150,7 +158,7 @@ sub _AUTO : Private { $auto->dispatch( $c ); return 0 unless $c->state; } - return 1; + return $c->state || 1; } sub _ACTION : Private { @@ -161,7 +169,12 @@ sub _ACTION : Private { { $c->action->dispatch( $c ); } - return !@{ $c->error }; + #If there is an error, all bets off + if( @{ $c->error }) { + return !@{ $c->error }; + } else { + return $c->state || 1; + } } sub _END : Private { @@ -275,7 +288,7 @@ sub register_action_methods { 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; @@ -317,7 +330,7 @@ sub action_class { ? $args{attributes}{ActionClass}[0] : $self->_action_class); - Class::MOP::load_class($class); + load_class($class); return $class; } @@ -333,6 +346,8 @@ sub create_action { 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; } @@ -352,13 +367,29 @@ sub create_action { 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}; + + push @roles, 'Catalyst::ActionRole::ConsumesContent' + if $args{attributes}->{Consumes}; + + push @roles, 'Catalyst::ActionRole::Scheme' + if $args{attributes}->{Scheme}; + + push @roles, 'Catalyst::ActionRole::QueryMatching' + if $args{attributes}->{Query}; + return @roles; +} + sub _parse_attrs { my ( $self, $c, $name, @attrs ) = @_; @@ -407,16 +438,30 @@ sub _parse_attrs { 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 ); } } @@ -426,14 +471,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(@_); } @@ -452,25 +499,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) = @_; @@ -519,7 +547,7 @@ 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 ); } @@ -529,6 +557,14 @@ sub _parse_Does_attr { 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_OPTIONS_attr { Method => 'OPTIONS' } +sub _parse_HEAD_attr { Method => 'HEAD' } +sub _parse_PATCH_attr { Method => 'PATCH' } + sub _expand_role_shortname { my ($self, @shortnames) = @_; my $app = $self->_application; @@ -636,7 +672,7 @@ 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. @@ -671,12 +707,330 @@ Catalyst::Action (or appropriate sub/alternative class) object. 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 =head2 $self->_app 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 + +Allowed values for CaptureArgs is a single integer (CaptureArgs(2), meaning two +allowed) or you can declare a L, L or L +named constraint such as CaptureArgs(Int,Str) would require two args with +the first being a Integer and the second a string. You may declare your own +custom type constraints and import them into the controller namespace: + + package MyApp::Controller::Root; + + use Moose; + use MooseX::MethodAttributes; + use MyApp::Types qw/Int/; + + extends 'Catalyst::Controller'; + + sub chain_base :Chained(/) CaptureArgs(1) { } + + sub any_priority_chain :Chained(chain_base) PathPart('') Args(1) { } + + sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { } + +See L for more. + +Please see L for more. + +=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. + +Allowed values for Args is a single integer (Args(2), meaning two allowed) or you +can declare a L, L or L named constraint such +as Args(Int,Str) would require two args with the first being a Integer and the +second a string. You may declare your own custom type constraints and import +them into the controller namespace: + + package MyApp::Controller::Root; + + use Moose; + use MooseX::MethodAttributes; + use MyApp::Types qw/Tuple Int Str StrMatch UserId/; + + extends 'Catalyst::Controller'; + + sub user :Local Args(UserId) { + my ($self, $c, $int) = @_; + } + + sub an_int :Local Args(Int) { + my ($self, $c, $int) = @_; + } + + sub many_ints :Local Args(ArrayRef[Int]) { + my ($self, $c, @ints) = @_; + } + + sub match :Local Args(StrMatch[qr{\d\d-\d\d-\d\d}]) { + my ($self, $c, $int) = @_; + } + +If you choose not to use imported type constraints (like L, or +you may use L 'stringy' types however just like when you use these types in your +declared attributes you must quote them: + + sub my_moose_type :Local Args('Int') { ... } + +If you use 'reference' type constraints (such as ArrayRef[Int]) that have an unknown +number of allowed matches, we set this the same way "Args" is. Please keep in mind +that actions with an undetermined number of args match at lower precedence than those +with a fixed number. You may use reference types such as Tuple from L +that allows you to fix the number of allowed args. For example Args(Tuple[Int,Int]) +would be determined to be two args (or really the same as Args(Int,Int).) You may +find this useful for creating custom subtypes with complex matching rules that you +wish to reuse over many actions. + +See L for more. + +=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