2008-09-26 8 views
37

일반적인 상속을 사용하지 않고 클래스 나 그 메서드 중 일부를 재정의하는 방법이 있습니까? 예 :클래스 메서드 또는 클래스 다시 정의

class third_party_library { 
    function buggy_function() { 
     return 'bad result'; 
    } 
    function other_functions(){ 
     return 'blah'; 
    } 
} 

buggy_function()을 대체하려면 어떻게해야합니까? 분명히 이것은 내가

class third_party_library redefines third_party_library{ 
    function buggy_function() { 
     return 'good result'; 
    } 
    function other_functions(){ 
     return 'blah'; 
    } 
} 

이 내 정확한 딜레마 할 싶은 것이있다 : 내 코드를 나누기 타사 라이브러리를 업데이트했습니다. 앞으로의 업데이트로 인해 코드가 다시 손상 될 수 있으므로 라이브러리를 직접 수정하고 싶지는 않습니다. 클래스 메서드를 바꿀 수있는 완벽한 방법을 찾고 있습니다.

나는 이것을 할 수 있다고 말하는이 library을 발견했으나 4 살 때 조심 스럽다.

편집 :

내가 magical_third_party_library 또는 때문에 프레임 워크 제한의 무엇에 third_party_library에서 클래스 이름을 바꿀 수 있다는 것을 명확히해야

.

제 목적을 위해 클래스에 함수를 추가하는 것이 가능합니까? C#에서는 "부분 클래스"라고하는 뭔가를 사용하여이 작업을 수행 할 수 있다고 생각합니다.

+0

PHP는 이것을 지원하지 않습니다. 클래스를 확장하고 다시 사용할 수 있습니다. 그게 다야. 죄송합니다. – Till

+0

원래 버그가있는 클래스의 이름을 바꿀 수 있습니까? buggy_third_party로 이름을 변경하고 클래스를 원래 이름으로 지정하여 직접 확장하십시오. – gnud

답변

36

monkey patching입니다. 그러나 PHP에는 기본 지원이 없습니다.

다른 사람들도 지적한 것처럼 runkit library은 언어 지원을 추가 할 수 있으며 classkit의 후속입니다. 그리고 작성자가 abandoned 인 것처럼 보였지만 (PHP 5.2 이상과 호환되지 않는다고 말한)이 프로젝트는 이제 new home and maintainer 인 것으로 보입니다.

여전히 접근 방식이 can't say I'm a fan입니다. 코드 문자열을 평가하여 수정하면 잠재적으로 위험하고 디버깅하기가 어려워 보입니다.

여전히 runkit_method_redefine 당신을 위해 무엇을 찾고있는 것으로 보인다, 그 사용의 예는 저장소에 /tests/runkit_method_redefine.phpt에서 찾을 수 있습니다 : 예, 그것은 extend

runkit_method_redefine('third_party_library', 'buggy_function', '', 
    'return \'good result\'' 
); 
+0

이것은 대답으로 받아 들여졌습니다 : PHP가 지원한다는 의미입니까? 좀 더 많은 정보가 도움이 될 것입니다. – nickf

+0

@nickf : "나는 믿지 않는다"는 부정입니다. 따라서 OP는 "PHP에서 지원되지 않습니다"를 의미합니다. 내가 말할 수있는 한 ** PHP는 원숭이 패치 **를 지원하지 않습니다. – Piskvor

+0

@Piskvor, 그렇습니다 - 읽을 수는 있지만,이 답변이 어떤 형태의 지침을 제공하는 다른 답변에 비해 받아 들여 졌는지 궁금했습니다. – nickf

1

젠드 스튜디오와 PDT (일식 기반)에는 굴절 공구가 내장되어 있습니다. 그러나 이것을 수행 할 방법이 없습니다.

또한 시스템에 전혀 문제가없는 것은 아닙니다. 그것은 실수로 부름받을 수 있기 때문입니다.

9

를 불렀다 :

<?php 
class sd_third_party_library extends third_party_library 
{ 
    function buggy_function() { 
     return 'good result'; 
    } 
    function other_functions(){ 
     return 'blah'; 
    } 
} 

"sd"가 접두사입니다. ;-)

클래스를 확장하여 메서드를 재정의 할 때 메서드의 서명이 원본과 일치해야합니다. 예를 들어 원본이 buggy_function($foo, $bar)이라고하면 클래스를 확장하는 매개 변수와 일치해야합니다.

PHP는 매우 장황합니다.

+2

그게 정상적으로 작동하지만 프레임 워크 내가 사용하는 설정 때문에 클래스 이름을 변경할 수 없습니다 – SeanDowney

+4

다음 대답은, 당신 수 없습니다. – Till

+0

왜 나는 downvoted을 얻고 있습니까? =) – Till

2

새롭고 적절한 메소드로 클래스를 확장하고 버그가있는 클래스 대신 클래스를 호출하는 경우가 있습니다.(엉터리 형식 죄송합니다)

class my_better_class Extends some_buggy_class { 
    function non_buggy_function() { 
     return 'good result'; 
    } 
} 

라이브러리가 명시 적으로 나쁜 클래스를 작성하고 당신은 운이되는 위치 표시 또는 종속 시스템을 사용하지 않는 경우

+0

질문을 읽었습니까? 그는이 솔루션을 사용할 수 없다는 것에 대해 분명했습니다. –

+1

은 중요하지 않습니다. 도움이됩니다. : P –

0

. 하위 클래스를 제외하고는 다른 클래스의 메소드를 재정의 할 수있는 방법이 없습니다. 해결 방법은 라이브러리를 수정하는 패치 파일을 만드는 것일 수 있으므로 라이브러리를 업그레이드하고 패치를 다시 적용하여 특정 방법을 수정할 수 있습니다.

10

완벽을 기하기 위해 - 원숭이 패치는 PHP에서 runkit을 통해 제공됩니다. 자세한 내용은 runkit_method_redefine()을 참조하십시오.

+1

이 질문에 대한 답변을 추가해 주셔서 감사합니다! – nickf

8

어떻게

class Wrapper { 
private $third_party_library; 
function __construct() { $this->third_party_library = new Third_party_library(); } 
function __call($method, $args) { 
    return call_user_func_array(array($this->third_party_library, $method), $args); 
} 
} 
+0

이것은 메이저 클래스를 배치하기에 적합합니다. 원래 PHP 변수를 생성자에 전달하고 $ this로 바꿀 수도 있습니다. (주요 클래스에 편리합니다. 인스턴스가 많지 않으므로 편리하지 않습니다.) – GDmac

14

runkit 같은 다른 클래스에서 포장에 대한 좋은 해결책처럼 보인다하지만 기본적으로 사용하고 일부는 아직 실험되는 것은 아닙니다. 그래서 클래스 파일의 함수 정의를 대체하는 작은 클래스를 해킹했습니다. 사용 예제 :

class Patch { 

private $_code; 

public function __construct($include_file = null) { 
    if ($include_file) { 
     $this->includeCode($include_file); 
    } 
} 

public function setCode($code) { 
    $this->_code = $code; 
} 

public function includeCode($path) { 

    $fp = fopen($path,'r'); 
    $contents = fread($fp, filesize($path)); 
    $contents = str_replace('<?php','',$contents); 
    $contents = str_replace('?>','',$contents); 
    fclose($fp);   

    $this->setCode($contents); 
} 

function redefineFunction($new_function) { 

    preg_match('/function (.+)\(/', $new_function, $aryMatches); 
    $func_name = trim($aryMatches[1]); 

    if (preg_match('/((private|protected|public) function '.$func_name.'[\w\W\n]+?)(private|protected|public)/s', $this->_code, $aryMatches)) { 

     $search_code = $aryMatches[1]; 

     $new_code = str_replace($search_code, $new_function."\n\n", $this->_code); 

     $this->setCode($new_code); 

     return true; 

    } else { 

     return false; 

    } 

} 

function getCode() { 
    return $this->_code; 
} 
} 

그런 다음 클래스를 포함하는 수정 및 메서드를 재정의합니다 : 작동

eval($objPatch->getCode()); 

약간의 원유하지만 :

$objPatch = new Patch('path_to_class_file.php'); 
$objPatch->redefineFunction(" 
    protected function foo(\$arg1, \$arg2) 
    { 
     return \$arg1+\$arg2; 
    }"); 

그런 다음 새 코드를 평가 후면!

+0

여행입니다! eval 문은 대체 된 함수로 클래스를 정의한다. 그래서 내가 할 필요가있는 것은 객체를 인스턴스화하고 그것을 실행하는 것뿐입니다. – SeanDowney

+1

예세르! 그게 당신이해야 할 전부입니다. – JPhilly

+0

@SeanDowney, 이것은 거의 사실로 쉬운 것처럼 보입니다. 이 수업은 박스에서 작동합니까? 예상 결과를 얻었습니까 @SeanDowney? 내 인생을 좀더 편하게 해줄 수있을거야. – Theunis

0

클래스 이름을 제외하고 모두 라이브러리 클래스의 복사본을 만들 수 있습니다. 그런 다음 이름이 바뀐 클래스를 재정의하십시오.

완벽하지는 않지만 확장 클래스의 변경 사항에 대한 가시성이 향상됩니다. Composer와 같은 라이브러리를 가져 오는 경우, 라이브러리를 업데이트 할 때 소스 컨트롤에 복사본을 커밋하고 업데이트해야합니다.

제 경우에는 https://github.com/bshaffer/oauth2-server-php의 구버전이었습니다. 대신 라이브러리의 오토로더를 수정하여 클래스 파일을 가져 왔습니다. 내 클래스 파일은 원래 이름을 가져 와서 파일 중 하나의 복사 된 버전을 확장했습니다.

2

아직이 답변을 찾는 사람들에게

namespaces과 조합하여 extends을 사용해야합니다.당신은 항상 PHP의 기본 코드에 액세스 할 수 있기 때문에 재정의 할 기본 클래스 함수를 재정의,

use MyCustomName\third_party_library; 

$test = new third_party_library(); 
$test->buggy_function(); 
//or static. 
third_party_library::other_functions(); 
+0

이 흥미로운 솔루션입니다. 유일한 단점은 내가 사용하는 장소에서 원래 클래스의 동작을 변경해야 할 때가되는 것입니다. –

0

이 같은

:

다음
namespace MyCustomName; 

class third_party_library extends \third_party_library { 
    function buggy_function() { 
     return 'good result'; 
    } 
    function other_functions(){ 
     return 'blah'; 
    } 
} 

는이 좋아해요 사용 다음과 같이 인터페이스가 손상되지 않도록해야합니다.

class third_party_library { 
    public static $buggy_function; 
    public static $ranOnce=false; 

    public function __construct(){ 
     if(!self::$ranOnce){ 
      self::$buggy_function = function(){ return 'bad result'; }; 
      self::$ranOnce=true; 
     } 
     . 
     . 
     . 
    } 
    function buggy_function() { 
     return self::$buggy_function(); 
    }   
} 

te 변수를 사용할 수 있지만 클래스 내부의 클래스 나 로직을 확장하여 함수에 액세스 할 수 있습니다. 마찬가지로 같은 클래스의 서로 다른 객체를 갖고 싶을 수도 있습니다. 그렇다면 정적을 사용하지 마라. 보통 정적 인 것이기를 바란다. 그래서 당신은 각 객체에 대한 메모리 사용을 복제하지 않는다. 'ranOnce'코드는 모든 클래스가 아닌 클래스에 대해 한 번만 초기화하면됩니다. $myObject = new third_party_library()

나중에 코드 또는 다른 클래스에서 나중에 로직이 재정의해야 할 지점에 도달 할 때마다 기능 - 다음과 같이 간단하게 수행

$backup['buggy_function'] = third_party_library::$buggy_function; 
third_party_library::$buggy_function = function(){ 
    //do stuff 
    return $great_calculation; 
} 
. 
. 
. //do other stuff that needs the override 
. //when finished, restore the original function 
. 
third_party_library::$buggy_function=$backup['buggy_function']; 

을 측면 참고로,이 방법을 모든 클래스의 기능을하고 public static $functions['function_name'] = function(...){...}; 같은 문자열 기반 키/값 저장소를 사용하는 경우이 반사 유용 할 수 있습니다. 이미 클래스와 함수 이름을 얻을 수 있기 때문에 다른 언어와 마찬가지로 PHP에서는 그렇지 않지만 일부 처리를 저장하면 나중에 클래스의 사용자가 PHP에서 재정의를 사용할 수 있습니다. 그러나 그것은 간접 지정의 추가 레벨이므로 가능하면 원시 클래스에서는 사용하지 않는 것이 좋습니다.