bonar note

京都のエンジニア bonar の技術的なことや技術的でない日常のブログです。

[perl] URI::QueryParam の query_form_hash でappendしたい

Webアプリケーションなどで LWP で REST API を叩く場合に、動的にURLを作ったりする事がよくあります(他にも色々ありますよね)。今までは sprintf 等でシコシコ文字列を作っていたりしたのですが、 URI::QueryParam というモジュールが便利だと id:boxphere さんから教わって今はそのモジュールを使っています。

#!/usr/bin/perl

use strict;
use warnings;

use URI;
use URI::QueryParam;

my $uri1 = URI->new('http://bonar.jp');
$uri1->query_param(foo => 'val_foo');
$uri1->query_param(bar => 'val_bar');

print "$uri1\n"; # http://bonar.jp?foo=val_foo&bar=val_bar

sprintf('%s/hoge.pl?foo=%s&bar=%s', $uri, @data) みたいにやるよりずっと読みやすくていいですね。ただ、query_param だと1個ずつしか指定できないですが、query_form_hash を使うとhashで一気にセットできます。

my $uri2 = URI->new('http://bonar.jp');
$uri2->query_form_hash(
    foo => 'val_foo',
    bar => 'val_bar',
);

print "$uri2\n"; # http://bonar.jp?foo=val_foo&bar=val_bar

見た目にもすごくわかりやすいですよね。ただ、このメソッドはちょっと癖があって、hashでセットするとそのhashで内部のパラーメタが置き換えられてしまい、以前にセットしたものが消えてしまいます。

my $uri3 = URI->new('http://bonar.jp');
$uri3->query_form_hash(
    foo => 'val_foo',
    bar => 'val_bar',
);
$uri3->query_form_hash(
    hoge => 'val_hoge',
    buzz => 'val_buzz',
);

print "$uri3\n"; # http://bonar.jp?hoge=val_hoge&buzz=val_buzz

中のソースを見るとこんな感じです。

sub URI::_query::query_form_hash {
    my $self = shift;
    my @old = $self->query_form;
    if (@_) {
        $self->query_form(@_ == 1 ? %{shift(@_)} : @_);
    }
    my %hash;
    while (my($k, $v) = splice(@old, 0, 2)) {
        if (exists $hash{$k}) {
            for ($hash{$k}) {
                $_ = [$_] unless ref($_) eq "ARRAY";
                push(@$_, $v);
            }
        }
        else {
            $hash{$k} = $v;
        }
    }
    return \%hash;
}

$self->query_form(@_ == 1 ? %{shift(@_)} : @_); の部分がまさにそれですね。

何かのURIを扱うモジュール等で、オブジェクト生成時に基本となるパラメータをセットしておいて、呼び出し元でいくつかの個別のパラメータをappendした、みたいなよくありがちな局面で query_form_hash が使いづらかったりします。

今あるものに加えて新しいhashを追加するような関数があるといいのになあ。例えばこんなショートカットとか。

sub URI::query_form_hash_append {
    my $self = shift;
    return $self->query_form_hash(%{ $self->query_form_hash }, @_);
}

my $uri4 = URI->new('http://bonar.jp');
$uri4->query_form_hash_append(
    foo => 'val_foo',
    bar => 'val_bar',
);
$uri4->query_form_hash_append(
    hoge => 'val_hoge',
    buzz => 'val_buzz',
);

print "$uri4\n"; # http://bonar.jp?bar=val_bar&foo=val_foo&hoge=val_hoge&buzz=val_buzz

こういう風に呼び出せると理想的。僕だけですかね。

query_param_append($key, @val) というそれっぽいメソッドがあるのですが、これは用途がまったく違って、$key=$val[0]&$key\$val[1]&$key=$val[2].... というパラメータが生成されます。こういう操作ってどういう需要があるんだろう。。

URI はパラメータの順番を保持する事を考慮した実装になっているみたいですが、これって何のためなんですかね。ベースとなるパスの部分とパラメータのhash、というシンプルな構造でURIを表現するモジュールってないのかなあ。