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()
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()
Jawaban:
Anda dapat memanfaatkan __call
untuk 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();
array_unshift($args, $this);
pemanggilan metode sebelum, sehingga fungsi tersebut mendapatkan referensi ke objek tanpa perlu mengikatnya secara eksplisit ...
Dengan PHP 7 Anda dapat menggunakan kelas anonim
$myObject = new class {
public function myFunction(){}
};
$myObject->myFunction();
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"
Pembaruan: Pendekatan yang ditampilkan di sini memiliki kekurangan utama: Fungsi baru bukan anggota kelas yang sepenuhnya memenuhi syarat;
$this
tidak 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 mengaksesprivate
atauprotected
anggota 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
/ protected
visibilitas 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.
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);
?>
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.
Tanpa __call
solusi, Anda dapat menggunakan bindTo
(PHP> = 5.4), untuk memanggil metode dengan $this
terikat $me
seperti 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();
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 ).
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 private
menyebabkan 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);