Как вызвать функцию из строки, хранящейся в переменной?

289

Мне нужно иметь возможность вызывать функцию, но имя функции хранится в переменной, это возможно? например:

функция foo ()
{
  // код здесь
}

функциональная панель ()
{
  // код здесь
}

$ functionName = "foo";
// мне нужно вызвать функцию в зависимости от значения $ functionName
ящерица
источник

Ответы:

465

$functionName() или call_user_func($functionName)

knittl
источник
85
Если вам нужно вызвать функцию объекта, имя которого находится в переменной, сделайте это: call_user_func (array ($ obj, $ func), $ params)
BlackDivine
1
Также смотрите мой ответ ниже, если вы имеете дело с классами и методами. Я не ожидал, что я нашел.
Крис К
Как я могу аннулировать ошибку определения функции, если функция не была определена? Достаточно ли ($ functionName)?
SaidbakR
14
@ sємsєм: Вы должны использовать is_callableфункцию. Он проверит, содержит ли переменная что-то, что может быть вызвано как функция (будь то имя функции, массив, содержащий экземпляр объекта и имя функции, или анонимная функция / замыкание)
knittl
6
@Pacerier: укажите имя класса в качестве первого элемента массива:call_user_func(array('ClassName', 'method'), $args)
knittl
122

Моя любимая версия - встроенная версия:

${"variableName"} = 12;

$className->{"propertyName"};
$className->{"methodName"}();

StaticClass::${"propertyName"};
StaticClass::{"methodName"}();

Вы можете поместить переменные или выражения в скобки тоже!

Лайос Месарос
источник
1
@marcioAlmada: публиковать комментарии, а не редактировать ответы других таким образом; это добавляет путаницу - непонятно, кто что сказал [любопытным: см. редакцию для объяснений]
kryger
4
Хорошо, @kryger. Строка 2 {"functionName"}();недопустима и выдаст синтаксическую ошибку.
Марсио
4
Спасибо, ребята, за ответы, это действительно выдает ошибку, даже в php 5.4, так что синтаксис работает только с объектными методами. Отредактировал пост.
Лайош Месарос
18

Как уже упоминалось, есть несколько способов достичь этого, возможно, самым безопасным способом, call_user_func()или, если необходимо, вы также можете пойти по пути $function_name(). Можно передать аргументы, используя оба этих метода, так

$function_name = 'foobar';

$function_name(arg1, arg2);

call_user_func_array($function_name, array(arg1, arg2));

Если вызываемая вами функция принадлежит объекту, вы все равно можете использовать любой из этих

$object->$function_name(arg1, arg2);

call_user_func_array(array($object, $function_name), array(arg1, arg2));

Однако, если вы собираетесь использовать $function_name()метод, может быть хорошей идеей проверить наличие функции, если имя каким-либо образом является динамическим

if(method_exists($object, $function_name))
{
    $object->$function_name(arg1, arg2);
}
olliefinn
источник
13

В случае, если Google привел сюда кого-то еще, потому что он пытался использовать переменную для метода в классе, ниже приведен пример кода, который действительно будет работать. Ничто из вышеперечисленного не сработало для моей ситуации. Основное различие заключается &в объявлении $c = & new...и &$cпередаче call_user_func.

Мой конкретный случай - при реализации чьего-либо кода, связанного с цветами и двумя методами-членами, lighten()и darken()из класса csscolor.php. По какой-то причине я хотел, чтобы один и тот же код мог вызывать светлее или темнее, а не выделять его с помощью логики. Это может быть результатом моего упрямства не просто использовать if-else или изменить код, вызывающий этот метод.

$lightdark="lighten"; // or optionally can be darken
$color="fcc";   // a hex color
$percent=0.15;  
include_once("csscolor.php");
$c = & new CSS_Color($color);
$rtn=call_user_func( array(&$c,$lightdark),$color,$percent);

Обратите внимание, что попытка чего-либо с $c->{...}не работает. Прочитав материалы, предоставленные читателями внизу страницы php.net call_user_func, я смог собрать все вышеперечисленное. Также обратите внимание, что $paramsкак массив не работал для меня:

// This doesn't work:
$params=Array($color,$percent);
$rtn=call_user_func( array(&$c,$lightdark),$params);

Приведенная выше попытка выдаст предупреждение о методе, ожидающем 2-й аргумент (проценты).

Крис К
источник
что делает "$ c = & new CSS_Color"? Я говорю об усилителе (&). Что это и меняет?
Данон
@danon & является оператором «address of» или «reference». Я вызываю новый объект класса CSS_Color, а затем присваиваю его ссылку (он же адрес) на $ c. Я ненавижу повторять код, поэтому часто использую имена переменных переменных.
Крис К
1
Я не понимаю Разве объекты не передаются по ссылке везде? Я имею в виду, если вы передадите $ c функции и измените ее, оригинальный объект тоже будет затронут, верно? Неважно, использовали ли вы это или нет, я прав?
Данон
Я пробовал разные комбинации символов, и только читая комментарии пользователей на php.org, я смог получить рабочий код. Вам нужно будет начать это обсуждение с людьми, ответственными за поведение php.
Крис К
Для второго примера, который вы неправильно прочитали, вы искали call_user_func_array
Tofandel
13

Несколько лет спустя, но сейчас это лучший способ imho:

$x = (new ReflectionFunction("foo"))->getClosure();
$x();
Внутренняя ошибка сервера
источник
3
Путь к ООП для меня, но я дал ему +1, так как никто еще не упомянул классы Reflection.
Лайош Месарос
Не понимаю этот ответ. Какую проблему это решает, точно?
Дэвид Спектор
10

Для полноты картины вы также можете использовать eval () :

$functionName = "foo()";
eval($functionName);

Тем не менее, call_user_func()это правильный путь.

karim79
источник
8
Следует отметить, что eval () допускает такое же поведение, но также позволяет выполнять любой код PHP. Таким образом, переменная может быть использована для взлома системы. Call_user_func () не приносит таких проблем безопасности.
Джон
4
evalне является решением этого вопроса.
ankr
1
Пожалуйста, не делайте этого, потому что злоумышленник может полностью уничтожить ваш сервер.
andreszs
10

Решение: использовать PHP7

Примечание: для краткой версии см. TL; DR в конце ответа.

Старые Методы

Обновление : один из старых методов, описанных здесь, был удален. Обратитесь к другим ответам для объяснения других методов, здесь это не рассматривается. Кстати, если этот ответ вам не поможет, вам следует вернуться к обновлению вашего материала. Поддержка PHP 5.6 прекратилась в январе 2019 года (теперь даже PHP 7.0 и 7.1 не поддерживаются). Смотрите поддерживаемые версии для получения дополнительной информации.

Как уже упоминалось, в PHP5 (а также в более новых версиях, таких как PHP7) мы могли использовать переменные в качестве имен функций, использовать call_user_func()и call_user_func_array()(которые, лично я ненавижу эти функции) и т. Д.

Новые Методы

Начиная с PHP7, появились новые способы:

Примечание. Все, что в <something>скобках, означает одно или несколько выражений для формирования чего-либо, например, <function_name>означает выражения, образующие имя функции.

Динамический вызов функции: имя функции на лету

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

(<function_name>)(arguments);

Например:

function something(): string
{
    return "something";
}

$bar = "some_thing";

(str_replace("_", "", $bar))(); // something

// Possible, too; but generally, not recommended, because makes your code more complicated
(str_replace("_", "", $bar))()(); 

Примечание. Хотя удаление скобок str_replace()не является ошибкой, добавление скобок делает код более читабельным. Тем не менее, вы не можете сделать это иногда, например, при использовании .оператора. Чтобы быть последовательным, я рекомендую ставить круглые скобки всегда.

Динамический вызов метода: имя метода на лету

Так же, как динамические вызовы функций, мы можем сделать то же самое с вызовами методов, заключенными в фигурные скобки вместо скобок (для дополнительных форм перейдите к разделу TL; DR):

$object->{<method_name>}(arguments);
$object::{<method_name>}(arguments);

Смотрите это в примере:

class Foo
{
    public function another(): string
    {
        return "something";
    }
}

$bar = "another thing";

(new Something())->{explode(" ", $bar)[0]}(); // something

Динамический вызов метода: синтаксис массива

В PHP7 добавлен более элегантный способ:

[<object>, <method_name>](arguments);
[<class_name>, <method_name>](arguments); // Static calls only

Например:

class Foo
{
    public function nonStaticCall()
    {
        echo "Non-static call";
    }
    public static function staticCall()
    {
        echo "Static call";
    }
}

$x = new X();

[$x, "non" . "StaticCall"](); // Non-static call
[$x, "static" . "Call"](); // Static call

Примечание . Преимущество использования этого метода перед предыдущим заключается в том, что вам не важен тип вызова (т. Е. Статический он или нет).

Дополнительный пример: использование анонимных классов

Делая вещи немного сложнее, вы можете использовать комбинацию анонимных классов и описанных выше функций:

$bar = "SomeThing";

echo (new class {
    public function something()
    {
        return 512;
    }
})->{strtolower($bar)}(); // 512

TL; DR (Заключение)

Как правило, в PHP7 возможно использование следующих форм:

// Everything inside `<something>` brackets means one or more expressions
// to form something

// Dynamic function call
(<function_name>)(arguments)

// Dynamic method call on an object
$object->{<method_name>}(arguments)
$object::{<method_name>}(arguments)

// Dynamic method call on a dynamically-generated object
(<object>)->{<method_name>}(arguments)
(<object>)::{<method_name>}(arguments)

// Dynamic method call, statically
ClassName::{<method_name>}(arguments)
(<class_name>)::{<method_name>}(arguments)

// Dynamic method call, array-like (no different between static and non-static calls
[<object>, <method_name>](arguments)

// Dynamic method call, array-like, statically
[<class_name>, <method_name>](arguments)

Основано в основном на этом разговоре PHP .

MAChitgarha
источник
1
Хороший список выражений, также включает в себя:(expressions)->$bar()
Necro
1
@Necro Спасибо, я добавил это!
MAChitgarha
8

Динамические имена функций и пространства имен

Просто добавить пункт о динамических именах функций при использовании пространств имен.

Если вы используете пространства имен, следующее не будет работать, если только ваша функция не находится в глобальном пространстве имен:

namespace greetings;

function hello()
{
    // do something
}

$myvar = "hello";
$myvar(); // interpreted as "\hello();"

Что делать?

Вы должны использовать call_user_func()вместо:

// if hello() is in the current namespace
call_user_func(__NAMESPACE__.'\\'.$myvar);

// if hello() is in another namespace
call_user_func('mynamespace\\'.$myvar);
Дживан
источник
4

В дополнение к ответу @Chris K, если вы хотите вызвать метод объекта, вы можете вызвать его с помощью одной переменной с помощью замыкания:

function get_method($object, $method){
    return function() use($object, $method){
        $args = func_get_args();
        return call_user_func_array(array($object, $method), $args);           
    };
}

class test{        

    function echo_this($text){
        echo $text;
    }
}

$test = new test();
$echo = get_method($test, 'echo_this');
$echo('Hello');  //Output is "Hello"

Я разместил еще один пример здесь

Дэвид
источник
4

Что я узнал из этого вопроса и ответов. Спасибо всем!

Допустим, у меня есть эти переменные и функции:

$functionName1 = "sayHello";
$functionName2 = "sayHelloTo";
$functionName3 = "saySomethingTo";

$friend = "John";
$datas = array(
    "something"=>"how are you?",
    "to"=>"Sarah"
);

function sayHello()
{
echo "Hello!";
}

function sayHelloTo($to)
{
echo "Dear $to, hello!";
}

function saySomethingTo($something, $to)
{
echo "Dear $to, $something";
}
  1. Для вызова функции без аргументов

    // Calling sayHello()
    call_user_func($functionName1); 

    Здравствуйте!

  2. Для вызова функции с 1 аргументом

    // Calling sayHelloTo("John")
    call_user_func($functionName2, $friend);

    Дорогой Джон, привет!

  3. Вызов функции с 1 или более аргументами. Это будет полезно, если вы динамически вызываете свои функции и каждая функция имеет разное количество аргументов. Это мой случай, который я искал (и раскрыл). call_user_func_arrayэто ключ

    // You can add your arguments
    // 1. statically by hard-code, 
    $arguments[0] = "how are you?"; // my $something
    $arguments[1] = "Sarah"; // my $to
    
    // 2. OR dynamically using foreach
    $arguments = NULL;
    foreach($datas as $data) 
    {
        $arguments[] = $data;
    }
    
    // Calling saySomethingTo("how are you?", "Sarah")
    call_user_func_array($functionName3, $arguments);

    Дорогая Сара, как ты?

Yay пока!

asmasyakirah
источник
3

Используйте функцию call_user_func.

PaulJWilliams
источник
2

Следующий код может помочь написать динамическую функцию в PHP. теперь имя функции можно динамически изменять с помощью переменной '$ current_page'.

$current_page = 'home_page';
$function = @${$current_page . '_page_versions'};
$function = function() {
    echo 'current page';
};
$function();
rakesh.falke
источник
Анонимные функции (т. Е. Динамические функции) отличаются от переменных функций (т. Е. Динамического вызова функций). Кроме того, вы заменяете $functionпеременную без каких-либо причин.
MAChitgarha
1

Самый простой способ безопасного вызова функции с использованием имени, хранящегося в переменной:

//I want to call method deploy that is stored in functionname 
$functionname = 'deploy';

$retVal = {$functionname}('parameters');

Я использовал, как показано ниже, для динамического создания таблиц миграции в Laravel,

foreach(App\Test::$columns as $name => $column){
        $table->{$column[0]}($name);
}
Дебаши Прусты
источник
-4

Один необычный подход, который мне пришёл в голову, заключается в том, что, если вы не генерируете весь код с помощью какого-либо супер-ультра-автономного ИИ, который пишет сам , есть большие шансы, что функции, которые вы хотите «динамически» вызывать, уже определены в вашем коде. основание. Так почему бы просто не проверить строку и не позорить незнакомый ifelseтанец, чтобы вызвать ... вы поняли мою точку зрения.

например.

if($functionName == 'foo'){
  foo();
} else if($functionName == 'bar'){
  bar();
}

Даже switch-caseможно использовать, если вам не нравится мягкий вкус ifelseлестницы.

Я понимаю, что есть случаи, когда « динамический вызов функции » был бы абсолютной необходимостью ( подобно некоторой рекурсивной логике, которая модифицирует себя ). Но большинство повседневных тривиальных сценариев использования можно просто избежать.

Он отсеивает большую неопределенность в вашем приложении, давая вам возможность выполнить резервную функцию, если строка не соответствует определению какой-либо из доступных функций. ПО МОЕМУ МНЕНИЮ.

Мохд Абдул Муджиб
источник
Если значение переменной уже является именем функции, зачем делать дополнительную работу? также было бы хорошо получить твердую обратную связь, чтобы увидеть, существует ли функция по php, прежде чем запускать ее: if (method_exists($functionName)) $functionName(); else //exception or handle
Necro
2
Да, ваша точка зрения верна, но поскольку имя функции является динамическим, в вашей логике любая строка, которая приходит, должна выполняться, если функция с таким именем существует, независимо от того, актуальна ли эта функция в текущей ситуации или нет. Мой подход гарантирует, что только определенная группа функций может быть выполнена, иначе может быть выдана ошибка. Это может быть упрощено массивами и как таковыми.
Мохд Абдул Муджиб
-10

Я не знаю, почему вы должны это использовать, мне это не очень хорошо звучит, но если есть только небольшое количество функций, вы можете использовать конструкцию if / elseif. Я не знаю, возможно ли прямое решение.

что-то вроде $ foo = "bar"; $ test = "foo"; эхо-тест $$;

должен вернуть бар, вы можете попробовать, но я не думаю, что это будет работать для функций

Фло
источник