hideden.hatenablog.com

はてなぶろぐー。URLなげー。

CGI.pmを継承せずにカスタマイズする方法?

Y.Sawaさんがまたd:id:succeed:20051114でアドバイスしていただいたのでそれをふまえて色々と。

CGI::ConvertParamはすごく汎用性があり素敵な実装だと思います。

use CGI;
use CGI::ConvertParam::UTF8;
my $org_cgi_q = new CGI;
my $q = CGI::ConvertParam::UTF8->new($org_cgi_q);
print $q->param('hoge');
my %query_data = $q->Vars;
print "$_: $query_data{$_}" foreach keys %query_data;

のようにした場合、$q->param('hoge')はConvertParamのメソッドが呼ばれるためコード変換されます。これは期待通りですね。
しかし、$q->Varsの場合はメソッドが定義されてないためAUTOLOADによりCGI.pmのインスタンス経由でCGI.pmのVarsを呼ぶことになります。これは以下のようにした場合と等価だと思います。

my %query_data = $org_cgi_q->Vars;

CGI::ConvertParam::UTF8のAUTOLOADを経由して遠回りにCGI.pmのインスタンスのVarsを呼んでるわけですから。

っという事は結果的に%query_dataからの値の取り出し時にはCGI.pmに定義されているFETCHメソッドによりCGI::ConvertParam::UTF8のparamとは全く無関係のCGI->paramが呼ばれるため文字コード変換は行われないと言うことになります。

そもそもこのモジュール自体がparamメソッドに変換機能を付け加える為のモノであり、Varsだと動作しないという挙動自体はおそらく作者の意図通りであると思います。

確かに継承してしまうのは良くないのだと思いますが、Varsを含めて委譲で実現するイイ方法をいまいち思いつきません。昨日書いたように自前でtie用のメソッドを用意し、それをそのまま乗っ取るのも絶対ダメって訳では無いのでしょうが、結果的にCGI.pmからソースをコピペしてきて少しだけいじると言う方向になるだろうので果てしなく微妙です。

って事で、コピペを避けるために一部分だけの継承ってのをしてみます。

package CGI_JP_conv;
use CGI qw/Vars TIEHASH DESTROY FETCH STORE DELETE EXISTS FIRSTKEY NEXTKEY/;
use strict;
use Jcode;

sub new {
    my $class = shift;
    my $self  = $class->SUPER::new(@_);
    return $self;
}

sub set_code {
    my $self = shift;
    my $code = shift;
    
    my $jcode = Jcode->new();
    $self->{'conv'} = sub { return @_; };

    if    ( $code eq 'sjis' ){ $self->{conv} = sub { return $jcode->set(@_)->sjis; }; }
    elsif ( $code eq 'euc'  ){ $self->{conv} = sub { return $jcode->set(@_)->euc;  }; }
    elsif ( $code eq 'jis'  ){ $self->{conv} = sub { return $jcode->set(@_)->jis;  }; }
    elsif ( $code eq 'utf8' ){ $self->{conv} = sub { return $jcode->set(@_)->utf8; }; }
}

sub param {
    my $self = shift;
    return $self->{'cgi'}->param(@_) unless( defined $self->{'conv'} );

    my @value = map { &{$self->{'conv'}}($_) } $self->{'cgi'}->param(@_);
    return wantarray ? @value : $value[0];
}

1;

さて、うまく行きません。一部分のメソッドだけ継承ってやり方知らないので適当にやってみたんですが、できるんでしょうか・・・?use base CGI qw//; とかやったら思いっきりエラーになったんで普通にuseしてみたわけですが。。。

そもそもCGI.pmの大部分のメソッドはAUTOLOADを使用して実装されており、Varsなどもその中の一つです。ざっと読んだ感じでは文字列として持っていて、最初に呼び出されたときにコンパイルしてる感じです。何のためなんだろう?機能が豊富なモジュールですから使わない機能をすべて定義するより使うものだけ定義する方が効率がいいのでしょうか。そのへん詳しくわかる方居ればご教授いただけると幸いです。

で、結局現時点での結論としてVarsでも文字コード変換を自動でやりたい!って場合で継承しない場合は、

  • 変換先文字コード設定時に、すべてのパラメータの値を変換してしまう
  • いったん委譲先のCGI.pmからtieされたハッシュを受け取り、それの中身を全部変換する
  • 自前でtieやVarsを実装し、CGI.pmがやってるのと同じような動作を真似る

なんか全部微妙ですね。途中で文字コードを変更したいとかいう要求にも耐えられるのは自前でtieでしょうが、結構面倒です。Vars以外にやっぱDumpもデバッグに使うし、同じページで文字コードが2種類になるとうぜぇからこれもきちんと変換したい!っとか言い出すとさらに面倒な事になります。なんせでかいですから。笑

結局の所、時には妥協も必要って事でしょうかね?


って事で、CGI.pmのソースを色々眺めてたら、importなるメソッドがあるようです。なんかこれを利用してうまく解決出来る方法があるような気がしないでもないので、機会があればこれに関しても色々遊んでみようと思います。

それにしても、なんか根本的に勘違いしてる気がしないでもないなぁ。。。