Bagaimana cara menambahkan metode baru ke objek php dengan cepat?


98

Bagaimana cara menambahkan metode baru ke objek "dengan cepat"?

$me= new stdClass;
$me->doSomething=function ()
 {
    echo 'I\'ve done something';
 };
$me->doSomething();

//Fatal error: Call to undefined method stdClass::doSomething()

Apakah ini secara khusus tanpa menggunakan kelas?
Thomas-peter

1
Anda bisa menggunakan __call. Tapi tolong jangan gunakan __call. Mengubah perilaku objek secara dinamis adalah cara yang sangat mudah untuk membuat kode Anda tidak dapat dibaca dan dipertahankan.
GordonM

Jawaban:


94

Anda dapat memanfaatkan __calluntuk ini:

class Foo
{
    public function __call($method, $args)
    {
        if (isset($this->$method)) {
            $func = $this->$method;
            return call_user_func_array($func, $args);
        }
    }
}

$foo = new Foo();
$foo->bar = function () { echo "Hello, this function is added at runtime"; };
$foo->bar();

6
Sangat, sangat pintar tapi kludgy dalam proyek dunia nyata IMO! (dan +1 untuk melawan downvoter anonim)
Pekka

2
@pekka - tapi bagaimana tepatnya, itu kludgy? Tentu, saya tidak mengatakan saya ahli dalam memperluas objek selama runtime di PHP, tapi jujur ​​saya tidak bisa mengatakan saya melihat banyak yang salah dengannya. (mungkin saya hanya memiliki selera yang buruk)
karim79

16
Saya akan menambahkan check is_callable jika juga (Jadi Anda tidak sengaja mencoba memanggil string). Dan juga lemparkan BadMethodCallException () jika Anda tidak dapat menemukan metode, sehingga Anda tidak mengembalikannya dan menganggapnya berhasil kembali. Juga, buat parameter pertama fungsi sebagai objek itu sendiri, lalu lakukan array_unshift($args, $this);pemanggilan metode sebelum, sehingga fungsi tersebut mendapatkan referensi ke objek tanpa perlu mengikatnya secara eksplisit ...
ircmaxell

3
Saya pikir orang kehilangan intinya, persyaratan aslinya menggunakan objek tanpa kelas.
Thomas-peter

1
Saya benar-benar menyarankan agar Anda sepenuhnya menghapus tes isset () karena jika fungsi itu tidak ditentukan, Anda mungkin tidak ingin kembali secara diam-diam.
Alexis Wilke


20

Menggunakan hanya __call untuk mengizinkan penambahan metode baru pada waktu proses memiliki kelemahan utama bahwa metode tersebut tidak dapat menggunakan $ referensi instance $ this. Semuanya bekerja dengan baik, hingga metode yang ditambahkan tidak menggunakan $ this dalam kode.

class AnObj extends stdClass
{
    public function __call($closure, $args)
    {
        return call_user_func_array($this->{$closure}, $args);
    }
 }
 $a=new AnObj();
 $a->color = "red";
 $a->sayhello = function(){ echo "hello!";};
 $a->printmycolor = function(){ echo $this->color;};
 $a->sayhello();//output: "hello!"
 $a->printmycolor();//ERROR: Undefined variable $this

Untuk mengatasi masalah ini, Anda dapat menulis ulang pola tersebut dengan cara ini

class AnObj extends stdClass
{
    public function __call($closure, $args)
    {
        return call_user_func_array($this->{$closure}->bindTo($this),$args);
    }

    public function __toString()
    {
        return call_user_func($this->{"__toString"}->bindTo($this));
    }
}

Dengan cara ini Anda dapat menambahkan metode baru yang dapat menggunakan referensi instance

$a=new AnObj();
$a->color="red";
$a->sayhello = function(){ echo "hello!";};
$a->printmycolor = function(){ echo $this->color;};
$a->sayhello();//output: "hello!"
$a->printmycolor();//output: "red"

12

Pembaruan: Pendekatan yang ditampilkan di sini memiliki kekurangan utama: Fungsi baru bukan anggota kelas yang sepenuhnya memenuhi syarat; $thistidak ada dalam metode saat dipanggil dengan cara ini. Ini berarti Anda harus meneruskan objek ke fungsi sebagai parameter jika Anda ingin bekerja dengan data atau fungsi dari instance objek! Selain itu, Anda tidak akan dapat mengakses privateatau protectedanggota kelas dari fungsi ini.

Pertanyaan bagus dan ide cerdas menggunakan fungsi anonim baru!

Menariknya, ini berhasil: Ganti

$me->doSomething();    // Doesn't work

oleh call_user_func pada fungsi itu sendiri :

call_user_func($me->doSomething);    // Works!

apa yang tidak berhasil adalah cara yang "benar":

call_user_func(array($me, "doSomething"));   // Doesn't work

jika dipanggil seperti itu, PHP membutuhkan metode untuk dideklarasikan dalam definisi kelas.

Apakah ini private/ public/ protectedvisibilitas masalah?

Pembaruan: Tidak. Tidak mungkin memanggil fungsi dengan cara normal bahkan dari dalam kelas, jadi ini bukan masalah visibilitas. Meneruskan fungsi sebenarnya ke call_user_func()adalah satu-satunya cara agar saya dapat membuat ini berfungsi.


2

Anda juga dapat menyimpan fungsi dalam array:

<?php
class Foo
{
    private $arrayFuncs=array();// array of functions
    //
    public function addFunc($name,$function){
        $this->arrayFuncs[$name] = $function;
    }
    //
    public function callFunc($namefunc,$params=false){
        if(!isset($this->arrayFuncs[$namefunc])){
            return 'no function exist';
        }
        if(is_callable($this->arrayFuncs[$namefunc])){
            return call_user_func($this->arrayFuncs[$namefunc],$params);
        }
    }

}
$foo = new Foo();

//Save function on array variable with params
$foo->addFunc('my_function_call',function($params){
    return array('data1'=>$params['num1'],'data2'=>'qwerty','action'=>'add');
});
//Save function on array variable
$foo->addFunc('my_function_call2',function(){
    return 'a simple function';
});
//call func 1
$data = $foo->callFunc('my_function_call',array('num1'=>1224343545));
var_dump($data);
//call func 2
$data = $foo->callFunc('my_function_call2');
var_dump($data);
?>

1

Untuk melihat bagaimana melakukan ini dengan eval, Anda dapat melihat kerangka mikro PHP saya, Halcyon, yang tersedia di github . Ini cukup kecil sehingga Anda harus dapat mengetahuinya tanpa masalah - berkonsentrasilah pada kelas HalcyonClassMunger.


Saya berasumsi (dan begitu juga kode saya) bahwa Anda menjalankan PHP <5.3.0 dan karenanya membutuhkan kludges dan hacks untuk membuat ini berfungsi.
ivans

Tidak ada gunanya bertanya apakah ada yang mau mengomentari downvote, saya kira ...
ivans

Mungkin saja, ada beberapa downvoting massal yang terjadi di sekitar sini. Tapi mungkin Anda ingin menjelaskan sedikit tentang apa yang Anda lakukan? Seperti yang Anda lihat, ini adalah pertanyaan menarik yang belum ada jawaban pasti.
Pekka

1

Tanpa __callsolusi, Anda dapat menggunakan bindTo(PHP> = 5.4), untuk memanggil metode dengan $thisterikat $meseperti ini:

call_user_func($me->doSomething->bindTo($me, null)); 

Skrip lengkapnya akan terlihat seperti ini:

$me = new stdClass;
// Property for proving that the method has access to the above object:
$me->message = "I\'ve done something"; 
$me->doSomething = function () {
    echo $this->message;
};
call_user_func($me->doSomething->bindTo($me)); // "I've done something"

Alternatifnya, Anda dapat menetapkan fungsi terikat ke variabel, lalu memanggilnya tanpa call_user_func:

$f = $me->doSomething->bindTo($me);
$f(); 

0

Ada posting serupa di stackoverflow yang menjelaskan bahwa ini hanya dapat dicapai melalui penerapan pola desain tertentu.

Satu-satunya cara lain adalah melalui penggunaan classkit , ekstensi php eksperimental. (juga di pos)

Ya, dimungkinkan untuk menambahkan metode ke kelas PHP setelah itu ditentukan. Anda ingin menggunakan classkit, yang merupakan ekstensi "eksperimental". Tampaknya ekstensi ini tidak diaktifkan secara default, jadi itu tergantung pada apakah Anda dapat mengkompilasi biner PHP kustom atau memuat DLL PHP jika di windows (misalnya Dreamhost mengizinkan binari PHP kustom, dan mereka cukup mudah untuk diatur ).


0

jawaban karim79 berfungsi namun menyimpan fungsi anonim di dalam properti metode dan deklarasinya dapat menimpa properti yang ada dengan nama yang sama atau tidak berfungsi jika properti yang ada privatemenyebabkan objek kesalahan fatal tidak dapat digunakan. Saya pikir menyimpannya dalam array terpisah dan menggunakan penyetel adalah solusi yang lebih bersih. metode penyetel injektor dapat ditambahkan ke setiap objek secara otomatis menggunakan sifat .

PS Tentu saja ini adalah hack yang tidak boleh digunakan karena melanggar prinsip open closed dari SOLID.

class MyClass {

    //create array to store all injected methods
    private $anon_methods = array();

    //create setter to fill array with injected methods on runtime
    public function inject_method($name, $method) {
        $this->anon_methods[$name] = $method;
    }

    //runs when calling non existent or private methods from object instance
    public function __call($name, $args) {
        if ( key_exists($name, $this->anon_methods) ) {
            call_user_func_array($this->anon_methods[$name], $args);
        }
    }

}


$MyClass = new MyClass;

//method one
$print_txt = function ($text) {
    echo $text . PHP_EOL;
};

$MyClass->inject_method("print_txt", $print_txt);

//method
$add_numbers = function ($n, $e) {
    echo "$n + $e = " . ($n + $e);
};

$MyClass->inject_method("add_numbers", $add_numbers);


//Use injected methods
$MyClass->print_txt("Hello World");
$MyClass->add_numbers(5, 10);

Jika itu adalah retasan yang tidak boleh digunakan, itu tidak boleh diposting sebagai jawaban.
miken32
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.