File Coverage

blib/lib/CGI/ParamComposite.pm
Criterion Covered Total %
statement 46 46 100.0
branch 11 12 91.6
condition 3 5 60.0
subroutine 10 10 100.0
pod 6 6 100.0
total 76 79 96.2


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             CGI::ParamComposite - Convert .-delimited CGI parameters to Perl classes/objects
4              
5             =head1 SYNOPSIS
6              
7             use CGI;
8             use CGI::ParamComposite;
9             my $q = CGI->new();
10             $q->param(-name=>'food.vegetable',-value=>['tomato','spinach']);
11             $q->param(-name=>'food.meat', -value=>['pork','beef','fish']);
12             $q->param(-name=>'food.meat.pork',-value=>'bacon');
13              
14             my $c = CGI::ParamComposite->new( cgi => $q );
15              
16             #Dumper([$composite->roots()]) returns (minor formatting):
17             $VAR1 = {
18             'food' => {
19             'meat' => [
20             'pork',
21             'beef',
22             'fish'
23             ],
24             'vegetable' => [
25             'tomato',
26             'spinach'
27             ]
28             }
29             };
30              
31             #either way, these calls now work:
32             my($market) = %{ $composite->param() };
33             ref($market); #returns HASH
34             keys(%{ $market->{food} }); #returns ('meat','vegetable')
35              
36             #note that food.meat.pork with throw an error b/c a higher level key, food.meat,
37             #has already had its value set. the keys are evaluated from least to most
38             #specific (measured by namespace depth, or number of dots)
39              
40             =head1 DESCRIPTION
41              
42             I needed this for a fairly large single-CGI script application that I was working on.
43             It was a script that had been actively, organically growing for 4+ years, and was
44             getting very difficult to track the undocumented 50+ CGI parameters that were being
45             passed, some of them dynamically generated, and almost all with very short names.
46              
47             I wanted a way to organize the parameters, to make it easier to set up some simple
48             guidelines for how to maintain parameters, and how to make sure they were accessable
49             in a consistent manner. I decided to use a hierarchical, dot-delimited convention
50             similar to what you seen in some programming languages. Now if I see a parameter
51             like:
52              
53             /my.cgi?navigation.instructions=1
54              
55             I can pretty quickly guess, after not looking at the code for days/weeks/months, that
56             this value is somehow affecting the instructions on the Gbrowse navigation page. In
57             my opinion, this is superior to:
58              
59             /my.cgi?ins=0
60              
61             which had the same effect in an earlier version of the code (negated logic :o).
62              
63             =head1 SEE ALSO
64              
65             L
66              
67             =head1 AUTHOR
68              
69             Allen Day, Eallenday@ucla.eduE
70              
71             =head1 COPYRIGHT AND LICENSE
72              
73             Copyright (C) 2004 by Allen Day
74              
75             This library is free software; you can redistribute it and/or modify
76             it under the same terms as Perl itself, either Perl version 5.8.3 or,
77             at your option, any later version of Perl 5 you may have available.
78              
79             =head1 METHODS
80              
81             =cut
82              
83             package CGI::ParamComposite;
84              
85 1     1   43494 use strict;
  1         2  
  1         38  
86 1     1   5 use CGI;
  1         2  
  1         7  
87 1     1   1055 use Data::Dumper;
  1         8448  
  1         93  
88 1     1   10 use constant DEBUG => 0;
  1         1  
  1         583  
89             our $VERSION = '0.02';
90              
91             my $self = undef;
92              
93             =head2 new()
94              
95             Usage : my $c = CGI::ParamComposite->new( populate => 1 , package => 'My::Param' );
96             my @roots = $c->roots(); #these are what you're after
97             Function: builds and returns a new CGI::ParamComposite object. calls L,
98             which is where all the action happens.
99             Returns : a CGI::ParamComposite instance
100             Args : all optional:
101             cgi - a CGI object from which params() are retrieved.
102             populate - should the objects returned by L be fleshed out?
103             defaults to false, this is fastest.
104             package - prefix to attach to new symbols. see L for
105             details.
106              
107             =cut
108              
109             sub new {
110 3     3 1 6175 my($class,%arg) = @_;
111 3 100       17 return $self if defined($self);
112              
113 1         3 $self = bless {}, $class;
114 1         6 $self->init(%arg);
115 1         4 return $self;
116             }
117              
118             =head2 init()
119              
120             Usage : $obj->init(%arg);
121             Function: initializes a CGI::ParamComposite object. this includes
122             registration of new packages, package constructors, and
123             package accessors into the Perl symbol table.
124             Returns : true on success.
125             Args : none. this is an internal method called by L.
126              
127              
128             =cut
129              
130             sub init {
131 1     1 1 3 my($self,%arg) = @_;
132              
133 1   33     8 $self->cgi($arg{cgi} || new CGI);
134              
135 1 50       2 return unless $self->cgi->param();
136              
137 1         17 my %result = ();
138              
139 1         5 foreach my $p (sort {depth($a) <=> depth($b)} $self->cgi->param()){
  3         22  
140 3         9 my @path = split '\.', $p;
141              
142 3         7 my @val = $self->cgi->param($p);
143              
144 3         55 follow(\@val,\%result,\@path,@path);
145             }
146              
147 1         6 $self->param(\%result);
148              
149             }
150              
151             =head1 ACCESSORS
152              
153             =head2 cgi()
154              
155             Usage : $obj->cgi($newval)
156             Function: holds a CGI instance. this is instantiated by L,
157             if you don't provide a value.
158             Returns : value of cgi (a CGI object)
159             Args : on set, new value (a scalar or undef, optional)
160              
161              
162             =cut
163              
164             sub cgi {
165 6     6 1 8 my($self,$val) = @_;
166 6 100       18 $self->{'cgi'} = $val if defined($val);
167 6         16 return $self->{'cgi'};
168             }
169              
170             =head2 param()
171              
172             Usage : $hashref = $obj->param($newval)
173             Function: get a hahsref of the treeified CGI parameters
174             Returns : a hashref
175             Args : none
176              
177              
178             =cut
179              
180             sub param {
181 5     5 1 8 my($self,$val) = @_;
182 5 100       12 $self->{'param'} = $val if defined($val);
183 5         18 return $self->{'param'};
184             }
185              
186              
187             =head1 INTERNAL METHODS
188              
189             You donn't need to touch these.
190              
191             =head2 depth()
192              
193             Usage : internal method, used for sorting CGI params based
194             on the depth of their namespace. this makes sure
195             the created symbols return the right thing (child
196             objects or simple scalars)
197              
198             =cut
199              
200             sub depth {
201 6     6 1 7 my $string = shift;
202 6         14 my @parts = split '\.', $string;
203 6         17 return scalar(@parts);
204             }
205              
206             =head2 follow()
207              
208             Usage : $obj->follow($value,$hashref,@path);
209             Function: internal method. recurses into $hashref foreach element of
210             @path, and sets the value of $path[-1] to $value. for
211             example:
212              
213             @path = qw(foo bar baz);
214             $value = 'boo';
215             $result = {};
216             follow($value,$result,@path);
217             $result->{foo}->{bar}->{baz}; #evaluates as 'boo'
218              
219             Returns : n/a
220             Args : 1. value to set
221             2. hash to assign value into
222             3. an array defining location of value in hash
223              
224              
225             =cut
226              
227             sub follow {
228 7     7 1 15 my($v,$r,$p,@path) = @_;
229 7         9 my $next = shift @path;
230 7 100       12 if(@path) {
231 4   100     14 $r->{$next} ||= {};
232 4         15 follow($v,$r->{$next},$p,@path);
233             } else {
234 3 100       7 if(ref($r) eq 'HASH'){
235 2         11 $r->{$next} = $v;
236             } else {
237 1         3 my @q = @$p;
238 1         2 pop @q;
239 1         25 warn sprintf("ignoring %s=%s, value of %s already set to %s",
240             join('.',(@$p)),$v,
241             join('.',(@q)), $r
242             );
243             }
244             }
245             }
246              
247             1;