Help us understand the problem. What is going on with this article?

CakePHPのソースコードを読んでいたら、知らないメソッドの呼び出し方があった

Fusic Advent Calendar 2019 - Qiita 9日目の記事です。

今回はPHPでこんなメソッドの呼び出しあるんだ?としらないことがあったので、そのことについて書いてみました。

知らなかった呼び出し方

CakePHPのソースコードを読んでいた時に、ここどうなってんだ?となったのがこの部分になります。
https://github.com/cakephp/cakephp/blob/master/src/Datasource/ModelAwareTrait.php#L127

$factoryに入ってる値

 /src/Datasource/ModelAwareTrait.php (line 128)
[
    (int) 0 => object(Cake\ORM\Locator\TableLocator) {
        [protected] locations => [
            (int) 0 => 'Model/Table'
        ]
        [protected] _config => []
        [protected] _instances => []
        [protected] _fallbacked => []
        [protected] _options => []
    },
    (int) 1 => 'get'
]

配列の1つ目にTableLocatorのインスタンス、2つめにgetという文字列が入ってます。

呼び出し

$factory($modelClass, $options);

こうなってます。配列(引数1, 引数2)です。
この部分の配列に対して、メソッド呼び出しみたいなことが出来ることを知りませんでした。
動き的には、 TableLocator::getが実行されており、call_user_func と同じような動作になってます。

調べてみました

ググってみましたが、結構情報が出ません。
というか検索しにくい。。。
php 配列 メソッド実行 とかでググると call_user_func ばかり引っかかって目的に到達できません。。。

以下のURLを見つけたのですが、どうやらPHP5.4からすでに実装されてるみたいです(笑)
知らなったよ。。。
PHP5.4っていうと2011年:joy:

参考URL
https://blog.sarabande.jp/post/6303916255
https://wiki.php.net/rfc/indirect-method-call-by-array-var

この記事によると、PHP5.5でベンチをとっており、直接インスタンスからの呼び出しが一番早いみたいですが、call_user_funcより配列からのメソッド実行の方が速い ようです。

せっかくPHP7.4が出たので、ベンチを取ってみました

ベンチ結果

基本的にはPHP5.5のときと変わらないですね。
参考URLでは call_user_funccall_user_func_arrayで多少差が出ているようですが、自分の環境ではまったく差が出なかったので省きました。

配列からの呼出しを2種類行ってますが、配列からの実行の場合でも書き方によって性能が出ないようです。

※参考URLより、ループの回数を増やしてます。(メソッド直接呼出しをおおよそ1秒に設定)

呼び出し方法 秒数
メソッド直接呼出し 0.99343309402466
call_user_func 4.6852278709412
配列から呼び出し1 4.5191860198975
配列から呼び出し2 2.491909027099
文字列から呼び出し 2.6240780353546
  • PHPバージョン : PHP7.4.0

ベンチソースコード

class Example {
    public function hoge()
    {
        return ;
    }
}

$e = new Example();
$start = microtime(true);

for ($i = 0; $i < 30000000; ++$i) {
    // この部分のコードのみを置き換えて計測しています。
    $e->hoge();
}
$end = microtime(true);

call_user_func

for ($i = 0; $i < 30000000; ++$i) {
    call_user_func([$e, 'hoge']);
}

配列から呼び出し1

変数に入れずに直接メソッド実行しています。
この方法だと、call_user_funcと性能が変わらないです。

for ($i = 0; $i < 30000000; ++$i) {
    [$e, 'hoge']();
}

配列から呼び出し2

ループの外で変数に配列を入れてからメソッド実行しています。

$arr = [$e, 'hoge'];

for ($i = 0; $i < 1000000; ++$i) {
    $arr();
}

文字列から呼び出し

$methodo = 'hoge';

for ($i = 0; $i < 30000000; ++$i) {
    $e->{$methodo}();
}

まとめ

相当ループを回さないと大きな性能差は出ませんが、速度を気にするのであればcall_user_funcは使わずに以下がよさそうです。

  • メソッドを直接実行
  • 配列から実行(必ず変数に入れてから実行)
  • 文字列から実行

今回はベンチ取ってないですが、以下のようにstaticなメソッドに関しても配列実行可能です。

class Example {
    public static function fuga()
    {
        return ;
    }
}
$arr = ['Example', 'fuga'];

for ($i = 0; $i < 30000000; ++$i) {
    $arr();
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした