PHPでは処理の中ではしばしばインスタンスのメソッドを呼び出したい時があります。この時、対象のインスタンスのメソッドが存在することが約束された状況で呼び出したくなります。もし次のようなコードを実行した場合はエラーが発生しやりたかった処理ができません。
<?php
/** echoMsgメソッドを持たないクラス */
class NoMethod
{
}
/**
* echoMsgメソッドを持つクラスのインスタンスを受け取り、そのメソッドを呼び出す
* @param object $obj
* @return void
*/
function callEchoMsg(object $obj): void
{
// ここで echoMsg メソッドを読んで msg を echo して欲しい
$obj->echoMsg(); // PHP Fatal error: Uncaught Error: Call to undefined method NoMethod::echoMsg() in xxx.php:17
}
function main(): void
{
$obj = new NoMethod();
callEchoMsg($obj);
}
main();
こういった場合クラスやインターフェースを型として指定する方が適切な時が多いですが、やんごとなき理由で”呼び出し側でメソッドを使えるか使えないか判定し使えるならば使う”としなければならない時があります。そういった時に使える PHP でメソッドを呼べるか呼べないか判別する方法を紹介します。
使うのは is_callable という組み込み関数です。これは名前の通り引数に渡した指定の内容が呼べるか呼べないかを判別する関数です。これを使うとマジックメソッドを使った実体がなくとも使えるメソッド呼び出しも使える判定ができます。これは例えば次のように使えます。
/**
* echoMsgメソッドを持つクラスのインスタンスを受け取り、そのメソッドを呼び出す
* @param object $obj
* @return void
*/
function callEchoMsg(object $obj): void
{
// ここで echoMsg メソッドを読んで msg を echo して欲しい
if(is_callable([$obj, 'echoMsg'])) {
$obj->echoMsg();
} else {
echo 'echoMsg メソッドがありません' . PHP_EOL;
}
}
インスタンスとメソッド名を配列で渡して返り値の bool を見ます。このインスタンスとメソッド名の配列は PHP の中ではしばしば使われる形式であり PhpStorm などの IDE のコードジャンプ機能にも対応しています。実際に動作させると次のようになります。
<?php
/** echoMsgメソッドを持たないクラス */
class NoMethod{}
/** echoMsgメソッドを持つクラス */
class HasEchoMsg
{
public function echoMsg(): void
{
echo 'こんにちわ' . PHP_EOL;
}
}
/** マジックメソッドでechoMsgメソッド相当の動きができるクラス */
class HasMagicMethod
{
public function __call($name, $arguments)
{
if ($name === 'echoMsg') {
echo 'こんにちわ' . PHP_EOL;
}
}
}
/**
* echoMsgメソッドを持つクラスのインスタンスを受け取り、そのメソッドを呼び出す
* @param object $obj
* @return void
*/
function callEchoMsg(object $obj): void
{
// ここで echoMsg メソッドを読んで msg を echo して欲しい
if (is_callable([$obj, 'echoMsg'])) {
$obj->echoMsg();
} else {
echo 'echoMsg メソッドがありません' . PHP_EOL;
}
}
function main(): void
{
$obj = new HasEchoMsg();
callEchoMsg($obj); // こんにちわ
$obj = new HasMagicMethod();
callEchoMsg($obj); // こんにちわ
$obj = new NoMethod();
callEchoMsg($obj); // echoMsg メソッドがありません
}
main();
メソッドの存在判定という意味では method_exists という関数がありますが、こちらはマジックメソッド経由で呼び出せるメソッドに対応していません。このため呼び出せるかどうかのチェックをしたい今回のケースでは不適切です。
こういったことは自分でコードを触れる範囲の場合、なるべくインターフェースを利用した方が改修する際に間違えにくくていいのですが、外部ライブラリ等をそのまま使いたい時などでそうもいかない場合があります。そういった際にこのようなオブジェクトの形を判定するような手法が使えると解決しやすいです。