2010.06.23
ジョン・パリス著

RPGの観点からみたPHP-パート3:PHP関数

PHP関数の概要およびRPGのサブ・プロシージャとの比較

PHPにおける「関数」という用語は、RPGで言うと少し異なる二つのものを指しています。一つは%XLATEや%SUBSTのような組み込み関数(BIF: built-in function)で、もう一つはサブ・プロシージャです。PHPでは、これらのことをそれぞれ組み込み関数、およびユーザー定義関数と言います。PHPでは、RPGと違ってこれらの二つを厳密には区別しておらず、「%」のような両者を区別するような記号あるいはそれに準ずるものがないので、見た目だけではどちらなのかはわかりません。

関数を呼び出すための基本的な構文はRPGの関数呼び出しと同様で、関数名の後の括弧の中にパラメータを記述します。RPGとPHPの関数の大きな違いは、PHPではパラメータをコロンではなくカンマで区切ります(つまり、PHPではFunctionName(parm1, parm2, ... parmN)のように記述しますが、RPGではFunctionName(parm1: parm2: ... parmN)と記述します)。

PHPの関数はRPGの対応する関数と同じで、変数に直接割り当てたり、式の中で評価したりできる値を返すことが可能です。実際、一つ慣れていただく必要があるのは、PHPのプログラマがよく、関数の実行と関数の結果の評価を結合することがある点です。詳細については「真実性とは何か?」を参照してください。

ユーザー定義関数を記述する

本シリーズでは、いくつかのPHPの組み込み関数、特に配列関連の関数をいくつか紹介してきました(4月号の記事「RPGの観点からみたPHP、パート2:ブラウザPHP関数」を参照してください)。さて、自分だけの関数を記述するための基礎について説明しましょう。PHPでは、関数は「function」というキーワードに続いて関数名とパラメータの詳細を記述します。関数名は変数名と同じ基本的な規則に従いますが、「$」記号で始まることはありません。 (「$」記号は厳密にいうと名前の一部分ではなく、それ以後の名前が変数であることを区別しているだけです。また、関数名は変数名と異なり、大文字と小文字を区別しません。PHP自体の組み込み関数や演算子が大文字と小文字を区別していないことを考えると、これはつじつまが合います。PHPのドキュメントを読んでいると、RPGの世界では「パラメータ」という単語を使うところで、PHPでは「引数」という用語が頻繁に使われているのにお気づきになるでしょう。この意味論について話すつもりはありませんが(異なる文脈では両方とも正しいのです)、「パラメータ」という単語はRPGの世界でもっともなじみのある単語なので、ここではこれを使用します。

PHP関数のスケルトンを以下に示します。

function MyFunction($parm1, $parm2, ... $parmN ) {
  // Function logic goes here

  // optional return($returnValue)
}

スケルトン中のコメントに記述してある通り、return()関数は、RPGと同様でPHPでもオプションです。関数から値を返す必要がある場合のみに必須となります。図―1に示す例は、私の名前を出力する以外にはたいした事をしていない簡単な関数を使用していますが、この関数が実際にどのように動作するかわかるでしょう。

図―1のAで、この関数が起動されます。RPGの場合と同様で、PHPはパラメータがない時は空の括弧を使用します。関数の本体は、図―1のBで始まります。条件操作と同じで、関数が起動された時に実行されるコードの本体は、括弧{および}で囲います(図―1のC)。この関数は値を返さないので、return()操作はコードに書かれていません。このスクリプトの実行結果は以下の通りです。

About to print name Result is Jon Paris Back in the main script

比較のために、この例と同じあるいは実際上できるだけ近いものをRPGで記述したのが以下です。

D PrintName    Pr
 /Free
 Dsply 'About to print name';
 Printname();
 Dsply 'Back in the main program';
 *InLR = *On;
 /End-Free

P PrintName    B
D PrintName    PI
 /Free
 Dsply 'Result is Jon Paris';
 /End-Free
P PrintName    E

ご覧の通り、一番の違いは、RPGでは必須となっている追加のスーパー構造にあります。PHPではプロトタイプは必要ありませんし、P-仕様の開始や終了も必要ではありません。そして、/Freeと/End-Freeディレクティブの組み合わせは言うまでもありません。また、ここでは例示されていないPHPとRPGの違いがもう一つあります。PHPの例(図―1)では、私が記述したように関数の定義がスクリプトの最後にあっても、あるいは最初にあっても違いはありません。それはスタイルの問題であり、個人的な好みの問題です。RPGでは選択の余地はなく、サブ・プロシージャはソースコードの最後に記述しなければなりません。

パラメータを関数に渡す

では図―2を見てください。ここでは、前述のPHP関数がパラメータを受け入れ、固定値ではなくそのパラメータの値を出力するように修正しました。この変更はとても簡単です。まず、関数の呼び出しでパラメータとして$nameを指定しました(図―2のA)。次に、関数自体の中でパラメータをfunction定義に追加し、$inputNameという名前を付けました(図―2のB)。この名前は次に関数の本体で参照されます(図―2のC)。同じ結果を得るためにRPG版の方で修正しなければならないことはたいしたものではありませんが、RPGの方が、多少手直しが多いでしょう。実際、まだご説明していない、PHP関数とRPGのサブ・プロシージャの大きな違いがもう一つあるのです。

それを説明するために、PHP版の修正内容と同じ結果をもたらすようにRPGのプロトタイプを(そしてもちろんプロシージャ・インタフェースも)どのように変更しなければならないのかを見てみましょう。

D PrintName    Pr
D  nameParm          30A  Varying Value

もちろん、最初の違いは、RPGではパラメータの長さとデータ・タイプを指定しなければならないことです。(ご存じの通り)これはPHPでは必要ありません。PHPでは動的にデータ・タイプが決まるからです。二番目の違いは、もっと大きな違いなのですが、PHPのパラメータはデフォルトでは値渡しであるのに対して、RPGのデフォルトは参照渡しになります。したがって、PHPのコードと同じ動作をするRPGのコードでは、パラメータの定義にVALUEキーワードを追加する必要があります。実際、PHPでは、RPGと同様にパラメータを参照渡しすることが可能ですが、デフォルトの動作ではないのです。参照渡しにする方法については後述します。

しかしまず、RPGにぜひ欲しいPHPの機能について説明しなければなりません。それは、渡されなかったパラメータのデフォルト値を指定する機能です。そのような値を指定するには以下のようにします。簡単にして上品なものです。すなわち、以下のように、functionの定義中でデフォルト値を代入として指定すれば良いのです。

function PrintName($inputName = "Default Value")

パラメータが渡されるとその値が使用されます。しかしパラメータが渡されない時は、提供されたデフォルト値が代わりに使用されます。関数の定義で他に変更する必要のある部分はありません。RPGのサブ・プロシージャをコーディングしている時、この機能があったら便利だろうと思ったことは数多くありました。

PHP関数から値を返す

これがどのように動作するのかを示すために、状態を表す2文字の略語をパラメータとして受け取り、それに対応する状態名を返す新しいPHP関数を紹介します。もしこれをRPGの関数で記述するとしたら、そのプロトタイプは以下のようなものとなるでしょう。

D StateName    Pr      20a
D  stateCode          2a

このPHP関数の初期版のコードは図―3にあります。状態名の連想配列($states)の定義が関数中にある(図―3のA)点に注意してください。したがって、この配列は関数ローカルなもので、これはRPGでも同様です。入力パラメータ($stateCode)は、return操作中でこの配列へのインデックスとして使用されているだけです(図―3のB)。これは単なる例なので、できるだけ簡単にするためにエラー・チェック(たとえばState Codeがリスト中にあることを確認する)はしていません。

この例はとても簡単で、データなどがどのように定義されているかを除けば、RPGバージョンと大きくは異なりません。

さて、不埒な理由で$states配列をPHPスクリプトのメイン・ボディー(グローバル・セクション)に移すことにしたとしましょう。明らかに不自然な例ですが、目的は果たせるでしょう。RPGでは、この移動は問題とはなりません。すべてのグローバル変数は、サブ・プロシージャ内から参照可能だからです。しかしPHPのデフォルトの規則では、関数からはローカルなデータのみが参照可能で、$states配列はStateName関数からは参照できないことになります。それでもなお、まったく無理だというわけではありません。PHPでの方法はグローバル変数への参照を許可するが、明確にそれを要求した時だけにする、というものです。

グローバル変数を参照する

上記の方法は面白いと思います。特にサブ・プロシージャを初めて作成する人に教える際に、グローバル変数への参照を禁止するオプションが、RPGのコンパイラ・オプションにあれば良いのにとよく思うからです。PHPでは、グローバル変数を参照したい場合は、そうしたいとPHPに対して明示的に伝えなければなりません。これの良い点は、PHPに対して明示的に伝えることで、このコードを見るすべてのプログラマに対して、これはグローバル変数であるということを伝えることにもなっていることです。命名法の慣例を使用してなんとかこの区別をしようとするRPG開発者をたくさん見てきましたので、PHPの方法は新鮮でした。

私が好んで使用する方法は、PHPのglobalキーワードです。このキーワードを使用すると、関数から参照可能であると指定された特定のグローバル変数を識別することができます。$states配列の定義をスクリプトのメイン・ボディ部分に移動したら、そこにglobal宣言を追加するだけでよいのです。修正後のスクリプトは以下のようになります。

function StateName($stateCode)
  {
  global $states; // Request access to global array

  return $states[$stateCode];
  }

  // Main script begins
$states = array('CA' => 'California', 'FL' => 'Florida',
           'NJ' => 'New Jersey', 'NY' => 'New York',
           'ON' => 'Ontario'); // States array now global
$state = 'CA';
$name = stateName($state);

echo "State Code $state is $name";

この結果、$states配列は、元の通り関数内から参照可能になりました。見た目は魅力的ではないのですが、グローバル変数を参照するたびにグローバル変数を使用することを明示的に記述するという方法にも利点があるので、その方法を好む方もいらっしゃるでしょう。この方法は、PHPのすべてのグローバル変数は$GLOBALS[]という配列に格納されている、という事実を利用しています。ですから$statesを直接参照する代わりに、$GLOBALS['states']と参照します。配列のインデックスに$文字を指定せず、大文字小文字の区別を正しく区別しなければならなかった点に注意してください(たとえば$GLOBALS['States']としたのではうまく動作しません)。修正版の関数の変形版を以下に示します。

function StateName($stateCode)
 {
  return $GLOBALS['states'][$stateCode];
 }

ご覧になってお分かりの通り、3行目のreturn操作が$GLOBALS配列を参照しています。配列のインデックスがどのように処理されているのかに注目してください。最初のサブ・スクリプトは参照したい変数の名前(states)でなければならず、また$states自体が配列であるため、二つ目のサブ・スクリプト($stateCode)を指定して正しい値へのインデックス付けをしなければなりません。四角括弧([])を連続して使用するのが、PHPで多次元配列を参照する方法です。PHPの多次元配列については本シリーズでいずれもっと詳しく説明しますが、現時点では、$GLOBALS内の配列を参照するにはこの構文で記述するのだと思ってください。

パラメータを参照渡しする

前述の通り、パラメータを参照渡しするのはRPGではデフォルトの動作で、ILEが登場するまでは、参照渡しがパラメータを渡す唯一の方法でした。今でもパラメータを値渡しするのは、プロシージャ呼び出しの場合だけです。プログラム呼び出しは依然として、参照渡ししなければなりません。パラメータが参照渡しされると、呼び出されたルーチンはパラメータの値(すなわち中身)を受け取るのではなく、元の変数が格納されている場所への「ポインタ」を受け取ります。ここで「ポインタ」と括弧で括ったのは、あるRPGプログラムが別のプログラムを呼び出す際に実際にポインタが使用されますが、PHPのようなスクリプト言語では実際に渡されるのは実際のアドレスではなく、その変数を参照するための何らかの内部参照が渡されます。とはいえ、パラメータを参照渡しする際に、そのメカニズムにかかわらず共通して言えることは次の通りです。パラメータが参照渡しされると、呼び出されたルーチンはその値を変えることができ、その変更はもちろん呼び出し側にも影響を与えます。これはあの古いRPG CALL/PARMと全く同じです。

これを説明するために、前述のStateName関数(図―3)を修正して受け取るパラメータを一つから二つに変え、return操作ではなく2番目のパラメータで結果を返すようにしました(図―4)。

図―4のCに修正後の関数呼び出しが示されています。変数$nameが関数呼び出しの結果を受け取りますので、代入操作は必要ないという点に注意してください。スクリプトの最大の変更点は、図―4のAをご覧になるとお分かりの通り、関数のインタフェースに2番目のパラメータを追加した点です。また、メインのスクリプトのフィールド($name)とは異なる名前($outName)を使用しましたので、関数が変更することのできる参照渡しのパラメータを渡しているということが確認できます。ではこの2番目のパラメータが参照渡しであると指定するにはどうすればよいのでしょうか。パラメータ名の前にアンパーサンド(&)を付ければよいのです。必要なのはたったこれだけです。関数内では(図―4のB)、関連する$states配列要素は変数$outNameに割り当てられ、その新しい値が出力されます(図―4のD)。

以上です

PHPのユーザー定義関数の導入部分の簡単な説明は以上です。今後の記事では、PHPの組み込み関数についてもう少し説明する予定です。特に、自分で関数を設計する際に興味がわきそうな組み込み関数について説明します。本シリーズが進むにつれて他の話題についても触れますが、宿題のディスカッションでも大いに触れます。

楽しんでください。

真実性とは何か?

このちょっと変わったタイトル(ただのバカげたものではありません)をお許しください。しかしこのタイトルは、ここで私が触れたかった問題をうまくまとめているタイトルなのです。すなわち、PHPの条件を評価するときにtrueとfalseを構成しているもののことです。これを理解する必要があるのは、PHPのプログラマがよく、RPGでは決して目にしない方法で条件を評価するからです。特に関数を使用しているときによく目にします。

RPGの世界はこの点においては単純です。単純な条件を評価するか(たとえばIf var1 = var2)、インジケーターのステータスを評価するか(たとえばIf *In90 = *OnやIf *IN90 = '1')です。実際に、インジケーターはブール式なのですでに分かっている方もいると思いますが、二つ目の評価はIf *In90と記述すればよりシンプルです。コンパイラは*In90がインジケーターであることはわかっていますから、それを適切に評価するだけです。逆に、インジケーターがoffであるか否かを知りたい場合は、単にIf Not *In90とコーディングします。

RPGが比較をどのように処理するのかを、なぜ学ぶのでしょうか。それはtrueやfalseを構成しているものに対して、PHPの方がRPGより柔軟な方法を採用しており、PHPのプログラマはこれをコード中でうまく利用していることが多いからです。PHPのブール式は、falseの値がゼロ(0)でtrueが1である点においてはRPGと全く同じです。しかしPHPは文字値も数字も受け取ります(すなわち、1と’1’は等価で0と’0’も等価です)。さらに、特殊な値trueとfalseを使用することもできます。ただし、PHPの方法はこのような些細な差をはるかに超えています。たとえば、PHPは空の配列要素であるヌル文字列や値が代入されていない変数をfalseとみなします。同様に、ゼロでない値や空でない文字列はtrueとみなされます。

PHPのふるまいがこのようになっているので、下記の例に示すような代入の評価がよく見られます。関数getAccountCode()が正当なアカウント・コードか空の文字列を返すとするなら、以下のようなコードとなります。

If ($accountCode = getAccountCode($transactionId)) {
  // Process valid code
} else {
  // Process error
}

コードの1行目では、関数を実行した結果が変数$accountCodeに代入され、この変数を評価した結果の値がtrueまたはfalse条件に合致するかを調べています。PHPではtrueやfalseを構成するものは「自由な」概念ですので、正当なアカウント番号はtrueと解釈され、ヌル文字列や空の文字列はfalseと解釈されます。(この考えを試してみたければ、図―A中の簡単なスクリプト例を参照ください。) これはRPGの観点からみるとちょっと異質なものです。RPGでは代入式と評価を一つの文中に混ぜることはできないので、これを二つの操作に分けなければなりません。RPGでは以下のようなコードになります。

$accountCode = getAccountCode($transactionId);

If ($accountCode <> '');
  // Process valid code
else;
  // Process error
endif;

一方の方法が他方より優れていると言っているのではありませんが、私の場合、PHPの簡略化されたスタイルを自分のプログラムで使用する機会が増えていると認めざるを得ません。PHPの世界では、代入式と評価を結合するこのテクニックはどこにでも使われていますから、これに慣れておくことをお勧めします。

条件式の話題について説明している途中ですが、PHPの世界でもう一つ慣れておく価値のあるちょっとした知恵があります。それは二つの変数が等しいというだけでなく、同一なものであるという概念です。たとえば次のPHPコードを見てください。

$x = 1;  // An integer
$y = '1'; // A string
If ($x == $y) echo '$x and $y match'; // This message will be output

$xは整数変数でそれと文字列を比較していますが、このメッセージは出力されます。なぜこうなるかと言うと、PHPが二つの変数を同じタイプに変換してから比較を実行するためです。その結果、両変数が一致するのです。しかし、値が一致するだけではなく、二つの変数が同じタイプであることも確かめたいとしましょう。もちろんこれらの各変数に対してgettype()関数を使用し、その評価の結果と値の比較とを結合することはできますが、それはとても見にくいコードになってしまいます。ここでPHPの同一演算子が救いの手を差し延べます。比較演算子にさらに=を追加すると(すなわちIf ($x === $y)となります)、PHPは、二つの変数が同じ値を持っており、同じデータ・タイプであること、すなわちこれらの変数が同一であることを確かめようとします。

この概念についてはもちろん宿題フォーラムでもう一度触れますので、そこにまたお越しください。

PHPの「サービス・プログラム」

今月の話題はユーザー定義関数でしたから、「サービス・プログラムに相当するものはPHPでは何ですか?」という質問が来ても不思議ではありません(実際にセミナーや会議でよく聞かれる質問です)。

単純な答えは、そのようなものはない、というものです。いずれにしろユーザー定義関数ではそのようなものはありません。ただし、PHPが解釈実行系言語であるということを忘れてはいけません。ですから、必要な関数を含んでいるソース・ファイル中に単純にコピーすれば、必要なのはそれだけです。もちろん「コピー」と言ったのは、SEUのCCブロックでするような文字通りのコピーのことではありません。RPGの/COPYの意味でのコピーのことで、別のソース・ファイルがコンパイル時に融合されます。PHPでいう「コンパイル時」とは実行時のことです。

PHPには、ソース・ファイルをコピーするのに使用するディレクティブが二つあります。requireとincludeです。両ディレクティブとも全く同じタスクを実行します。両者の唯一の差は、存在しないファイルの処理方法です。include を使用した場合で、ファイルが見つからない場合は警告が出ますが、スクリプトの実行は継続されます。一方requireを使用した場合、ファイルが存在しないと致命的なエラーになり、スクリプトの実行が終了します。ご想像の通り、ほとんどの場合で私が使用するのはrequire です。存在しない関数のために台無しになるだけの関数呼び出しであれば、スクリプトを実行する意味がありません。

スクリプトにいくつかの異なる関数群を入れておくと、その関数群が順番に共通のヘルパー関数を呼び出し、同じスクリプトを2度以上取り入れてしまった、ということがあるかもしれません。PHPはこれを、上記の二つのディレクティブの_once バリアントを提供することで処理します。したがって、ファイルが何度リクエストされても1度だけインクルードされることを確実にするには、require_onceかinclude_onceを使用します。

指定するファイル名はフルパス名を含んでいるので、これらの関数群はどこに格納してもかまいません。実際に、URLでもかまいません。RPGのデフォルトが/COPYのソース・メンバーをQRPGLESRCで探すのと同様に、PHPにも独自のデフォルトの規則があります。ファイル名にパス情報が含まれている場合は(たとえば/MyPHPFunctions/ValidationRoutines.php)、その位置だけが検索対象となります。しかしファイル名だけをコーディングした場合は(たとえばValidationRoutines.php)、php.iniファイル中のinclude_path設定で指定された位置から検索が始まります。ファイルがそこになければ、現在のスクリプトが読み込まれた場所を探します。

検索対象パスをスクリプトからもっとコントロールしたい場合は、set_include_path()関数を使用します。この関数は、スクリプト自身で検索パスを設定できますので、php.iniの設定に依存しません。検索パスを一度変更したら、あとでrestore_include_path()関数を使用して元のデフォルトに戻すことができます。

ご覧の通り、PHPは従来のILEの意味でのサービス・プログラムは使用しませんが、スクリプトに共通関数のライブラリを組み入れる独自の方法を備えています。

ページトップ

ボタン