즉석에서 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을 사용하지 마십시오. 개체의 동작을 동적으로 변경하는 것은 코드를 읽을 수없고 유지 관리 할 수 ​​없게 만드는 매우 쉬운 방법입니다.
GordonM

이 도구를 사용할 수 있습니다. 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)
Pekka

2
@pekka-그러나 정확히 어떻게, 그것은 kludgy입니까? 물론, 내가 PHP에서 런타임 동안 객체를 확장하는 전문가라고 말하는 것은 아니지만 솔직히 내가 그것에 대해 많이 잘못 보았다고 말할 수는 없습니다. (아마도 맛이 좋지 않을 수도 있습니다)
karim79

16
나는 is_callable 체크를 추가 할 것입니다 (그러므로 실수로 문자열을 호출하지 마십시오). 또한 메서드를 찾을 수없는 경우 BadMethodCallException ()을 throw하여 메서드가 반환되지 않고 성공적으로 반환되었다고 생각합니다. 또한 함수의 첫 번째 매개 변수를 객체 자체로 만든 다음 array_unshift($args, $this);메소드 호출 전에 수행 하여 함수가 명시 적으로 바인딩 할 필요없이 객체에 대한 참조를 얻도록합니다 ...
ircmaxell

3
사람들이 요점을 놓치고 있다고 생각합니다. 원래 요구 사항은 클래스없는 객체를 사용하는 것이 었습니다.
토마스 - 피터

1
실제로 isset () 테스트를 완전히 제거하는 것이 좋습니다. 해당 함수가 정의되어 있지 않으면 자동으로 반환하고 싶지 않기 때문입니다.
Alexis Wilke 2013 년


20

런타임에 새 메서드를 추가 할 수 있도록 단순히 __call을 사용하면 해당 메서드가 $ this 인스턴스 참조를 사용할 수 없다는 주요 단점이 있습니다. 추가 된 메서드가 코드에서 $ 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을 사용하여이 작업을 수행하는 방법을 보려면 github에서 사용할 수있는 PHP 마이크로 프레임 워크 인 Halcyon을 살펴보십시오 . 문제없이 알아낼 수있을만큼 충분히 작습니다. HalcyonClassMunger 클래스에 집중하세요.


나는 당신이 PHP <5.3.0에서 실행 중이고 따라서이 작업을 수행하기 위해 kludges와 해킹이 필요하다고 가정하고 있습니다.
ivans


아마도 여기에 대규모 반대 투표가있을 것입니다. 하지만 당신이 한 일에 대해 좀 더 자세히 설명하고 싶습니까? 보시다시피 아직 명확한 답이없는 흥미로운 질문입니다.
Pekka

1

__call솔루션이 없으면 bindTo(PHP> = 5.4)를 사용 $this하여 다음 $me과 같이 bound to 메서드를 호출 할 수 있습니다 .

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 에 있습니다.

유일한 다른 방법은 실험적인 PHP 확장 인 classkit을 사용하는 것입니다 . (또한 게시물)

예, 정의 된 후에 PHP 클래스에 메소드를 추가 할 수 있습니다. "실험용"확장 인 classkit을 사용하려고합니다. 그러나이 확장은 기본적으로 활성화되지 않은 것으로 보이므로 Windows에서 사용자 지정 PHP 바이너리를 컴파일하거나 PHP DLL을로드 할 수 있는지 여부에 따라 다릅니다 (예 : Dreamhost는 사용자 지정 PHP 바이너리를 허용하며 설정이 매우 쉽습니다. ).


0

karim79 답변은 작동하지만 메서드 속성 내부에 익명 함수를 저장하며 그의 선언은 동일한 이름의 기존 속성을 덮어 쓸 수 있거나 기존 속성이 private치명적인 오류 개체를 사용할 수없는 경우 작동하지 않습니다 . 별도의 배열에 저장하고 setter를 사용하는 것이 더 깨끗한 솔루션이라고 생각합니다. 방법 인젝터 세터가 자동으로 사용하는 모든 객체에 추가 할 수있는 특성을 .

추신 물론 이것은 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.