Как вызвать метод в классе с динамически генерируемым именем через рефлексию в PHP
lang_of_article_differ
want_proper_trans
Вызов метода обычным способом
Допустим, у вас есть какой-то объект и вы хотите вызвать его метод, например вызов метода индекс на контроллере:
$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 выдает исключение, и вам не нужно обрабатывать номера ошибок, которые функциональный метод возвращает в случае сбоя операции и так далее. Более того, рефлексия позволяет вам вызывать закрытые и защищенные методы, что невозможно сделать другими способами .
А какой способ больше по душе вам?