मक्खी पर php ऑब्जेक्ट में एक नई विधि कैसे जोड़ें?


98

मैं "फ्लाई पर" किसी ऑब्जेक्ट में एक नई विधि कैसे जोड़ूं?

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

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

क्या यह विशेष रूप से एक वर्ग का उपयोग किए बिना है?
थोमस-पेटर

1
आप __call का उपयोग कर सकते हैं। लेकिन कृपया __call का उपयोग न करें। गतिशील रूप से किसी वस्तु के व्यवहार को बदलना आपके कोड को अपठनीय और अस्वीकार्य बनाने का एक बहुत आसान तरीका है।
गॉर्डन

आप इस टूल का उपयोग कर सकते हैं: packagist.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
मुझे लगता है कि लोग इस बिंदु को याद कर रहे हैं, मूल आवश्यकता एक क्लासलेस ऑब्जेक्ट का उपयोग कर रही थी।
थोमस-पेटर

1
मैं वास्तव में सुझाव दूंगा कि आप पूरी तरह से isset () परीक्षण को हटा दें क्योंकि यदि वह फ़ंक्शन परिभाषित नहीं है, तो आप शायद चुपचाप वापस नहीं लौटना चाहते।
एलेक्सिस विल्के


20

क्रम में नए तरीकों को जोड़ने की अनुमति देने के लिए केवल __call का उपयोग करने से बड़ी खामी यह है कि उन तरीकों से $ इस उदाहरण संदर्भ का उपयोग नहीं किया जा सकता है। सब कुछ बहुत अच्छा काम करता है, जब तक कि जोड़े गए तरीके कोड में इस का उपयोग न करें।

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

यह स्पष्ट करने के लिए कि यह कैसे किया जाता है, यह देखने के लिए, आप मेरे PHP माइक्रो-फ्रेमवर्क, हेलसीओन को देख सकते हैं, जो लिथुब पर उपलब्ध है । यह काफी छोटा है कि आपको बिना किसी समस्या के इसका पता लगाने में सक्षम होना चाहिए - हैल्सी क्लोक्लास मुंगेर वर्ग पर ध्यान केंद्रित करें।


मैं मान रहा हूं (और इसलिए मेरा कोड) आप PHP <5.3.0 पर चल रहे हैं और इस काम को करने के लिए कूडल और हैक्स की आवश्यकता है।
ivans

यह पूछने पर कि क्या कोई टिप्पणी करना चाहेगा, व्यर्थ है, मुझे लगता है ...
ivans

यह शायद है, वहाँ कुछ बड़े पैमाने पर downvoting यहाँ चारों ओर जा रहा है। लेकिन हो सकता है कि आपने जो किया, उस पर थोड़ा विस्तार करना चाहते हैं? जैसा कि आप देख सकते हैं, यह एक दिलचस्प सवाल है जिसका अभी तक कोई निश्चित जवाब नहीं है।
पेक्का

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

स्टैकओवरफ़्लो पर एक समान पोस्ट है है जो स्पष्ट करता है कि यह केवल कुछ डिज़ाइन पैटर्न के कार्यान्वयन के माध्यम से प्राप्त करने योग्य है।

एक अन्य तरीका क्लासीकिट , एक प्रयोगात्मक php एक्सटेंशन के उपयोग के माध्यम से है । (पोस्ट में भी)

हाँ यह परिभाषित करने के बाद एक PHP वर्ग के लिए एक विधि जोड़ना संभव है। आप क्लासकिट का उपयोग करना चाहते हैं, जो "प्रयोगात्मक" विस्तार है। ऐसा प्रतीत होता है कि यह एक्सटेंशन डिफ़ॉल्ट रूप से सक्षम नहीं है, इसलिए यह इस बात पर निर्भर करता है कि क्या आप एक कस्टम PHP बाइनरी संकलित कर सकते हैं या विंडोज़ पर PHP DLL लोड कर सकते हैं (उदाहरण के लिए Dreamhost कस्टम PHP बायनेरिज़ की अनुमति देता है, और वे सेटअप करना बहुत आसान है )।


0

karim79 उत्तर काम करता है, हालांकि यह विधि गुणों के अंदर अनाम कार्यों को संग्रहीत करता है और उसकी घोषणाएं उसी नाम के मौजूदा गुणों को अधिलेखित कर सकती हैं या काम नहीं करती हैं यदि मौजूदा संपत्ति privateघातक त्रुटि का कारण बन रही है तो मुझे लगता है कि उन्हें अलग सरणी में संग्रहीत करना और सेटर का उपयोग करना क्लीनर समाधान है। विधि इंजेक्टर सेटर को हर वस्तु में स्वचालित रूप से लक्षणों का उपयोग करके जोड़ा जा सकता है

पीएस बेशक यह हैक है जिसका उपयोग नहीं किया जाना चाहिए क्योंकि यह एसओएलआईडी के खुले बंद सिद्धांत का उल्लंघन करता है।

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 Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.