PHP Perl 2016年12月21日 14:36   編集
perlで書いていたCGIを、PHPしか動かないサーバーで使う必要に迫られ、PHPに書き換えた。その際の注意すべき点をまとめた。

変数

perlの場合は、変数のタイプによって頭につける文字が違う。スカラー変数は$、配列は@、ハッシュは%というように。一方PHPの場合は、すべて$をつけることになるので、一見しただけでは変数に何が保存されているのかわかりにくいという問題がある。
また、perlでは、変数のタイプが違うと、同じ名前をつけることができる。たとえば、$data@data%dataを同時に別の変数として扱える。PHPではこれはできないので、perlからPHPへの移行の際には、まずこれに注意が必要だ。
perlからPHPへ書き換える場合、変数名はそのまま使うという場合は、変数のタイプによって命名のルールを作っておくといいかもしれない。たとえば配列を入れる変数の場合は、変数名末尾に_listをつけ、ハッシュの場合は末尾に_hashをつけるとか。$data $data_list $data_hash

PHPへの書き換えの前、配列やハッシュの変数名をこのルールに基づいて変換しておくと、あとでスカラー変数と配列変数がごっちゃになって大変なことにならずに済むかもしれない。
これと関連して、PHPではperlのmy宣言のような、ローカル使用限定の宣言というものがない。
このため、後で使用したい変数名をありがちなものにしてしまうと、どこかで別の用途に同じ変数名を使ってしまって消えたり値が変わってしまうというケースもある。こうしたことからも、変数名はきちんと管理しておく必要がある。

perlからPHPへ

perl php
ファイルに値が存在するかどうか if ($data) { if (isset($data) && $data) {
perlからPHPへ移行する際、一番多く使うのがこのissetかもしれない。
PHPでは、未定義の変数をいきなり参照しようとするとエラーになる。
このため、未定義の値を参照しようとする場合は、定義済みかどうかをチェックするissetを使って
isset($data)
を追加しておく必要がある。
これは配列や連想配列の場合でも同じで、連想配列$data_hashは定義済みでも、$data_hash['sheck']が未定義だとエラーになる。
配列や連想配列を参照する場合も
if (isset($data_hash['sheck']) && $data_hash['sheck']) {
などとする必要がある場合がある。
perl php
文字列はクォーテーションで囲む qq() ""
perlでもダブルクォーテーションは使えるが、HTMLの記述などで文字列内にダブルクォーテーションがある場合、エスケープするのが面倒なので、ウチではqq()を使う場合が多いが、PHPでは使用できない。
このため、文字列は基本的にダブルクォーテーションで囲って、中のダブルクォーテーションは地道にエスケープするしかない。
サクラエディタの正規表現を使った置換で一気に変換しようとしても、うまくいかない。
qq()から""への変換ぐらいはできるが、qq()内のダブルクォーテーションのエスケープが難しい。
サクラエディタの置換で何とかなりそうなのは、以下。
サクラエディタの「置換」のオプションで「正規表現」をチェックして使用する
置換前置換後
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;)([^ &\(\);="+#]+)(\Plugin not found.?([^\/]*$) $1$2($4)$5

さらにPerlとPHPの違いは...
perl php
ハッシュを配列変数に $in{'data'} $in['data']
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))[9] filemtime($path)
ファイルのサイズ (stat($path))[7] filesize($path)
ファイルを開いて内容を配列に保存 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などと別の名前にしておく必要がある。
比較演算子
== → == か === (===は値が同じで、なおかつ型が同じかどうか判定。==は型を相互変換した上で値が同じか判定)
!= != か !== (!==は値が等しくないか、型が違う場合にTrue。!=は型を相互変換した上で値が違う場合True)
eq ===
ne !==
perl php
ハッシュの各キーについて処理 foreach $key (keys %data) foreach (array_keys($data_hash) as $key)
PHPの場合、array_keysでキーを取得できる。
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の方が高速らしい。
正規表現を使う場合は、perlの場合と同じように、パターン全体にマッチした部分が$0、最初の()内にマッチした部分が$1、2番目の()内にマッチした部分が$2に格納される。
perl php
配列に要素を追加 push(@new_data,$new_data); array_push($new_data_list,$new_data);
$new_data_list[] = $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引く必要もない。

...と長々と説明したが、このような変更をひとつずつやるのが面倒ということで書いたのが
PHP/PerlからPHPへ書き換え補助PHP
counter:6,559