let you use Hash::MultiValue everywhere if you like it
John Napiorkowski [Fri, 18 Oct 2013 21:56:49 +0000 (16:56 -0500)]
Changes
lib/Catalyst.pm
lib/Catalyst/Engine.pm
lib/Catalyst/Request.pm
t/lib/TestFromPSGI.pm
t/more-psgi-compat.t

diff --git a/Changes b/Changes
index beadd42..e53bedc 100644 (file)
--- a/Changes
+++ b/Changes
   - NEW FEATURE: Catalyst::Response can now pull response from a PSGI
     specification response.  This makes it easier to host external Plack
     applications under Catalyst.  See Catalyst::Response->from_psgi_response
+  - NEW FEATURE: New configuration option 'use_hash_multivalue_in_request'
+    will populate $request methods 'parameters', 'body_parameters' and
+    'query_parameters' with an instance of Hash::MultiValue instead of a
+    HashRef.  This is used by Plack and is intended to reduce the need to
+    write defensive logic since you are never sure if an incoming parameter
+    is a scalar or arrayref.
 
 5.90049_003 - 2013-09-20
   - Documented the new body_data method added in the previous release
index aa98926..2d3a391 100644 (file)
@@ -65,6 +65,8 @@ sub _build_request_constructor_args {
     my %p = ( _log => $self->log );
     $p{_uploadtmp} = $self->_uploadtmp if $self->_has_uploadtmp;
     $p{data_handlers} = {$self->registered_data_handlers};
+    $p{_use_hash_multivalue} = $self->config->{use_hash_multivalue_in_request}
+      if $self->config->{use_hash_multivalue_in_request};
     \%p;
 }
 
@@ -3397,6 +3399,26 @@ In the future this might become the default behavior.
 
 =item *
 
+C<use_hash_multivalue_in_request>
+
+In L<Catalyst::Request> the methods C<query_parameters>, C<body_parametes>
+and C<parameters> return a hashref where values might be scalar or an arrayref
+depending on the incoming data.  In many cases this can be undesirable as it
+leads one to writing defensive code like the following:
+
+    my ($val) = ref($c->req->parameters->{a}) ?
+      @{$c->req->parameters->{a}} :
+        $c->req->parameters->{a};
+
+Setting this configuration item to true will make L<Catalyst> populate the
+attributes underlying these methods with an instance of L<Hash::MultiValue>
+which is used by L<Plack::Request> and others to solve this very issue.  You
+may prefer this behavior to the default, if so enable this option (be warned
+if you enable it in a legacy application we are not sure if it is completely
+backwardly compatible).
+
+=item *
+
 C<psgi_middleware> - See L<PSGI MIDDLEWARE>.
 
 =item *
index 2a1ed87..3b4ceb0 100644 (file)
@@ -492,7 +492,10 @@ sub prepare_query_parameters {
     my $env = $c->request->env;
 
     if(my $query_obj = $env->{'plack.request.query'}) {
-         $c->request->query_parameters($query_obj->as_hashref_mixed);
+         $c->request->query_parameters(
+           $c->request->_use_hash_multivalue ?
+              $query_obj->clone :
+              $query_obj->as_hashref_mixed);
          return;
     }
 
@@ -540,7 +543,10 @@ sub prepare_query_parameters {
     }
 
     $env->{'plack.request.query'} ||= Hash::MultiValue->from_mixed(\%query);
-    $c->request->query_parameters( \%query );
+    $c->request->query_parameters( 
+      $c->request->_use_hash_multivalue ?
+        $env->{'plack.request.query'}->clone :
+        \%query);
 }
 
 =head2 $self->prepare_read($c)
index 7c1ae96..87ce66a 100644 (file)
@@ -131,6 +131,11 @@ sub _build_body_data {
     }
 }
 
+has _use_hash_multivalue => (
+    is=>'ro', 
+    required=>1, 
+    default=> sub {0});
+
 # Amount of data to read from input on each pass
 our $CHUNKSIZE = 64 * 1024;
 
@@ -212,6 +217,10 @@ sub _build_parameters {
         Hash::MultiValue->new($query->flatten, $body->flatten);
     };
 
+    if($self->_use_hash_multivalue) {
+        return $self->env->{'plack.request.merged'}->clone; # We want a copy, in case your App is evil
+    }
+
     # We copy, no references
     foreach my $name (keys %$query_parameters) {
         my $param = $query_parameters->{$name};
@@ -325,7 +334,9 @@ sub prepare_body_parameters {
     $self->prepare_body if ! $self->_has_body;
     return {} unless $self->_body;
 
-    return $self->_body->param;
+    return $self->_use_hash_multivalue ?
+        $self->env->{'plack.request.body'}->clone :
+        $self->_body->param;
 }
 
 sub prepare_connection {
index def2832..31f486d 100644 (file)
@@ -5,6 +5,7 @@ use Catalyst;
 
 __PACKAGE__->config(
   'Controller::Root', { namespace => '' },
+  use_hash_multivalue_in_request => 1,
 );
 
 __PACKAGE__->setup;
index 71dc283..da2a292 100644 (file)
@@ -34,4 +34,23 @@ use Catalyst::Test 'TestFromPSGI';
     'expected content body /from_psgi_code_itr';
 }
 
+{
+  ok my($res, $c) = ctx_request(POST '/test_psgi_keys?a=1&b=2', [c=>3,d=>4]);
+
+  ok $c->req->env->{"psgix.input.buffered"}, "input is buffered";
+  ok $c->req->env->{"plack.request.http.body"};
+  ok my $body = $c->req->env->{"plack.request.body"};
+  ok my $query = $c->req->env->{"plack.request.query"};
+  ok my $merged = $c->req->env->{"plack.request.merged"};
+
+  is $body->get('c'), 3;
+  is $body->get('d'), 4;
+  is $query->get('a'), 1;
+  is $query->get('b'), 2;
+  is $merged->get('c'), 3;
+  is $merged->get('d'), 4;
+  is $merged->get('a'), 1;
+  is $merged->get('b'), 2;
+}
+
 done_testing;