The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Mojolicious::Plugin::ParamExpand;

use Mojo::Base 'Mojolicious::Plugin';
use CGI::Expand;

our $VERSION = '0.01';

sub register
{
  my ($self, $app, $config) = @_;
  my $class = 'Mojolicious::Plugin::ParamExpand::expander';

  _make_package($class, $config);

  $app->hook(before_dispatch => sub {
      my $c = shift;
      my $hash;
    
      eval { $hash = $class->expand_hash($c->req->params->to_hash) };
      if($@) {
	  $c->render_exception($@);
	  return;
      }

      $c->param($_ => $hash->{$_}) for keys %$hash;
  });
}

sub _make_package
{
    no strict 'refs';

    my ($class, $config) = @_;  
    @{"${class}::ISA"} = 'CGI::Expand';

    for(qw|max_array separator|) {
        my $val = $config->{$_};
        *{"${class}::$_"} = sub { $val } if defined $val;
    }
}

1;

__END__

=pod

=head1 NAME

Mojolicious::Plugin::ParamExpand - Use objects and data structures in your forms

=head1 SYNOPSIS

  # Mojolicious
  $self->plugin('ParamExpand', %options);
   
  # Mojolicious::Lite
  plugin 'ParamExpand', %options;

  # In your action
  sub action
  {
      my $self = shift;
      my $order = $self->param('order');
      $order->{address};
      $order->{items}->[0]->{id};
      $order->{items}->[0]->{price};
      # ...
  }

=head1 DESCRIPTION

L<Mojolicious::Plugin::ParamExpand> turns request parameters into nested data 
structures using L<CGI::Expand>.

=head1 MOJOLICIOUS VERSION

Due to the old way C<Mojolicious::Controller> handled multi-valued request parameters,
versions of Mojolicious B<less than> 2.52 will not work with this plugin. If this is a problem for
you try L<Mojolicious::Plugin::GroupedParams>.

=head1 OPTIONS

Options must be specified when loading the plugin.

=head2 separator

  $self->plugin('ParamExpand', separator => ',')

The character used to separate the data structure's hierarchy in the 
flattened parameter. Defaults to C<'.'>.

=head2 max_array

  $self->plugin('ParamExpand', max_array => 10)

Maximum number of array elements C<CGI::Expand> will create. 
Defaults to C<100>. If a parameter contains more than C<max_array> 
elements an exception will be raised.

To force the array into a hash keyed by its indexes set this to C<0>.

=head1 Methods

=head2 param

This is just L<Mojolicious::Controller/param> but, when using C<Mojolicious::Plugin::ParamExpand>, a
request with the parameters 

  users.0.name=nameA&users.1.name=nameB&id=123

will return a nested data structure for the param C<'users'>

  @users = $self->param('users');   
  $users[0]->{name};    	
  $users[1]->{name};   	

Other parameters can be accessed as usual

  $id = $self->param('id');

The flattened parameter name can also be used

  $name0 = $self->param('users.0.name');   

=head3 Arguments

C<$name>

The name of the parameter.

=head3 Returns

The value for the given parameter. If applicable it will be an expanded 
data structure. 

Top level arrays will be returned as arrays B<not> as array references. 
This is how C<Mojolicious> behaves. In other words

  users.0=userA&users.1=userB 

is equivlent to 

  users=userA&users=userB

If this is undesirable you could L<< set C<max_array> to zero|/max_array >>.
 
=head1 SEE ALSO

L<CGI::Expand>, L<Mojolicious::Plugin::GroupedParams>