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

# はじめに

`Callable` という擬似型は `PHP 5.4` 系で導入されていたようなのですが、ある値が `Callable` かどうかがどのように決まるのかさっぱり理解できず、業務で使う機会もそんなになかったことから放置していました。

業務で使う機会が出てきたというわけでも全くもって全くないのですが、ふと考えてみたら完全に理解できた気がしてしまったので、ツッコミ覚悟で記事にまとめることにしました。

この記事ではどんな値を `is_callable()` に渡したら `true` を返されるのかを探っていきます。 `is_callable()` が `true` を返す値はそのまま `call_user_func()` , `call_user_func_array()` などに渡して呼び出すことが可能です。

`var_dump()` を含んだコードの動作確認は [paiza.IO](https://paiza.io/projects/new) で行っています。執筆時のPHPバージョンは `PHP 7.4.1` でした。

# この記事のスコープ外

「関数やクラスがどの時点で定義されるのか」というのは、これはこれで複雑な話題です。

この記事では、「関数やクラスが定義されているかどうか」を明確にした上で `Callable` かどうかの判定方法を理解したいため、関数やクラスの定義方法は最もシンプルなもののみを用います。

```php
<?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
<?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
<?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
<?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
<?php

var_dump(is_callable(function (): bool { return true; }));
// => bool(true)
```

# `__invoke()` を実装したオブジェクト

`public function __invoke()` を実装したオブジェクトは `Callable` と判定されます。

```php
<?php

var_dump(is_callable(new class() {
    public function __invoke(): bool
    {
        return true;
    }
}));
// => bool(true)
```

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

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

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

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

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

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

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

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