Jak dodać nową metodę do obiektu php w locie?

98

Jak dodać nową metodę do obiektu „w locie”?

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

//Fatal error: Call to undefined method stdClass::doSomething()
MadBad
źródło
Czy to konkretnie bez użycia klasy?
thomas-peter,
1
Możesz użyć __call. Ale proszę nie używać __call. Dynamiczna zmiana zachowania obiektu to bardzo łatwy sposób na uczynienie kodu nieczytelnym i niemożliwym do utrzymania.
GordonM,
Możesz użyć tego narzędzia: packagist.org/packages/drupol/dynamicobjects
Pol Dellaiera

Odpowiedzi:

94

Możesz to wykorzystać __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();
karim79
źródło
6
Bardzo, bardzo sprytny, ale niezdarny w prawdziwym projekcie IMO! (i +1 do kontrataku anonimowego przeciwnika)
Pekka
2
@pekka - ale jak właściwie to jest niezdarne? Jasne, nie twierdzę, że jestem ekspertem w rozszerzaniu obiektu w PHP w czasie wykonywania, ale szczerze mówiąc, nie mogę powiedzieć, że widzę w tym wiele złego. (może po prostu mam kiepski gust)
karim79
16
Dodałbym is_callable check in that if (Więc nie próbujesz przypadkowo wywołać ciągu). A także wyrzuć BadMethodCallException (), jeśli nie możesz znaleźć metody, więc nie musisz jej zwracać i myślisz, że powróciła pomyślnie. Ponadto, uczyń pierwszy parametr funkcji samym obiektem, a następnie wykonaj procedurę array_unshift($args, $this);przed wywołaniem metody, aby funkcja otrzymała odniesienie do obiektu bez konieczności jawnego wiązania go ...
ircmaxell
3
Myślę, że ludzie nie mają sensu, pierwotnym wymaganiem było użycie obiektu bezklasowego.
thomas-peter
1
Właściwie sugerowałbym całkowite usunięcie testu isset (), ponieważ jeśli ta funkcja nie jest zdefiniowana, prawdopodobnie nie chcesz wracać po cichu.
Alexis Wilke,
50

W PHP 7 możesz używać anonimowych klas

$myObject = new class {
    public function myFunction(){}
};

$myObject->myFunction();

PHP RFC: Anonimowe klasy

Mohamed Ramrami
źródło
20

Używanie prostego __call w celu umożliwienia dodawania nowych metod w czasie wykonywania ma tę główną wadę, że te metody nie mogą używać odwołania $ this do instancji. Wszystko działa świetnie, dopóki dodane metody nie używają $ this w kodzie.

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

Aby rozwiązać ten problem, możesz przepisać wzór w ten sposób

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

W ten sposób możesz dodać nowe metody, które mogą używać odwołania do instancji

$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"
alexroat
źródło
12

Aktualizacja: Przedstawione tutaj podejście ma poważną wadę: nowa funkcja nie jest w pełni wykwalifikowanym członkiem klasy; $thisnie występuje w metodzie, gdy jest wywoływana w ten sposób. Oznacza to, że musisz przekazać obiekt do funkcji jako parametr, jeśli chcesz pracować z danymi lub funkcjami z instancji obiektu! Ponadto nie będzie można uzyskać dostępu privateani protectedczłonków klasy z tych funkcji.

Dobre pytanie i sprytny pomysł z wykorzystaniem nowych anonimowych funkcji!

Co ciekawe, to działa: Wymień

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

przez call_user_func na samej funkcji :

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

to, co nie działa, to „właściwy” sposób:

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

jeśli tak się nazywa, PHP wymaga zadeklarowania metody w definicji klasy.

Czy jest to problem z widocznością private/ public/ protected?

Aktualizacja: nie . Niemożliwe jest wywołanie funkcji w normalny sposób, nawet z poziomu klasy, więc nie jest to problem z widocznością. Przekazanie faktycznej funkcji call_user_func()to jedyny sposób, w jaki mogę to zrobić.

Pekka
źródło
2

możesz także zapisać funkcje w tablicy:

<?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);
?>
miglio
źródło
1

Aby zobaczyć, jak to zrobić za pomocą eval, możesz rzucić okiem na mój mikro-framework PHP, Halcyon, który jest dostępny na github . Jest na tyle mały, że powinieneś być w stanie to rozgryźć bez żadnych problemów - skoncentruj się na klasie HalcyonClassMunger.

ivans
źródło
Zakładam (podobnie jak mój kod), że pracujesz na PHP <5.3.0 i dlatego potrzebujesz kludges i hacków, aby to zadziałało.
ivans
Pytanie, czy ktoś chciałby skomentować głos przeciw nie ma sensu, przypuszczam ...
ivans
Prawdopodobnie tak jest, że dzieje się tutaj masowe przegłosowanie. Ale może chcesz trochę rozwinąć to, co zrobiłeś? Jak widać, jest to interesujące pytanie, na które nie ma jeszcze określonej odpowiedzi.
Pekka
1

Bez __callrozwiązania możesz użyć bindTo(PHP> = 5.4), aby wywołać metodę z $thispowiązaniem w $menastępujący sposób:

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

Kompletny skrypt mógłby wyglądać następująco:

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

Alternatywnie możesz przypisać powiązaną funkcję do zmiennej, a następnie wywołać ją bez call_user_func:

$f = $me->doSomething->bindTo($me);
$f(); 
trincot
źródło
0

Jest podobny post w stackoverflow, który wyjaśnia, że ​​jest to osiągalne tylko poprzez implementację pewnych wzorców projektowych.

Jedynym innym sposobem jest użycie classkit , eksperymentalnego rozszerzenia php. (również w poście)

Tak, istnieje możliwość dodania metody do klasy PHP po jej zdefiniowaniu. Chcesz użyć zestawu klas, który jest rozszerzeniem „eksperymentalnym”. Wygląda na to, że to rozszerzenie nie jest domyślnie włączone, więc zależy to od tego, czy możesz skompilować niestandardowy plik binarny PHP lub załadować biblioteki DLL PHP w systemie Windows (na przykład Dreamhost zezwala na niestandardowe pliki binarne PHP i są one dość łatwe do skonfigurowania ).

Zilverdistel
źródło
0

odpowiedź karim79 działa, ale przechowuje anonimowe funkcje we właściwościach metody, a jego deklaracje mogą nadpisywać istniejące właściwości o tej samej nazwie lub nie działa, jeśli istniejąca właściwość privatepowoduje błąd krytyczny, obiekt jest bezużyteczny. metodę ustawiającą wtryskiwacz można dodać do każdego obiektu automatycznie przy użyciu cech .

PS Oczywiście jest to hack, którego nie należy używać, ponieważ narusza on zasadę open closed 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);
Roman Toasov
źródło
Jeśli jest to hack, którego nie należy używać, nie należy go publikować jako odpowiedzi.
miken32