"Shade検索"Wiki

perlからPHPへ書き換えメモ

ここで公開しているCGIではありませんが、perlで書いていたCGIプログラムを
PHPしか使用できないWEBサーバーで動かす必要に迫られ、急遽perlからPHPへ書き換えを行いました。
PHPはこれまで全く経験がなく、WEB上の情報だけを頼りになんとか書き換えが終わりました。
せっかくPHPを体験したので、今後はPHPも書いてみようかなと思いつつ、備忘録をまとめてみました。
主にperlから移行する場合のメモと言う感じになっています。

変数名に注意


perlの場合は、変数のタイプによって頭につける文字が違います。
スカラー変数は$、配列は@、ハッシュは%というように。
ところがPHPの場合は、スカラーも配列も変数にはすべて$をつけることになっています。
このため、一見しただけでは変数に何が保存されているのかわかりにくいという問題があります。

また、perlでは、変数のタイプが違うと、同じ名前をつけることができます。
たとえば、$data、@data、%dataを同時に別の変数として扱えます。
ウチで書いているCGIの場合、スカラーや配列、ハッシュで同じ変数名を使ってしまっているというケースがかなりあります。
この状態でPHPに書き換えて、単純に配列変数もスカラー変数も頭に$だからと書き換えてしまうと、
どれがスカラーだったか、配列だったかわけが分からなくなってしまいます。

perlからPHPへの書き換えで、変数名は変えたくないという場合でも、最低限これは重複しないようにしておく必要があります。
変数のタイプによって命名のルールを作っておくといいかもしれません。
たとえば配列を入れる変数の場合は、変数名末尾に_listをつけ、ハッシュの場合は*1末尾に_hashをつけるとか。
$data $data_list $data_hash

これと関連して、PHPではperlのmy宣言のような、ローカル使用限定の宣言というものがありません。
このため、後で使用したい変数名をありがちなものにしてしまうと、
どこかで別の用途に同じ変数名を使ってしまってエラーになるという危険も考えられます。
こうしたことからも、変数名はperl以上にきちんと管理しておく必要があるといえます。

perlとPHPの違うところ


perlとPHP、似ている部分も多いんですが、もちろん違うところもたくさんあります。
ざっとピックアップしてみると

perl php
elsifとelseif elsif else if
ファイル、ディレクトリが存在するかどうか -e $path file_exists($path)
ファイルかどうか -f $path is_file($path)
ディレクトリかどうか -d $path is_dir($path)
ループをスキップ next continue
ループを終了 last break
配列を初期化 @data = (); $data = array();
ファイルのサイズ stat($path))[7] filesize($path)
ファイルの更新時間 (stat($path))[9] filemtime($path)
変数が定義済みか defined($data) isset($data)

perlではdefinedなんてほとんど使ったことがないのですが、PHPではissetは頻繁に使うことになりました。
というのは、perlでは未定義の変数をいきなり参照しようとしても、Falseになるだけですが、PHPの場合はエラーになってしまいます。
$dataが未定義の時、perlの場合

if ($data) {

と書いても問題ありませんが、PHPの場合は$dataは未定義ですというようなエラー文が出ます。
エラーが出ないようにするには

if (isset($data) && $data) {

のように書かなければなりません。issetの場合は$dataが未定義でもFalseを返すだけでエラーにはなりません。
これは配列や連想配列の場合でも同じで、連想配列$data_hashは定義済みでも、
$data_hash['sheck']が未定義だとエラーになります。
配列や連想配列を参照する場合も

if (isset($data_hash['sheck']) && $data_hash['sheck']) {

などとする必要がある場合があります。

ウチの場合、perlではエラーにならないのをいいことに、
未定義の変数を参照するようなケースが結構あるので、大量にissetのお世話になることになります。
最初からPHPで書く場合は、変数は最初に初期化をちゃんとしておくようにした方がいいのかもしれません。

さらに違うところをピックアップ。

perl php
ファイルを開いて内容を配列に保存 open(DATA,$path);
@data = <DATA>;
close(DATA);
$data_list = file($path);

この作業はPHPでは、簡単に書けます。
オプションでFILE_IGNORE_NEW_LINESをつけると行末の改行文字が削除され、
FILE_SKIP_EMPTY_LINESをつけると、空の行を読み込みません。

perl php
ファイルに書き込む open(DAT,">$path");
print DAT @data;
close(DAT);
file_put_contents($path, $data_list, LOCK_EX);

これも、PHPでは簡単に書けます。LOCK_EXは書き込み中にファイルをロックするオプションです。

perl php
配列から重複した要素を削除 %seen = ();
@data = grep { ! $seen{$_} ++ } @data;
$data_list = array_unique($data_list);

これは1行でできる便利な関数があります。

perl php
文字列を区切り文字で区切って配列にする split(/<>/,$data) explode('<>',$data)
文字列を区切り文字で区切って配列にする split(/\d{4}/,$data) preg_split('/\d{4}/',$data)

普通に文字列で分割する場合はexplodeを使うらしいです。
preg_splitでもできますが、正規表現を使い、処理が少し遅いらしいので、正規表現が必要ない場合はexplodeの方がいいということです。
PHPで正規表現を使う場合は、//で囲まれたパターンをさらに''(シングルクォーテーション)で囲ってやる必要があります。

perl php
複数の文字列を指定文字を介して連結 $str = join('<>',$str1,$str2); $str = join('<>',array($str1,$str2));

ほぼ同じですが、PHPの場合は第2引数として明示的に配列を指定する必要があります。

perl php
配列の各要素について処理 foreach $data (@data) { foreach ($data_list as $data) {

as と続けて配列から取り出す要素名を指定します。
perlでは$dataを省略すると$_に格納されますが、PHPではas以降は省略できないようです。
そして、配列と要素で同じ変数名は使えないので、配列を保存した変数は$data_listなどと
別の名前にしておく必要があります。

perl php
ハッシュの各キーについて処理 foreach $key (keys %data) $keys = array_keys($data_hash);
foreach ($keys as $key) {

PHPの場合、array_keysであらかじめキーを取得する必要があります。

foreach (array_keys($data_hash) as $key) {

とまとめて書いてもOKです。

foreach ($data_hash as $_) {

とすると、$_はキーではなく、値になります。
キーと値を同時に処理したい場合は、

foreach ($data_hash as $key => $value) {

とすると、$keyにキーが、$valueに値が入ります。

perl php
文字列が含まれるかどうか if ($str =~ /str/) { if (strpos($str,'str')) {
if (strstr($str,'str')) {
文字列が含まれるかどうか(正規表現) if ($str =~ /\d{1,3}$/) { if preg_match('/\d{1,3}$/',$str) {
文字列が含まれるかどうか
(正規表現、マッチ部分を後で使用)
$str =~ /[a-z]+(\d{1,3}$)/;
$dig = $1;
preg_match('/[a-z]+(\d{1,3}$)/',$str,$match);
$dig = $match[1];

指定した文字列が含まれるかどうかの判定ですが、これも単純な文字列なら
strposやstrstrの方が高速、正規表現を使うならpreg_matchということです。
マッチした部分を後で使用する場合は、preg_matchに第3引数を指定すると、
マッチ部分が配列に格納されます。
パターン全体にマッチするのが$match[0]、
最初の()内にマッチするのが、$match[1]ということになります。

perl php
文字列の置換 $str =~ s/</&lt;/g; $str = str_replace('<','&lt;',$str);
文字列の置換 $str =~ s/(^[a-z]{4})/〜$1〜/g; $str = preg_replace('/(^[a-z]{4})/','〜$1〜',$str);

str_replaceの方が速いので、正規表現を使わない場合は、str_replaceを使った方がいいということです。
正規表現を使う場合は、perlの場合と同じように、
パターン全体にマッチした部分が$0、最初の()内にマッチした部分が$1、2番目
の()内にマッチした部分が$2に格納されます。

perl php
配列に要素を追加 push(@new_data,$new_data); array_push($new_data_list,$new_data);
エポック時間から日付、時間を得る ($sec, $min, $hour, $mday, $mon, $year,$wday, $yday, $isdst)=localtime(time);
$month = $mon+1;
$year += 1900;
$tm = sprintf("%04d年%02d月%02d日%02d:%02d", $year, $month, $mday,$hour, $min);
$tm = date("Y年m月d日 H:i",time());

これはPHPの方が圧倒的に簡単。
dateを使えば、月に1足したり、年に1900足したりする必要なく、フォーマット
済みの日付が得られます。

perl php
日付からエポック時間を得る use Time::Local 'timelocal';
$epc = timelocal($sec, $min, $hour, $mday, ($month - 1), ($year - 1900));
$epc = mktime($sec, $min, $hour, $mday, $month, $year);

これもPHPの方が簡単。Time::Localモジュールを宣言する必要もなく、
月から1引いたり、年から1900引く必要もありません。

エディタの正規表現置換で一気に書き換え


実際にperlからPHPへの書き換えを行うと、上記のような違いを書き換えるということになりますが、多くは単純作業の繰り返しです。
長いプログラムだと、相当何カ所も書き換えなければならないので、なるべく楽にできないか考えてみました。
まずエディタの置換機能を使って一気に書き換えるという方法が考えられます。
いつも使っているサクラエディタには正規表現による検索、置換機能があるので、これを使うと多少込み入った書き換えもできます。
たとえば、ハッシュから配列への変換は置換前を

(\$[^ $!(){]+?)\{(.+?)\}

置換後を

$1[$2]

というパターンにします。これで置換を実行すると、

my $line = qq(\$in{') . $_ . qq('}='$in{"$_"}';\n);

my $line = qq(\$in[') . $_ . qq(']='$in["$_"]';\n);

と書き換えられます。もちろん置換するときはオプションの「正規表現」をチェックしておきます。

たとえば、以下のような置換が可能です。上の行が置換前、下の行が置換後の正規表現パターンです。

my除去
(\s*)my\s+([\$@%])
$1$2


ハッシュから連想配列へ
(\$[^ $!(){]+?)\{(.+?)\}
$1[$2]


行末のifを行頭へ
(\s*)((.*)?) if (.+);
$1if ($4) { $2; }


subをfunctionに
^sub ([^ ]+)
function $1()


正規表現のマッチを変換
(\$[^&\|\=]+) =~ (\/(.*)?\/)
preg_match(\'/$3/\',$1)


正規表現の置換を変換(複数回)
(\$.+) =~ s(\/(.*)?\/)([^\/]*)\/i?m?s?g
$1 = preg_replace(\'$2\',\'$4\',$1)


正規表現の置換を変換(1回のみ)
(\$.+) =~ s(\/(.*)?\/)([^\/]*)\/i?m?s?[^g]
$1 = preg_replace(\'$2\',\'$4\',$1,1)


関数の呼び出し変更
(^[^\/]*)&(?!amp;|lt;|gt;|quot;|nbsp;)([^&\(\);="+#]+)(\(([^\)]*)\))?([^\/]*$)
$1$2($4)$5

これは、&amp;や&lt;を関数呼び出しと勘違いしないようにするために、けっこう長めのパターンになってしまいました。

foreach文変更(要素名指定)
foreach\s+(my )?(\$[^ ]+)\s*\(@([^\)]+)\)\s*\{
foreach (\$$3 as $2) {


foreach文変更(要素名省略)
foreach\s*\(@([^\)]+)\)\s*\{
foreach (\$$1 as $_) {

perlでは

foreach (@data) {

と省略して書いた場合、$_
で各要素を参照するので、ループ内はそれ前提で書かれています。
このため、PHPにした場合、変数名$_を変更しなくていいように

foreach ($data as $_) {

に変更します。

と、ここまでやってきましたが、実のところウチで一番書き換えが大変なのは、qq()です。
これは""と同じで、perlでも""は使えますが、""内の"\"とエスケープしなければならないのが面倒なので、
ウチでは""を使うべき所はほとんどqq()を使用しています。 PHPではqq()は使えないので、
""に変更した上で、中の"をエスケープしなければなりません。
これも正規表現置換で何とかならないかやってみましたが、どうもうまくいきません。
qq()から""への置換は何とかなるにしても、何カ所あるかわからない"のエスケープに対応する正規表現パターンがよくわかりませんでした。

そこで次に考えたのが、クォートの書き換えをPHPでやってしまおうという方法です。

というわけでPHP/perlからPHPへ書き換え補助PHPへ続く



1408


>>2014/11/17 14:15:38更新>>