# This file documents the revision history for Perl extension Catalyst.
- - Add test that env does not get trampled (Arthur Axel fREW Schmidt)
+5.90099 - 2015-08-XX
+ - Document using namespace::autoclean with controllers that have actions
+ with type constraints.
+ - Look for type constraints in super classes and consumed roles.
+ - Change the way the stash middleware works to no longer localize $psgi_env.
5.90098 - 2015-08-11
- Fix for RT#106373 (Issue when you try to install and also have an old
=cut
use Moose;
-use Scalar::Util 'looks_like_number';
+use Scalar::Util 'looks_like_number', 'blessed';
use Moose::Util::TypeConstraints ();
with 'MooseX::Emulate::Class::Accessor::Fast';
use namespace::clean -except => 'meta';
sub resolve_type_constraint {
my ($self, $name) = @_;
- my @tc = eval "package ${\$self->class}; $name" or die "'$name' not a type constraint in ${\$self->private_path}";
+
+ if(defined($name) && blessed($name) && $name->can('check')) {
+ # Its already a TC, good to go.
+ return $name;
+ }
+
+ if($name=~m/::/) {
+ eval "use Type::Registry; 1" || die "Can't resolve type constraint $name without installing Type::Tiny";
+ my $tc = Type::Registry->new->foreign_lookup($name);
+ return defined $tc ? $tc : die "'$name' not a type constraint in ${\$self->private_path}";
+ }
+
+ my @tc = eval "package ${\$self->class}; $name" or do {
+ # ok... so its not defined in the package. we need to look at all the roles
+ # and superclasses, look for attributes and figure it out.
+ # Superclasses take precedence;
+ #
+ my @supers = map { $_->meta } $self->class->meta->superclasses;
+ my @roles = $self->class->meta->calculate_all_roles;
+
+ # So look thru all the super and roles in order and return the
+ # first type constraint found. We should probably find all matching
+ # type constraints and try to do some sort of resolution.
+
+ foreach my $parent (@roles, @supers) {
+ if(my $m = $parent->get_method($self->name)) {
+ if($m->can('attributes')) {
+ my ($key, $value) = map { $_ =~ /^(.*?)(?:\(\s*(.+?)\s*\))?$/ }
+ grep { $_=~/^Args\(/ or $_=~/^CaptureArgs\(/ }
+ @{$m->attributes};
+ next unless $value eq $name;
+ my @tc = eval "package ${\$parent->name}; $name";
+ return @tc if @tc;
+ }
+ }
+ }
+
+ die "'$name' not a type constraint in ${\$self->private_path}";
+ };
+
if($tc[0]) {
return map { ref($_) ? $_ : Moose::Util::TypeConstraints::find_or_parse_type_constraint($_) } @tc;
} else {
my $new_env = +{ %$env };
my %stash = %{ ($env->{+PSGI_KEY} || sub {})->() || +{} };
- $new_env->{+PSGI_KEY} = _create_stash( \%stash );
- return $self->app->($new_env);
+ $env->{+PSGI_KEY} = _create_stash( \%stash );
+ return $self->app->($env);
}
=head1 NAME
recommend looking at the copious documentation in L<Type::Tiny> or in L<MooseX::Types> if
you prefer that system. The author recommends L<Type::Tiny> if you are unsure which to use.
+=head3 Type constraint namespace.
+
+By default we assume the namespace which defines the type constraint is in the package
+which contains the action declaring the arg or capture arg. However if you do not wish
+to import type constraints into you package, you may use a fully qualified namespace for
+your type constraint. If you do this you must install L<Type::Tiny> which defines the
+code used to lookup and normalize the various types of Type constraint libraries.
+
+Example:
+
+ package MyApp::Example;
+
+ use Moose;
+ use MooseX::MethodAttributes;
+
+ extends 'Catalyst::Controller';
+
+ sub an_int_ns :Local Args(MyApp::Types::Int) {
+ my ($self, $c, $int) = @_;
+ $c->res->body('an_int (withrole)');
+ }
+
+Would basically work the same as:
+
+ package MyApp::Example;
+
+ use Moose;
+ use MooseX::MethodAttributes;
+ use MyApp::Types 'Int';
+
+ extends 'Catalyst::Controller';
+
+ sub an_int_ns :Local Args(Int) {
+ my ($self, $c, $int) = @_;
+ $c->res->body('an_int (withrole)');
+ }
+
+=head3 namespace::autoclean
+
+If you want to use L<namespace::autoclean> in your controllers you must 'except' imported
+type constraints since the code that resolves type constraints in args / capture args
+run after the cleaning. For example:
+
+ package MyApp::Controller::Autoclean;
+
+ use Moose;
+ use MooseX::MethodAttributes;
+ use namespace::autoclean -except => 'Int';
+ use MyApp::Types qw/Int/;
+
+ extends 'Catalyst::Controller';
+
+ sub an_int :Local Args(Int) {
+ my ($self, $c, $int) = @_;
+ $c->res->body('an_int (autoclean)');
+ }
+
+=head3 Using roles and base controller with type constraints
+
+If your controller is using a base class or a role that has an action with a type constraint
+you should declare your use of the type constraint in that role or base controller in the
+same way as you do in main controllers. Catalyst will try to find the package with declares
+the type constraint first by looking in any roles and then in superclasses. It will use the
+first package that defines the type constraint. For example:
+
+ package MyApp::Role;
+
+ use Moose::Role;
+ use MooseX::MethodAttributes::Role;
+ use MyApp::Types qw/Int/;
+
+ sub an_int :Local Args(Int) {
+ my ($self, $c, $int) = @_;
+ $c->res->body('an_int (withrole)');
+ }
+
+ sub an_int_ns :Local Args(MyApp::Types::Int) {
+ my ($self, $c, $int) = @_;
+ $c->res->body('an_int (withrole)');
+ }
+
+ package MyApp::BaseController;
+
+ use Moose;
+ use MooseX::MethodAttributes;
+ use MyApp::Types qw/Int/;
+
+ extends 'Catalyst::Controller';
+
+ sub from_parent :Local Args(Int) {
+ my ($self, $c, $id) = @_;
+ $c->res->body('from_parent $id');
+ }
+
+ package MyApp::Controller::WithRole;
+
+ use Moose;
+ use MooseX::MethodAttributes;
+
+ extends 'MyApp::BaseController';
+
+ with 'MyApp::Role';
+
+If you have complex controller hierarchy, we
+do not at this time attempt to look for all packages with a match type constraint, but instead
+take the first one found. In the future we may add code that attempts to insure a sane use
+of subclasses with type constraints but right now there are no clear use cases so report issues
+and interests.
+
=head3 Match order when more than one Action matches a path.
As previously described, L<Catalyst> will match 'the longest path', which generally means
MyApp::Controller::Root->config(namespace=>'');
+ package MyApp::Controller::Autoclean;
+ $INC{'MyApp/Controller/Autoclean.pm'} = __FILE__;
+
+ use Moose;
+ use MooseX::MethodAttributes;
+ use namespace::autoclean -except => 'Int';
+
+ use MyApp::Types qw/Int/;
+
+ extends 'Catalyst::Controller';
+
+ sub an_int :Local Args(Int) {
+ my ($self, $c, $int) = @_;
+ $c->res->body('an_int (autoclean)');
+ }
+
+ MyApp::Controller::Autoclean->config(namespace=>'autoclean');
+
+ package MyApp::Role;
+ $INC{'MyApp/Role.pm'} = __FILE__;
+
+ use Moose::Role;
+ use MooseX::MethodAttributes::Role;
+ use MyApp::Types qw/Int/;
+
+ sub an_int :Local Args(Int) {
+ my ($self, $c, $int) = @_;
+ $c->res->body('an_int (withrole)');
+ }
+
+ sub an_int_ns :Local Args(MyApp::Types::Int) {
+ my ($self, $c, $int) = @_;
+ $c->res->body('an_int (withrole)');
+ }
+
+ package MyApp::BaseController;
+ $INC{'MyApp/BaseController.pm'} = __FILE__;
+
+ use Moose;
+ use MooseX::MethodAttributes;
+ use MyApp::Types qw/Int/;
+
+ extends 'Catalyst::Controller';
+
+ sub from_parent :Local Args(Int) {
+ my ($self, $c, $id) = @_;
+ $c->res->body("from_parent $id");
+ }
+
+ package MyApp::Controller::WithRole;
+ $INC{'MyApp/Controller/WithRole.pm'} = __FILE__;
+
+ use Moose;
+ use MooseX::MethodAttributes;
+
+ extends 'MyApp::BaseController';
+
+ with 'MyApp::Role';
+
+ MyApp::Controller::WithRole->config(namespace=>'withrole');
+
package MyApp;
use Catalyst;
}
+{
+ my $res = request '/autoclean/an_int/1';
+ is $res->content, 'an_int (autoclean)';
+}
+
+{
+ my $res = request '/withrole/an_int_ns/S';
+ is $res->content, 'default';
+}
+
+{
+ my $res = request '/withrole/an_int_ns/111';
+ is $res->content, 'an_int (withrole)';
+}
+
+{
+ my $res = request '/withrole/an_int/1';
+ is $res->content, 'an_int (withrole)';
+}
+
+{
+ my $res = request '/withrole/from_parent/1';
+ is $res->content, 'from_parent 1';
+}
done_testing;