PHPのCallableについて完全に理解する


はじめに

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)

[クラス名, ::を含まない文字列]

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

[クラス名, ::の両側になんかある文字列]

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

[オブジェクト, ::を含まない文字列]

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

[オブジェクト, ::の両側になんかある文字列]

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