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

 

Висновок

Використовуйте дані методи відштовхуючись від своїх цілей, конкретної ситуації і контексту. Думайте, коли краще використовувати method_exists, а коли - is_callable. Однак з останнім слід бути акуратним і враховувати, що в якомусь із предків можуть бути визначені магічні методи.