5 minutes to read, 13.85K views since 2019.10.10 Read in english

Как вызвать метод в классе с динамически генерируемым именем через рефлексию в PHP

Вызов метода обычным способом

Допустим, у вас есть какой-то объект и вы хотите вызвать его метод, например вызов метода индекс на контроллере:

$controller->indexAction();

С этим все понятно, а что если имя метода неизвестно в тот момент, когда вы написали свой скрипт, и вам нужно получить его из переменной, например $method. Тоже норм:

$method = 'indexAction';
$object->{$method}();

но если метод не существует на объекте, это вызовет фатальную ошибку и прекратит выполнение скрипта. Но вы скажете, что можно использовать функцию method_exists, чтобы проверить, существует ли метод класса, и вы правы, это можно сделать:

$method = 'indexAction';
if (method_exists($object,$method)) {
  $object->{$method}();
} else {
  echo "Method not exists";
}

А что делать, если вы хотите передать какие-то параметры в вызываемый метод? если число параметров и их значений являются постоянными, вы можете легко передать его в обычном виде:

$object->{$method}($some,$param);

В случае когда количество параметров наперед не известно, вы можете передать динамический набор параметров создав массив и передав их в вызов функции call_user_func_array как это сделано в следующем примере:

$params = [];

$params[] = 1;
$params[] = 'second param value';

call_user_func_array([$object,$method]), $params); // это вызовет метод с двумя параметрами

А как же обрабатывать ошибки, которые могут возникнуть в случае, если количество параметров неверно или отсутствуют некоторые параметры? И как правильно вызвать статический метод объекта, который находится в пространстве имен?

call_user_func_array(__NAMESPACE__ .'\Controller::aliasAction',['about']);

Это бы сработало, но эта часть кода выглядит ужасно. Да, действительно, функция call_user_func_array была в пхп еще задолго до того как появились неймспейсы да и вообще обьекты, поэтому она не особо приспособлена для таких вещей. В таких случаях лучше (красивее, мы не говорим здесь об эффективности) использовать Reflection Api для вызова методов на объектах. Давайте посмотрим, как сделать такие вещи:  

Вызов метод класса через отражение

    Сначала вам нужно получить объект ReflectionMethod, который ссылается на необходимый вам класс и метод. Это можно сделать двумя способами:  

  • создать экземпляр ReflectionMethod:

       $method = new ReflectionMethod('ClassName','MethodName');
  • получив его через метод getMethod с параметром - именем метода на обьекте ReflectionClass необходимого нам класса:

       $class = new ReflectionClass('ClassName');
       $method = $class->getMethod('MethodName');

Давайте посмотрим на сложный пример вызова метода с параметрами, при помощи класса ReflectionMethod:


 $controller = new IndexController(); // imagine its a controller from framework with mvc architecture
 $method = 'indexAction'; // you need to call indexAction on it

 $params = ['en','about'];

 try {
   $reflectionMethod = new ReflectionMethod($controller,$method); // this would throw an exception in case method is not exists on object
   $reflectionMethod->invokeArgs($controller,$params); // throws exception if parameters are wrong

 } catch (/ReflectionException $e) {
   echo 'Error calling method '.$method.' on IndexController';
 }

Да, куда лучше и понятнее чем пример выше с помощью call_user_func_array.
Более того, вышеупомянутый пример позволяет вам вызывать любой метод объекта таким образом: статический или обычный, с параметрами или без.  

Вызов private метода

    Reflection API позволяет вам вызывать даже приватные методы класса. Предположим есть класс:

 class IndexController {

   private function disabledAction(){
     echo 'private action result';
   }

   // ... rest of code ...

 }

Вам не удасться вызвать этот метод при помощи примера который мы привели выше:

 $controller = new IndexController();
 $reflectionMethod = (new ReflectionClass($controller))->getMethod($method);
 $reflectionMethod->invoke($controller);

Такой код вызовет ексепшн, который может привести программу к несовсем ожидаемым последствиям. Для того чтобы вызвать приватный метод класса в PHP нужно сделать его доступным к исполнению. В арсенале Reflection API PHP есть метод для этого: перед тем как делать invoke нашего метода , вы должны setAccesible его:

 $controller = new IndexController();
 $reflectionMethod = (new ReflectionClass($controller))->getMethod($method); 
 $reflectionMethod->setAccessible(true); // вот строчка которая делает метод доступным к вызову даже если он private
 $reflectionMethod->invoke($controller); // сейчас никаких ексепшнов уже не будет

Плюсы и минусы

  Что касается меня, то я скажу это гораздо более элегантный код, чем использование функции call_user_func_array. Конечно, метод, который мы выбрали для вызова метода, зависит от того, чего нам нужно достичь: если нужно использовать как можно меньше ресурсов, то лучше использовать функциональный стиль.  

Если нам нужен более элегантный код, более читаемый и написанный в объектно-ориентированном стиле - лучше использовать Reflection Api. Reflection API выдает исключение, и вам не нужно обрабатывать номера ошибок, которые функциональный метод возвращает в случае сбоя операции и так далее. Более того, рефлексия позволяет вам вызывать закрытые и защищенные методы, что невозможно сделать другими способами .

А какой способ больше по душе вам?

Read next article Как получить список свойств класса при помощи рефлексии в PHP in course Reflection in PHP