Як додати новий метод до об’єкту php на льоту?


98

Як додати новий метод до об’єкта "на льоту"?

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

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

Це конкретно без використання класу?
thomas-peter

1
Ви можете використовувати __call. Але, будь ласка, не використовуйте __call. Динамічна зміна поведінки об'єкта - це дуже простий спосіб зробити ваш код нечитабельним і нездійсненним.
GordonM

Ви можете скористатись цим інструментом: packgist.org/packages/drupol/dynamicobjects
Pol Dellaiera

Відповіді:


94

Ви можете використати __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
Дуже, дуже розумний, але химерний у реальному світі проект IMO! (і +1, щоб протистояти анонімному потоку)
Пекка

2
@pekka - але як саме це хитро? Звичайно, я не кажу, що я фахівець з розширення об'єкта під час виконання в PHP, але я, чесно кажучи, не можу сказати, що я бачу в цьому багато поганого. (Може бути , я просто поганий смак)
karim79

16
Я також додав би позначку is_callable, якщо це так (щоб ви випадково не намагалися викликати рядок). А також киньте BadMethodCallException (), якщо ви не можете знайти метод, щоб у вас не було цього повернення і думаєте, що він успішно повернувся. Крім того, зробіть перший параметр функції самого об'єкта, а потім зробіть array_unshift($args, $this);перед викликом методу, щоб функція отримала посилання на об'єкт без явного необхідності його прив'язувати ...
ircmaxell

3
Я думаю, що люди пропускають суть, первісною вимогою було використання об'єкта без класу.
thomas-peter

1
Я б фактично запропонував би ви повністю видалити тест isset (), тому що якщо ця функція не визначена, ви, ймовірно, не хочете мовчки повертатися.
Алексіс Вільке


20

Використання просто __call для того, щоб додати нові методи під час виконання, має головний недолік, що ці методи не можуть використовувати $ цей посилання на екземпляр. Все працює чудово, поки додані методи не використовують $ this у коді.

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

Для вирішення цієї проблеми ви можете переписати шаблон таким чином

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

Таким чином ви можете додати нові методи, які можуть використовувати посилання на екземпляр

$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

Оновлення: тут показаний підхід має головний недолік: Нова функція не є повноцінним членом класу; $thisпри використанні цього способу немає в методі. Це означає, що вам доведеться передати об'єкт функції як параметр, якщо ви хочете працювати з даними або функціями з екземпляра об'єкта! Крім того , ви не зможете отримати доступ privateабо protectedчлен класу з цих функцій.

Гарне запитання та розумна ідея з використанням нових анонімних функцій!

Цікаво, що це працює: Замінити

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

від call_user_func щодо самої функції :

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

те, що не працює - це "правильний" спосіб:

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

якщо викликано таким чином, PHP вимагає, щоб метод був оголошений у визначенні класу.

Це питання private/ public/ protectedвидимості?

Оновлення: Ні. Назвати функцію звичайним способом навіть із класу неможливо, тому це не проблема видимості. Передача фактичної функції до call_user_func()єдиного способу, як я можу зробити цю роботу.


2

Ви також можете зберегти функції в масиві:

<?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, ви можете подивитися на мій фреймворк PHP Halcyon, який доступний на github . Це досить мало, що ви зможете це зрозуміти без проблем - зосередитись на класі HalcyonClassMunger.


Я припускаю (так само і мій код), що ви працюєте на PHP <5.3.0 і тому вам потрібні шахраї та хаки, щоб зробити цю роботу.
ivans

Запитання, чи хотіли б хтось коментувати сутінг, - гадаю, ...
ivans

Це, мабуть, тут є якась масова боротьба. Але, можливо, ви хочете трохи детальніше розібратися в тому, що зробили? Як бачите, це цікаве питання, на який ще немає однозначної відповіді.
Пекка

1

Без __callрішення ви можете використовувати bindTo(PHP> = 5.4) для виклику методу з таким $thisприв’язкою $me:

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

Повний сценарій може виглядати так:

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

Крім того, ви можете призначити зв'язану функцію змінній, а потім викликати її без call_user_func:

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

0

Існує аналогічна публікація про stackoverflow, яка виявляє, що це можна досягти лише завдяки впровадженню певних моделей дизайну.

Єдиний інший спосіб - це використання classkit , експериментального розширення php. (також у дописі)

Так, можна додати метод до класу PHP після його визначення. Ви хочете використовувати classkit, який є "експериментальним" розширенням. Здається, що це розширення не включено за замовчуванням, тому це залежить від того, чи зможете ви скласти користувацький бінарний PHP або завантажити PHP DLL-файли, якщо у Windows (наприклад, Dreamhost дозволяє користувацькі бінарні файли PHP, і їх досить легко налаштувати ).


0

відповідь karim79 працює, однак він зберігає анонімні функції у властивостях методу, і його декларації можуть замінити існуючі однакові властивості або не працюють, якщо наявна властивість privateспричиняє фатальний помилку об'єкта, непридатного. метод інжектор-сетер може бути доданий до кожного об'єкта автоматично, використовуючи ознаки .

PS Звичайно, це хакер, який не слід застосовувати, оскільки він порушує відкритий закритий принцип 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);

Якщо це злом, який не слід використовувати, він не повинен розміщуватися як відповідь.
miken32
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.