File Coverage

blib/lib/CatalystX/RequestModel/ContentBodyParser/FormURLEncoded.pm
Criterion Covered Total %
statement 32 32 100.0
branch 10 12 83.3
condition 4 6 66.6
subroutine 7 7 100.0
pod 0 4 0.0
total 53 61 86.8


line stmt bran cond sub pod time code
1             package CatalystX::RequestModel::ContentBodyParser::FormURLEncoded;
2              
3 6     6   3223 use warnings;
  6         22  
  6         215  
4 6     6   134 use strict;
  6         21  
  6         168  
5 6     6   41 use base 'CatalystX::RequestModel::ContentBodyParser';
  6         13  
  6         3049  
6              
7 84     84 0 315 sub content_type { 'application/x-www-form-urlencoded' }
8              
9             sub default_attr_rules {
10 101     101 0 202 my ($self, $attr_rules) = @_;
11 101         567 return +{ flatten=>1, %$attr_rules };
12             }
13              
14             sub expand_cgi {
15 13     13 0 37 my ($self) = shift;
16             my $params = (($self->{ctx}->req->method eq 'GET') || ($self->{request_model}->get_content_in eq 'query')) ?
17             $self->{ctx}->req->query_parameters :
18 13 100 100     57 $self->{ctx}->req->body_parameters;
19              
20 13         892 my $data = +{};
21 13         75 foreach my $param (keys %$params) {
22 66         218 my (@segments) = split /\./, $param;
23 66         135 my $data_ref = \$data;
24 66         127 foreach my $segment (@segments) {
25 135 100       286 $$data_ref = {} unless defined $$data_ref;
26              
27 135         345 my ($prefix,$i) = ($segment =~m/^(.+)?\[(\d*)\]$/);
28 135 100       285 $segment = $prefix if defined $prefix;
29              
30 135 50       303 die "CGI param clash for $param=$_" unless ref $$data_ref eq 'HASH';
31 135         355 $data_ref = \($$data_ref->{$segment});
32 135 100       375 $data_ref = \($$data_ref->{$i}) if defined $i;
33             }
34 66 50       134 die "CGI param clash for $param value $params->{$param}" if defined $$data_ref;
35 66         190 $$data_ref = $params->{$param};
36             }
37              
38 13         101 return $data;
39             }
40              
41             sub new {
42 13     13 0 67 my ($class, %args) = @_;
43 13         46 my $self = bless \%args, $class;
44 13   33     126 $self->{context} ||= $self->expand_cgi;
45              
46 13         50 return $self;
47             }
48              
49             1;
50              
51             =head1 NAME
52              
53             CatalystX::RequestModel::ContentBodyParser::FormURLEncoded - Parse HTML Form POSTS
54              
55             =head1 SYNOPSIS
56              
57             TBD
58              
59             =head1 DESCRIPTION
60              
61             Given a flat list of HTML Form posted parameters will attempt to convert it to a hash of values,
62             with nested and arrays of nested values as needed. For example you can convert something like:
63              
64             .-------------------------------------+--------------------------------------.
65             | Parameter | Value |
66             +-------------------------------------+--------------------------------------+
67             | person.username | jjn |
68             | person.first_name [multiple] | 2, John |
69             | person.last_name | Napiorkowski |
70             | person.password | abc123 |
71             | person.password_confirmation | abc123 |
72             '-------------------------------------+--------------------------------------'
73              
74             Into:
75              
76             {
77             first_name => "John",
78             last_name => "Napiorkowski",
79             username => "jjn",
80             }
81              
82             Or:
83              
84             .-------------------------------------+--------------------------------------.
85             | Parameter | Value |
86             +-------------------------------------+--------------------------------------+
87             | person.first_name [multiple] | 2, John |
88             | person.last_name | Napiorkowski |
89             | person.person_roles[0]._nop | 1 |
90             | person.person_roles[1].role_id | 1 |
91             | person.person_roles[2].role_id | 2 |
92             | person.username | jjn |
93             '-------------------------------------+--------------------------------------'
94              
95             Into:
96              
97             {
98             first_name => "John",
99             last_name => "Napiorkowski",
100             username => "jjn",
101             person_roles => [
102             {
103             role_id => 1,
104             },
105             {
106             role_id => 2,
107             },
108             ],
109             }
110              
111             We define some settings described below to help you deal with some of the issues you find when trying
112             to parse HTML form posted body content. For now please see the test cases for more examples.
113              
114             =head1 VALUE PARSER CONFIGURATION
115              
116             This parser defines the following attribute properties which effect how a value is parsed.
117              
118             =head2 flatten
119              
120             If the value associated with a field is an array, flatten it to a single value. Its really a hack to deal
121             with HTML form POST and Query parameters since the way those formats work you can't be sure if a value is
122             flat or an array.
123              
124             =head2 always_array
125              
126             Similar to C<flatten> but opposite, it forces a value into an array even if there's just one value.
127              
128             B<NOTE>: The attribute property settings C<flatten> and C<always_array> are currently exclusive (only one of
129             the two will apply if you supply both. The C<always_array> property always takes precedence. At some point
130             in the future supplying both might generate an exception so its best not to do that. I'm only leaving it
131             allowed for now since I'm not sure there's a use case for both.
132              
133             =head1 INDEXING
134              
135             When POSTing deeply nested forms with repeated elements you can use a naming convention to indicate ordering:
136              
137             param[index]...
138              
139             For example:
140              
141             .-------------------------------------+--------------------------------------.
142             | Parameter | Value |
143             +-------------------------------------+--------------------------------------+
144             | person.person_roles[0]._nop | 1 |
145             | person.person_roles[1].role_id | 1 |
146             | person.person_roles[2].role_id | 2 |
147             | person.person_roles[].role_id | 3 |
148             '-------------------------------------+--------------------------------------'
149              
150             Could convert to:
151              
152             [
153             {
154             role_id => 1,
155             },
156             {
157             role_id => 2,
158             },
159             ]
160              
161             Please note the the index value is just used for ordering purposed, the actual value is tossed after its
162             used to do the sorting. Also if you just need to add a new item to the end of the indexed list you can use an
163             empty index '[]' as in the example above. You might find this useful if you are building HTML forms and need
164             to tack on an extra value but don't know the last index.
165              
166             =head1 HTML FORM POST ISSUES
167              
168             Many HTML From input controls don't make it easy to send a default value if they are left blank. For example
169             HTML checkboxes will not send a 'false' value if you leave them unchecked. To deal with this issue you can either
170             set a default attribute property or you can use a hidden field to send the 'unchecked' value and rely on the
171             flatten option to choose the correct value.
172              
173             You may also have this issue with indexed parameters if the indexed parameters are associated with a checkbox
174             or other control that sends no default value. In that case you can do the same thing, either set a default
175             empty arrayref as the value for the attribute or send a ignored indexed parameter (as in the above example '_nop').
176              
177             =head1 EXCEPTIONS
178              
179             See L<CatalystX::RequestModel::ContentBodyParser> for exceptions.
180              
181             =head1 AUTHOR
182              
183             See L<CatalystX::RequestModel>.
184            
185             =head1 COPYRIGHT
186            
187             See L<CatalystX::RequestModel>.
188              
189             =head1 LICENSE
190            
191             See L<CatalystX::RequestModel>.
192            
193             =cut