Anında bir php nesnesine yeni bir yöntem nasıl eklenir?


98

Bir nesneye "anında" yeni bir yöntemi nasıl eklerim?

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

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

Bu özellikle bir sınıf kullanmadan mı?
thomas-peter

1
__Call kullanabilirsiniz. Ama lütfen __call kullanmayın. Bir nesnenin davranışını dinamik olarak değiştirmek, kodunuzu okunamaz ve sürdürülemez hale getirmenin çok kolay bir yoludur.
GordonM

Yanıtlar:


94

Bunun için koşabilirsiniz __call:

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
Gerçek bir dünya projesi olan IMO'da çok, çok zeki ama huysuz! (ve anonim olumsuz seçmenlere karşı +1)
Pekka

2
@pekka - ama tam olarak nasıl, kludgy? Elbette, PHP'de çalışma zamanı sırasında bir nesneyi genişletme konusunda uzman olduğumu söylemiyorum, ancak dürüst olmak gerekirse, onunla çok fazla yanlış gördüğümü söyleyemem. (belki sadece zevke
sahibimdir

16
Buna bir de is_callable kontrolü eklerdim (Yani yanlışlıkla bir dizge çağırmaya çalışmazsınız). Ayrıca bir yöntem bulamazsanız, bir BadMethodCallException () oluşturun, böylece geri dönmesine sahip olmazsınız ve başarılı bir şekilde döndüğünü düşünebilirsiniz. Ayrıca, işlevin ilk parametresini nesnenin kendisi yapın ve ardından array_unshift($args, $this);yöntem çağrısından önce bir tane yapın , böylece işlev nesneye açıkça bağlanmasına gerek kalmadan bir başvuru alır ...
ircmaxell,

3
Bence insanlar noktayı kaçırıyorlar, asıl gereksinim sınıfsız bir nesne kullanmaktı.
thomas-peter

1
Aslında isset () testini tamamen kaldırmanızı öneririm çünkü bu işlev tanımlanmamışsa, muhtemelen sessizce dönmek istemezsiniz.
Alexis Wilke


20

Çalışma zamanında yeni yöntemler eklemeye izin vermek için basitçe __call kullanmak, bu yöntemlerin $ this örnek başvurusunu kullanamaması gibi büyük bir dezavantaja sahiptir. Eklenen yöntemler kodda $ this kullanmayana kadar her şey harika çalışıyor.

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

Bu sorunu çözmek için kalıbı bu şekilde yeniden yazabilirsiniz.

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

Bu şekilde, örnek referansını kullanabilen yeni yöntemler ekleyebilirsiniz.

$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

Güncelleme: Burada gösterilen yaklaşımın büyük bir kusuru vardır: Yeni işlev, sınıfın tam nitelikli bir üyesi değildir; $thisbu şekilde çağrıldığında yöntemde mevcut değildir. Bu, nesne örneğinden veri veya işlevlerle çalışmak istiyorsanız nesneyi bir parametre olarak işleve geçirmeniz gerektiği anlamına gelir! Ayrıca, bu işlevlerden sınıfın üyelerine privateveya protectedüyelerine erişemezsiniz .

Yeni anonim işlevleri kullanarak iyi soru ve akıllıca fikir!

İlginç bir şekilde, bu işe yarıyor: Değiştir

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

call_user_func tarafından işlevin kendisinde :

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

işe yaramayan şey "doğru" yoldur:

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

bu şekilde çağrılırsa, PHP, yöntemin sınıf tanımında bildirilmesini gerektirir.

Bu bir private/ public/ protectedgörünürlük sorunu mu?

Güncelleme: Hayır. Sınıfın içinden bile işlevi normal şekilde çağırmak imkansızdır, bu nedenle bu bir görünürlük sorunu değildir. Gerçek işlevi devretmek call_user_func(), bunu çalıştırabilmemin tek yolu.


2

fonksiyonları bir diziye de kaydedebilirsiniz:

<?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

Eval ile bunun nasıl görmek için, geçerli benim PHP mikro çerçevede, Halcyon, bir göz alabilir github . Herhangi bir sorun yaşamadan çözebilmeniz için yeterince küçüktür - HalcyonClassMunger sınıfına odaklanın.


PHP <5.3.0 üzerinde çalıştığınızı ve bu yüzden bunu çalıştırmak için kludges ve hack'lere ihtiyacınız olduğunu varsayıyorum (ve benim kodum da öyle).
ivans

Olumsuz oy hakkında yorum yapmak isteyip istemediğinizi sormak anlamsız, sanırım ...
ivans

Muhtemelen buralarda kitlesel bir olumsuz oylama var. Ama belki ne yaptığınızı biraz detaylandırmak istersiniz? Gördüğünüz gibi, bu henüz kesin bir cevabı olmayan ilginç bir soru.
Pekka

1

Olmadan __callçözümü kullanabilirsiniz bindTo(PHP> = 5.4) ile yöntemini çağırmak için $thisbağlı $meböyle:

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

Komut dosyasının tamamı şöyle görünebilir:

$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"

Alternatif olarak, bağlı işlevi bir değişkene atayabilir ve ardından onu şun olmadan çağırabilirsiniz call_user_func:

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

0

Bir var stackoverflow benzer sonrası bu belli tasarım desenleri uygulanması yoluyla sadece ulaşılabilir olduğunu temizler.

Diğer tek yol, deneysel bir php uzantısı olan classkit'in kullanılmasıdır . (ayrıca gönderide)

Evet, tanımlandıktan sonra bir PHP sınıfına bir yöntem eklemek mümkündür. "Deneysel" bir uzantı olan classkit'i kullanmak istiyorsunuz. Görünüşe göre bu uzantı varsayılan olarak etkin değildir, bu nedenle özel bir PHP ikili dosyası derleyip derleyemeyeceğinize veya Windows üzerinde ise PHP DLL'leri yükleyip yükleyemeyeceğinize bağlıdır (örneğin Dreamhost özel PHP ikili dosyalarına izin verir ve bunların kurulumu oldukça kolaydır. ).


0

karim79 cevabı çalışır ancak anonim fonksiyonları metot özelliklerinde saklar ve bildirimleri aynı isimdeki mevcut özelliklerin üzerine yazabilir veya mevcut özellik privateölümcül hata nesnesinin kullanılamamasına neden oluyorsa çalışmaz.Bence bunları ayrı bir dizide depolamak ve setter kullanmak daha temiz bir çözümdür. yöntem enjektör ayarlayıcı özellikleri kullanılarak her nesneye otomatik olarak eklenebilir .

Not Tabii ki bu, SOLID'in açık kapalı ilkesini ihlal ettiği için kullanılmaması gereken bir hack'tir.

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

Kullanılmaması gereken bir hack ise, cevap olarak gönderilmemelidir.
miken32
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.