はじめに
Callable という擬似型は PHP 5.4 系で導入されていたようなのですが、ある値が Callable かどうかがどのように決まるのかさっぱり理解できず、業務で使う機会もそんなになかったことから放置していました。
業務で使う機会が出てきたというわけでも全くもって全くないのですが、ふと考えてみたら完全に理解できた気がしてしまったので、ツッコミ覚悟で記事にまとめることにしました。
この記事ではどんな値を is_callable() に渡したら true を返されるのかを探っていきます。 is_callable() が true を返す値はそのまま call_user_func() , call_user_func_array() などに渡して呼び出すことが可能です。
var_dump() を含んだコードの動作確認は paiza.IO で行っています。執筆時のPHPバージョンは PHP 7.4.1 でした。
この記事のスコープ外
「関数やクラスがどの時点で定義されるのか」というのは、これはこれで複雑な話題です。
この記事では、「関数やクラスが定義されているかどうか」を明確にした上で Callable かどうかの判定方法を理解したいため、関数やクラスの定義方法は最もシンプルなもののみを用います。
<?php
// シンプルな関数定義
function someFunction() {}
// シンプルなクラス定義
class SomeClass()
{
public static funcion SomeClassMethod() {}
public function SomeObjectMethod() {}
}
// シンプルでない関数定義たち
if (true) {
function anotherFunction() {}
}
function defineYetAnotherFunction()
{
function yetAnotherFunction() {}
}
// シンプルでないクラス定義たち
if (true) {
class AnotherClass()
{
public static funcion AnotherClassMethod() {}
public function AnotherObjectMethod() {}
}
}
function defineYetAnotherClass()
{
class YetAnotherClass()
{
public static funcion YetAnotherClassMethod() {}
public function YetAnotherObjectMethod() {}
}
}
:: を含まない文字列
文字列と同じ名前の関数が定義されている場合は Callable であり、そうでない場合は Callable でない、と判定されます。
<?php
function is_odd(int $int): bool
{
return $int % 2 === 1;
}
var_dump(is_callable('is_odd'));
// => bool(true)
var_dump(is_callable('is_even'));
// => bool(false)
:: の両側になんかある文字列
:: の左側の文字列と同じ名前のクラスが定義されていて、かつ :: の右側の文字列と同じ名前のメソッドが定義されていて public な場合は Callable であり、そうでない場合は Callable でないと判定されます。
<?php
class SomeClass
{
public static function somePublicClassMethod() {}
protected static function someProtectedClassMethod() {}
private static function somePrivateClassMethod() {}
public function somePublicObjectMethod() {}
protected function someProtectedObjectMethod() {}
private function somePrivateObjectMethod() {}
}
var_dump(is_callable('SomeClass::somePublicClassMethod'));
// => bool(true)
var_dump(is_callable('SomeClass::someProtectedClassMethod'));
// => bool(false)
var_dump(is_callable('SomeClass::somePrivateClassMethod'));
// => bool(false)
var_dump(is_callable('SomeClass::somePublicObjectMethod'));
// => bool(true)
var_dump(is_callable('SomeClass::someProtectedObjectMethod'));
// => bool(false)
var_dump(is_callable('SomeClass::somePrivateObjectMethod'));
// => bool(false)
var_dump(is_callable('AnotherClass::somePublicClassMethod'));
// => bool(false)
var_dump(is_callable('AnotherClass::somePublicObjectMethod'));
// => bool(false)
var_dump(is_callable('SomeClass::anotherPublicClassMethod'));
// => bool(false)
var_dump(is_callable('SomeClass::anotherPublicObjectMethod'));
// => bool(false)
クラスのメソッド内で Callable かどうかを判定する場合、 :: の左側の文字列には追加で self , parent が許可され、 :: の右側の文字列には追加で protected , private なメソッドの名前が許可されます。
<?php
class ParentClass
{
public static function somePublicClassMethod() {}
public function somePublicObjectMethod() {}
}
class SomeClass extends ParentClass
{
public static function classMethod()
{
var_dump(is_callable('SomeClass::somePublicClassMethod'));
// => bool(true)
var_dump(is_callable('self::somePublicClassMethod'));
// => bool(true)
var_dump(is_callable('parent::somePublicClassMethod'));
// => bool(true)
var_dump(is_callable('SomeClass::someProtectedClassMethod'));
// => bool(true)
var_dump(is_callable('SomeClass::somePrivateClassMethod'));
// => bool(true)
var_dump(is_callable('SomeClass::somePublicObjectMethod'));
// => bool(true)
var_dump(is_callable('self::somePublicObjectMethod'));
// => bool(true)
var_dump(is_callable('parent::somePublicObjectMethod'));
// => bool(true)
var_dump(is_callable('SomeClass::someProtectedObjectMethod'));
// => bool(true)
var_dump(is_callable('SomeClass::somePrivateObjectMethod'));
// => bool(true)
}
public static function somePublicClassMethod() {}
protected static function someProtectedClassMethod() {}
private static function somePrivateClassMethod() {}
public function objectMethod()
{
var_dump(is_callable('SomeClass::somePublicClassMethod'));
// => bool(true)
var_dump(is_callable('self::somePublicClassMethod'));
// => bool(true)
var_dump(is_callable('parent::somePublicClassMethod'));
// => bool(true)
var_dump(is_callable('SomeClass::someProtectedClassMethod'));
// => bool(true)
var_dump(is_callable('SomeClass::somePrivateClassMethod'));
// => bool(true)
var_dump(is_callable('SomeClass::somePublicObjectMethod'));
// => bool(true)
var_dump(is_callable('self::somePublicObjectMethod'));
// => bool(true)
var_dump(is_callable('parent::somePublicObjectMethod'));
// => bool(true)
var_dump(is_callable('SomeClass::someProtectedObjectMethod'));
// => bool(true)
var_dump(is_callable('SomeClass::somePrivateObjectMethod'));
// => bool(true)
}
public function somePublicObjectMethod() {}
protected function someProtectedObjectMethod() {}
private function somePrivateObjectMethod() {}
}
SomeClass::classMethod();
(new SomeClass())->objectMethod();
Closure オブジェクト
無名関数式を使って作成した Closure オブジェクトは Callable と判定されます。
<?php
var_dump(is_callable(function (): bool { return true; }));
// => bool(true)
__invoke() を実装したオブジェクト
public function __invoke() を実装したオブジェクトは Callable と判定されます。
<?php
var_dump(is_callable(new class() {
public function __invoke(): bool
{
return true;
}
}));
// => bool(true)
[クラス名, ::を含まない文字列]
※力尽きたので一旦記事は公開する
[クラス名, ::の両側になんかある文字列]
※力尽きたので一旦記事は公開する
[オブジェクト, ::を含まない文字列]
※力尽きたので一旦記事は公開する
[オブジェクト, ::の両側になんかある文字列]
※力尽きたので一旦記事は公開する