🐣

[Hoge::class, 'method'] が Callable なのはなぜ?

2 min read

正確さを保証できない記事です

社内Slackで [$instance, 'hello']() と呼べる、という話と

https://qiita.com/carrotRakko/items/48fcf2efcb8a93cb01e4
この記事を見て、タイトルの疑問については

※力尽きたので一旦記事は公開する

とあり、自分で php-src を読みに行った話です

なお ぼくはC言語読めません。高校のときにポケコンで書いた main を書いたくらいなので、雰囲気で読んでます

Laravel のルーティングなどで [HogeController::class, 'index'] とか書いていますが、とくに疑問に思いませんでしたが、これは Callable なんですね Callable だから () をつければ実行できると

じゃあなぜ Callable なのか、どこで判定しているのか探ります

辿った php-src は次のコミット時点のものです

https://github.com/php/php-src/tree/b6958bb8476306c2f6ce110782330c41e6a5df3a

is_callable() はどこで実装されてるか

https://github.com/php/php-src/blob/b6958bb8476306c2f6ce110782330c41e6a5df3a/ext/standard/type.c#L400-L446

ここに PHP の is_callable() が実装されています
実際に Callable か判定しているのは zend_is_callable_ex のようです

zend_is_callable_ex

https://github.com/php/php-src/blob/b6958bb8476306c2f6ce110782330c41e6a5df3a/Zend/zend_API.c#L3809-L3822

戻り値になる bool を取得しているのは zend_is_callable_at_frame ですね

zend_is_callable_at_frame

https://github.com/php/php-src/blob/b6958bb8476306c2f6ce110782330c41e6a5df3a/Zend/zend_API.c#L3688-L3807

さて、ここにお目当ての実装があります
C が読めなくても case IS_ARRAY: はすぐわかりますね

この中で

  • 先頭の要素を obj に 2つめの要素を method に入れる
  • obj, method いずれかが NULL なら break
  • method が string じゃなければ break
  • obj が string で
    • is_callable(syntax_only: true) なら true
    • そうでない時 obj が Callable なクラスの名前でなければ false
      (以下略)

おや?

syntax_only: true

https://3v4l.org/WvpeG#focus=8.0.3
<?php

var_dump(is_callable(['存在しないクラス', 'method'], syntax_only: true)); // bool(true)
var_dump(is_callable(['存在しないクラス', 'method'], syntax_only: false)); // bool(false)

syntax_only の使い所がわからない…

method が string ならば

追記その2を参照してください、下記コードはミスってました!! ありがとうございます @SchroSisさん!!

https://3v4l.org/5XMgg#focus=8.0.3
<?php

class Hoge 
{
    public static function fuga() {}
}

var_dump(is_callable(Hoge::class, 'fuga')); // bool(true) わかる
var_dump(is_callable(Hoge::class, 'piyo')); // bool(true) !?

Callable とは一体…?


php-src をそれなりにちゃんと読み込むってあんまりしないので疲れます
今回は GitHub 上でずっと辿っていたので IDE 使ったらもう少し楽なのかな…?

最後の 存在しないメソッド名を指定しても Callable と判定されるのはなんでなんだろう


追記

https://twitter.com/n_1215/status/1404992938791227395

[Hoge::class, 'fuga']() は static じゃないと怒られていたからなんでルートに使えるのかと思ったらそういうことだったのか

似非Callableなるほどなるほど


追記その2

https://twitter.com/SchroSis/status/1404994474787303430

凡ミスすぎる、型は大事ですねえ!!