Jak wywołać funkcję z ciągu zapisanego w zmiennej?

289

Muszę być w stanie wywołać funkcję, ale nazwa funkcji jest przechowywana w zmiennej, czy to możliwe? na przykład:

funkcja foo ()
{
  // kod tutaj
}

pasek funkcji ()
{
  // kod tutaj
}

$ functionName = "foo";
// muszę wywołać funkcję w oparciu o wartość $ functionName
Jaszczurka
źródło

Odpowiedzi:

465

$functionName() lub call_user_func($functionName)

knittl
źródło
85
Jeśli chcesz wywołać funkcję obiektu, którego nazwa znajduje się w zmiennej, wykonaj następujące czynności: call_user_func (tablica ($ obj, $ func), $ params)
BlackDivine,
1
Zobacz także moją odpowiedź poniżej, jeśli masz do czynienia z klasami i metodami. Nie spodziewałem się tego, co znalazłem.
Chris K
Jak mogę anulować błąd definicji funkcji, jeśli funkcja nie została zdefiniowana? Czy jeśli ($ functionName) wystarczy?
SaidbakR
14
@ sємsєм: Powinieneś użyć is_callablefunkcji. Sprawdza, czy zmienna zawiera coś, co można wywołać jako funkcję (czy to nazwa funkcji, tablica zawierająca instancję obiektu i nazwę funkcji, czy anonimowa funkcja / zamknięcie)
knittl
6
@Pacerier: Podaj nazwę klasy jako pierwszy element tablicy:call_user_func(array('ClassName', 'method'), $args)
knittl
122

Moja ulubiona wersja to wersja wbudowana:

${"variableName"} = 12;

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

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

Zmienne lub wyrażenia możesz również umieszczać w nawiasach!

Lajos Meszaros
źródło
1
@marcioAlmada: opublikuj komentarz zamiast edytować odpowiedzi innych w ten sposób; dodaje zamieszanie - nie jest jasne, kto powiedział co [ciekawi: zobacz wersje dla wyjaśnień]
kryger
4
Ok @kryger. Wiersz 2 {"functionName"}();jest niezgodny z prawem i zgłosi błąd składniowy.
marcio
4
Dziękuję wam, za odpowiedzi, w rzeczywistości wyrzuca błąd, nawet w php 5.4, więc składnia działa tylko z metodami obiektowymi. Edytowałem post.
Lajos Meszaros
18

Jak już wspomniano, istnieje kilka sposobów, aby to osiągnąć za pomocą możliwie najbezpieczniejszej metody call_user_func()lub, jeśli to konieczne, możesz również zejść trasą $function_name(). Możliwe jest przekazywanie argumentów przy użyciu obu tych metod

$function_name = 'foobar';

$function_name(arg1, arg2);

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

Jeśli funkcja, którą wywołujesz, należy do obiektu, nadal możesz użyć jednego z nich

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

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

Jeśli jednak zamierzasz użyć tej $function_name()metody, dobrym pomysłem może być przetestowanie istnienia funkcji, jeśli nazwa jest w jakikolwiek sposób dynamiczna

if(method_exists($object, $function_name))
{
    $object->$function_name(arg1, arg2);
}
olliefinn
źródło
13

W przypadku, gdy ktoś jest tu sprowadzany przez Google, ponieważ próbował użyć zmiennej dla metody w klasie, poniżej znajduje się przykładowy kod, który faktycznie będzie działał. Żadne z powyższych nie działało w mojej sytuacji. Kluczową różnicą jest &deklaracja $c = & new...i &$cprzekazanie call_user_func.

Mój szczególny przypadek dotyczy implementacji czyjegoś kodu związanego z kolorami i dwiema metodami składowymi lighten()oraz darken()z klasy csscolor.php. Z jakiegokolwiek powodu chciałem, aby ten sam kod mógł wywoływać rozjaśnianie lub przyciemnianie zamiast wybierać go za pomocą logiki. Może to wynikać z mojego uporu, by nie używać tylko if-else lub zmieniać kod wywołujący tę metodę.

$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);

Zauważ, że próbowanie czegokolwiek z $c->{...}nie działało. Po zapoznaniu się z treściami napisanymi przez czytelników na dole strony php.net call_user_func, udało mi się poskładać powyższe. Zauważ też, że $paramsjako tablica nie działała dla mnie:

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

Ta powyższa próba dałaby ostrzeżenie o metodzie oczekującej drugiego argumentu (procent).

Chris K.
źródło
co robi „$ c = i nowy CSS_Color”? Mówię o wzmacniaczu (&). Co to zmienia i zmienia?
Danon,
@danon & to „adres” lub operator „odniesienia”. Instynkowuję nowy obiekt klasy CSS_Color, a następnie przypisuję jego odwołanie (czyli adres) do $ c. Nienawidzę powtarzania kodu, więc często używam nazw zmiennych.
Chris K
1
Nie rozumiem. Czy w każdym razie obiekty nie są przekazywane przez odniesienie wszędzie? To znaczy, jeśli przekażesz $ c do funkcji i zmienisz ją, wpłynie to również na oryginalny obiekt, prawda? Nie ważne, czy używałeś tego i nie, mam rację?
Danon,
Próbowałem różnych kombinacji symboli i tylko podczas czytania komentarzy użytkowników na php.org udało mi się uzyskać działający kod. Musisz rozpocząć tę dyskusję z osobami odpowiedzialnymi za zachowanie php.
Chris K
W drugim przypadku, który źle odczytałeś, szukałeś call_user_func_array
Tofandel
13

Kilka lat spóźnienia, ale teraz jest to najlepszy sposób, imho:

$x = (new ReflectionFunction("foo"))->getClosure();
$x();
Wewnętrzny błąd serwera
źródło
3
Sposób na OOP dla mnie, ale dałem mu +1, ponieważ nikt jeszcze nie wspomniał o klasach Refleksji.
Lajos Meszaros
Nie rozumiem tej odpowiedzi. Jaki dokładnie problem rozwiązuje?
David Spector
10

Dla kompletności możesz również użyć eval () :

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

Jednak call_user_func()to jest właściwy sposób.

karim79
źródło
8
Należy zauważyć, że eval () pozwoli na takie samo zachowanie, ale pozwoli również na wykonanie dowolnego kodu PHP. Tak więc zmienna może zostać wykorzystana do przejęcia systemu. Funkcja call_user_func () nie powoduje takich problemów z bezpieczeństwem.
Jan
4
evalnie jest rozwiązaniem tego pytania.
ankr
1
Nie rób tego, ponieważ osoba atakująca może całkowicie wyczyścić serwer.
andreszs
10

Rozwiązanie: użyj PHP7

Uwaga: Aby uzyskać podsumowaną wersję, patrz TL; DR na końcu odpowiedzi.

Stare metody

Aktualizacja : Jedna ze starych opisanych tutaj metod została usunięta. Wyjaśnienia na temat innych metod można znaleźć w innych odpowiedziach, które nie są tutaj omówione. Nawiasem mówiąc, jeśli ta odpowiedź ci nie pomoże, powinieneś zaktualizować swoje rzeczy. Obsługa PHP 5.6 zakończyła się w styczniu 2019 r. (Obecnie nawet PHP 7.0 i 7.1 nie są obsługiwane). Aby uzyskać więcej informacji, zobacz obsługiwane wersje .

Jak wspomniano inni, w PHP5 (a także w nowszych wersjach, takich jak PHP7) możemy używać zmiennych jako nazw funkcji, używać call_user_func()icall_user_func_array() (które osobiście nienawidzę tych funkcji) itp.

Nowe metody

Od PHP7 wprowadzono nowe sposoby:

Uwaga: Wszystko w <something>nawiasach oznacza jedno lub więcej wyrażeń, aby coś utworzyć, np. <function_name>Oznacza wyrażenia tworzące nazwę funkcji.

Dynamiczne wywołanie funkcji: nazwa funkcji w locie

Możemy użyć jednego lub więcej wyrażeń w nawiasach jako nazwy funkcji za jednym razem, w postaci:

(<function_name>)(arguments);

Na przykład:

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))()(); 

Uwaga: Chociaż usunięcie nawiasów wokoło str_replace()nie jest błędem, umieszczenie nawiasów czyni kod bardziej czytelnym. Czasami jednak nie możesz tego zrobić, np. Podczas używania. operatora. Aby zachować spójność, zawsze zaleca się umieszczanie nawiasów.

Dynamiczne wywołanie metody: nazwa metody w locie

Podobnie jak dynamiczne wywołania funkcji, możemy zrobić to samo z wywołaniami metod, otoczonymi nawiasami klamrowymi zamiast nawiasów (w przypadku dodatkowych formularzy przejdź do sekcji TL; DR):

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

Zobacz to w przykładzie:

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

$bar = "another thing";

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

Dynamiczne wywołanie metody: składnia macierzy

Bardziej elegancki sposób dodany w PHP7 jest następujący:

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

Jako przykład:

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

Uwaga: Zaletą korzystania z tej metody w porównaniu z poprzednią jest to, że nie obchodzi Cię typ połączenia (tj. Czy jest statyczny czy nie).

Dodatkowy przykład: korzystanie z anonimowych klas

Sprawiając nieco komplikację, możesz użyć kombinacji anonimowych klas i powyższych funkcji:

$bar = "SomeThing";

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

TL; DR (Podsumowanie)

Ogólnie rzecz biorąc, w PHP7 możliwe są wszystkie następujące formularze:

// 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)

Oparte głównie na tej rozmowie PHP .

MAChitgarha
źródło
1
Ładna lista wyrażeń, obejmuje również:(expressions)->$bar()
Necro
1
@Necro Dzięki, dodałem to!
MAChitgarha,
8

Dynamiczne nazwy funkcji i przestrzenie nazw

Wystarczy dodać punkt o nazwach funkcji dynamicznych podczas korzystania z przestrzeni nazw.

Jeśli używasz przestrzeni nazw, następujące czynności nie będą działać, z wyjątkiem sytuacji, gdy twoja funkcja znajduje się w globalnej przestrzeni nazw:

namespace greetings;

function hello()
{
    // do something
}

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

Co robić?

Zamiast tego musisz użyć 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);
Jivan
źródło
4

Uzupełniając odpowiedź @Chris K, jeśli chcesz wywołać metodę obiektu, możesz wywołać ją za pomocą jednej zmiennej za pomocą zamknięcia:

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"

Tutaj zamieściłem inny przykład

David
źródło
4

Czego nauczyłem się z tego pytania i odpowiedzi. Dziękuje wszystkim!

Powiedzmy, że mam te zmienne i funkcje:

$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. Aby wywołać funkcję bez argumentów

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

    Witaj!

  2. Aby wywołać funkcję z 1 argumentem

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

    Drogi Johnie, cześć!

  3. Aby wywołać funkcję z 1 lub więcej argumentami Przyda się to, jeśli dynamicznie wywołujesz swoje funkcje, a każda funkcja ma inną liczbę argumentów. To jest mój przypadek, którego szukałem (i rozwiązałem). call_user_func_arrayjest kluczem

    // 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);

    Droga Sarah, jak się masz?

Cześć!

asmasyakirah
źródło
3

Użyj funkcji call_user_func.

PaulJWilliams
źródło
2

Poniższy kod może pomóc napisać funkcję dynamiczną w PHP. teraz nazwę funkcji można dynamicznie zmieniać za pomocą zmiennej „$ current_page”.

$current_page = 'home_page';
$function = @${$current_page . '_page_versions'};
$function = function() {
    echo 'current page';
};
$function();
rakesh.falke
źródło
Funkcje anonimowe (tj. Funkcje dynamiczne) różnią się od funkcji zmiennych (tj. Funkcje wywoływania dynamicznie). Ponadto zastępujesz $functionzmienną bez żadnego powodu.
MAChitgarha
1

Najłatwiejszym sposobem na bezpieczne wywołanie funkcji przy użyciu nazwy zapisanej w zmiennej jest:

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

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

Użyłem jak poniżej do dynamicznego tworzenia tabel migracji w Laravel,

foreach(App\Test::$columns as $name => $column){
        $table->{$column[0]}($name);
}
Debashis Prusty
źródło
-4

Jedno niekonwencjonalne podejście, jakie przyszło mi do głowy, to takie, że jeśli nie generujesz całego kodu za pomocą super ultra autonomicznej sztucznej inteligencji, która sama się pisze , istnieje duże prawdopodobieństwo, że funkcje, które chcesz „dynamicznie” wywołać, są już zdefiniowane w twoim kodzie baza. Dlaczego więc nie po prostu sprawdzić łańcucha i zrobić niesławnyifelse taniec, aby przywołać ... rozumiesz?

na przykład.

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

switch-caseMożna go nawet użyć, jeśli nie lubisz łagodnego smaku ifelsedrabiny.

Rozumiem, że istnieją przypadki, w których „ dynamiczne wywoływanie funkcji ” byłoby absolutną koniecznością ( jak pewna logika rekurencyjna, która się modyfikuje ). Ale większość codziennych trywialnych przypadków użycia można uniknąć.

Usuwa wiele niepewności z aplikacji, dając jednocześnie szansę na wykonanie funkcji rezerwowej, jeśli ciąg znaków nie pasuje do żadnej z dostępnych funkcji. MOIM ZDANIEM.

Mohd Abdul Mujib
źródło
Jeśli wartość zmiennej jest już nazwą funkcji, dlaczego dodatkowa funkcja działa? również dobrze byłoby uzyskać solidne informacje zwrotne, aby sprawdzić, czy funkcja istnieje przez php przed uruchomieniem: if (method_exists($functionName)) $functionName(); else //exception or handle
Necro
2
Tak, twój punkt jest poprawny, ale ponieważ nazwa funkcji jest dynamiczna, w twojej logice każdy ciąg, który przyjdzie, zostanie wykonany, jeśli funkcja o tej nazwie istnieje, niezależnie od tego, czy funkcja ta jest aktualna w obecnej sytuacji, czy nie. Moje podejście zapewnia, że ​​można wykonać tylko pewną grupę funkcji, w przeciwnym razie może zostać zgłoszony błąd. Można to uprościć za pomocą tablic i jako takie.
Mohd Abdul Mujib,
-10

Nie wiem, dlaczego musisz tego używać, wcale nie brzmi dla mnie tak dobrze, ale jeśli jest tylko niewielka liczba funkcji, możesz użyć konstrukcji if / elseif. Nie wiem, czy możliwe jest bezpośrednie rozwiązanie.

coś jak $ foo = "bar"; $ test = "foo"; test echa $$;

powinien zwrócić pasek, możesz spróbować, ale nie sądzę, że to zadziała dla funkcji

Flo
źródło