จะเรียกใช้ฟังก์ชันจากสตริงที่เก็บไว้ในตัวแปรได้อย่างไร?


289

ฉันต้องสามารถเรียกใช้ฟังก์ชั่นได้ แต่ชื่อฟังก์ชั่นนั้นถูกเก็บไว้ในตัวแปรเป็นไปได้หรือไม่? เช่น:

ฟังก์ชั่น foo ()
{
  // รหัสที่นี่
}

แถบฟังก์ชั่น ()
{
  // รหัสที่นี่
}

$ functionName = "foo";
// ฉันต้องเรียกใช้ฟังก์ชันตาม $ functionName

คำตอบ:


465

85
ถ้าคุณต้องการเรียกฟังก์ชันของวัตถุที่มีชื่ออยู่ในตัวแปรให้ทำดังนี้: call_user_func (array ($ obj, $ func), $ params)
BlackDivine

1
โปรดดูคำตอบของฉันด้านล่างหากคุณจัดการกับคลาสและวิธีการ ฉันไม่ได้คาดหวังสิ่งที่ฉันพบ
Chris K

ฉันจะยกเลิกข้อผิดพลาดการกำหนดฟังก์ชั่นได้อย่างไรถ้าฟังก์ชั่นไม่ได้กำหนดไว้? ถ้า ($ 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"}();ไม่ถูกกฎหมายและจะมีข้อผิดพลาดทางไวยากรณ์
marcio

4
ขอขอบคุณพวกคุณสำหรับคำตอบจริงๆมันผิดพลาดแม้แต่ใน php 5.4 ดังนั้นไวยากรณ์ที่ใช้งานได้เฉพาะกับวิธีการของวัตถุ แก้ไขโพสต์
Lajos Meszaros


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

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

ความพยายามด้านบนนี้จะให้คำเตือนเกี่ยวกับวิธีการที่คาดว่าจะมีอาร์กิวเมนต์ที่สอง (เปอร์เซ็นต์)


"$ c = & CSS_Color ใหม่" ทำอะไร ฉันกำลังพูดเกี่ยวกับแอมป์ (&) สิ่งนี้ & เปลี่ยนแปลงอะไร
Danon

@danon & คือ "ที่อยู่ของ" หรือผู้ดำเนินการ "อ้างอิง" ฉันกำลังสร้างวัตถุใหม่ของคลาส CSS_Color แล้วกำหนดการอ้างอิง (ที่อยู่ aka) ให้กับ $ c ฉันเกลียดรหัสซ้ำดังนั้นฉันมักจะใช้ชื่อตัวแปร
Chris K

1
ฉันไม่เข้าใจ วัตถุไม่ได้ถูกส่งผ่านโดยการอ้างอิงทุกที่เลยเหรอ? ฉันหมายความว่าถ้าคุณส่ง $ c ไปยังฟังก์ชั่นและเปลี่ยนวัตถุเดิมจะได้รับผลกระทบด้วยใช่ไหม ไม่ว่าคุณจะใช้สิ่งนี้หรือไม่ฉันก็ใช่มั้ย
Danon

ฉันลองใช้สัญลักษณ์ต่าง ๆ กันและในขณะที่อ่านความคิดเห็นของผู้ใช้ใน php.org ฉันสามารถใช้รหัสได้ คุณจะต้องเริ่มการสนทนากับผู้ที่รับผิดชอบพฤติกรรมของ php
Chris K

สำหรับตัวอย่างที่สองที่คุณอ่านผิดคุณกำลังค้นหา call_user_func_array
Tofandel

13

ไม่กี่ปีที่ผ่านมา แต่นี่เป็นวิธีที่ดีที่สุดในตอนนี้ imho:

$x = (new ReflectionFunction("foo"))->getClosure();
$x();

3
หนทางสู่ OOP สำหรับฉัน แต่ฉันให้ +1 เพราะยังไม่มีใครพูดถึงชั้นเรียน Reflection
Lajos Meszaros

ไม่เข้าใจคำตอบนี้ มันแก้ปัญหาอะไรกันแน่?
David Spector

10

เพื่อความสมบูรณ์คุณสามารถใช้eval () :

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

อย่างไรก็ตามcall_user_func()เป็นวิธีที่เหมาะสม


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>หมายถึงนิพจน์ที่สร้างชื่อฟังก์ชัน

การเรียกใช้ฟังก์ชั่นแบบไดนามิก: ชื่อฟังก์ชัน On-the-fly

เราสามารถใช้หนึ่งหรือมากกว่าหนึ่งนิพจน์ในวงเล็บเป็นชื่อฟังก์ชั่นในครั้งเดียวในรูปแบบของ:

(<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นี้


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

    เรียน John, สวัสดี!

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

    เรียนซาร่าห์คุณสบายดีไหม

บายบาย!



2

รหัสต่อไปนี้สามารถช่วยในการเขียนฟังก์ชั่นแบบไดนามิกใน PHP ตอนนี้ชื่อฟังก์ชั่นสามารถเปลี่ยนแปลงได้แบบไดนามิกโดยตัวแปร '$ current_page'

$current_page = 'home_page';
$function = @${$current_page . '_page_versions'};
$function = function() {
    echo 'current page';
};
$function();

ฟังก์ชั่นนิรนาม (เช่นฟังก์ชั่นไดนามิก) แตกต่างจากฟังก์ชั่นตัวแปร (เช่นฟังก์ชั่นการโทรแบบไดนามิก) นอกจากนี้คุณกำลังแทนที่$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

วิธีการที่แปลกใหม่อย่างหนึ่งที่อยู่ในใจของฉันคือถ้าคุณไม่ได้สร้างรหัสทั้งหมดผ่าน AI อิสระพิเศษที่เขียนตัวเองมีโอกาสสูงที่ฟังก์ชั่นที่คุณต้องการเรียกใช้ "แบบไดนามิก" ถูกกำหนดไว้แล้วในรหัสของคุณ ฐาน. ดังนั้นทำไมไม่เพียงแค่ตรวจสอบสตริงและทำที่น่าอับอายifelseเต้นรำที่เพื่อเรียก ... คุณได้รับจุดของฉัน

เช่น.

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

switch-caseสามารถใช้ได้แม้กระทั่งถ้าคุณไม่ชอบรสชาติที่นุ่มนวลของifelseบันได

ฉันเข้าใจว่ามีบางกรณีที่ "การเรียกใช้ฟังก์ชันแบบไดนามิก " จะเป็นสิ่งจำเป็นอย่างยิ่ง ( เช่นตรรกะแบบเรียกซ้ำซึ่งแก้ไขตัวเอง ) แต่กรณีการใช้งานเล็กน้อยในชีวิตประจำวันส่วนใหญ่สามารถหลบได้

มันกำจัดความไม่แน่นอนจำนวนมากออกจากแอปพลิเคชันของคุณในขณะที่ให้โอกาสคุณเรียกใช้ฟังก์ชันทางเลือกหากสตริงไม่ตรงกับคำจำกัดความของฟังก์ชันที่มี IMHO


หากค่าของตัวแปรเป็นชื่อของฟังก์ชันอยู่แล้วเหตุใดจึงต้องทำงานพิเศษ นอกจากนี้มันจะเป็นการดีที่จะได้รับผลตอบรับที่ดีเพื่อดูว่าฟังก์ชั่นนั้นมีอยู่โดย php ก่อนที่คุณจะรันหรือไม่: if (method_exists($functionName)) $functionName(); else //exception or handle
Necro

2
Yup จุดของคุณถูกต้อง แต่เนื่องจากชื่อฟังก์ชั่นเป็นแบบไดนามิกในตรรกะของคุณสตริงใด ๆ ที่จะมาจะถูกดำเนินการหากฟังก์ชั่นโดยชื่อที่มีอยู่โดยไม่คำนึงถึงถ้าฟังก์ชั่นที่เกี่ยวข้องในสถานการณ์ปัจจุบันหรือไม่ วิธีการของฉันทำให้แน่ใจว่าสามารถใช้งานฟังก์ชันบางอย่างได้อย่างอื่นและสามารถโยนข้อผิดพลาดได้ มันสามารถทำให้ง่ายขึ้นโดยอาร์เรย์และเช่น
Mohd Abdul Mujib

-10

ฉันไม่รู้ว่าทำไมต้องใช้สิ่งนั้นฟังดูไม่ดีสำหรับฉันเลย แต่ถ้ามีฟังก์ชั่นเพียงเล็กน้อยคุณสามารถใช้ a / if ถ้าสร้าง ฉันไม่รู้ว่าจะสามารถแก้ปัญหาโดยตรงได้หรือไม่

บางอย่างเช่น $ foo = "bar"; $ test = "foo"; echo $$ test;

ควรกลับแถบคุณสามารถลอง แต่ฉันไม่คิดว่ามันจะทำงานสำหรับฟังก์ชั่น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.