method_exists или is_callable?

method_exists или is_callable?

Речь пойдёт о нюансах, которые по неким причинам явно не упомянуты в официальной документации. Если быть точнее, в документации - нет, но в комментариях к докам - да. Как бы то ни было, во избежание логических ошибок в коде, знать описанное ниже не помешает.

method_exists

Создадим простейший класс:

class Test
{
    public function foo()
    {
        return 'You called: ' . __METHOD__;
    }
}

 

Попробуем использовать функцию method_exists:

$test = new Test;

$method = 'foo';

if (method_exists($test, $method)) {
    echo $test->$method();
}

 

Вроде всё хорошо, на выходе получаем:

You called: Test::foo

 

Теперь поменяем public на private или protected. Сюрприз - fatal error! Т.е., неплохо было бы добавить пару строк в доках:

method_exists - проверяет, существует ли метод в данном классе, но не учитывает область видимость. Использование method_exists вне класса может привести к фатальным ошибкам.

К слову, на php.net приведены примеры использования именно вне класса. С другой стороны, применение данной функции в классе вполне приемлемо. Например, так:

class Test
{
    public function __get($property)
    {
        $method = "get{$property}";
        if (method_exists($this, $method)) {
            return $this->$method();
        }
    }

    public function getFoo()
    {
        return 'foo';
    }
}

$test = new Test;

echo $test->foo;

  

И для полноты картины:

  • что со статическим методами? То же самое - если метод определён в классе, method_exists() будет возвращать true
  • что, если метод определён в родителе? Пусть и в родителе, но определён, а значит получим true
  • что, если метода как такового нет, но определён магический __call()? А вот тут всё равно: метод существует? Нет. Значит false.

 

is_callable

Тут с областями видимости всё в порядке - если метод определён как private или protected, и Вы пытаетесь его вызвать вне класса - получите отказ (false). А теперь давайте представим ситуацию, что у нас есть некая иерархия классов:

class A
{
    public function __call($name, $args)
    {
        return 'You can call me!';
    }
}

class B extends A {}

class C extends B {}

$test = new C;

var_dump(is_callable([$test, 'foo']));

 

Какой результат получим? true! Следовательно, можно дополнить существующее определение:

is_callable проверяет, может ли значение переменной быть вызвано в качестве функции в текущем контексте. Следует учитывать, что если в классе или его родителе определён метод __call, данная функция всегда будет возвращать true.

И опять-таки:

  • проверка статических методов ведёт себя таким же образом
  • если метод можно вызвать из родителя, значит его можно вызвать и из дочернего класса
  • если определены __call (__callStatic для статическихх методов) - всегда будем получать true

 

Вывод

Используйте данные методы отталкиваясь от cвоих целей конкретной ситуации и контекста. Думайте, когда предпочтительней использовать method_exists, а когда - is_callable. Однако с последним следует быть аккуратным и учитывать, что в каком-то из предков могут быть определены магические методы.