Pergunta

Existe alguma maneira para redefinir uma classe ou alguns dos seus métodos sem usar herança típico? Por exemplo:

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

O que posso fazer para substituir buggy_function()? Obviamente, isso é o que eu gostaria de fazer

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

Este é o meu dilema exata: eu atualizei uma biblioteca de terceiros que quebra o meu código. Eu não deseja modificar a biblioteca diretamente, como as futuras atualizações poderiam quebrar o código novamente. Eu estou procurando uma maneira perfeita para substituir o método de classe.

Eu encontrei esta biblioteca que diz que pode fazê-lo, mas estou cauteloso como é de 4 anos de idade.

EDIT:

Eu deveria ter esclarecido que eu não posso mudar o nome da classe a partir third_party_library para magical_third_party_library ou qualquer outra coisa por causa das limitações estruturais.

Para os meus propósitos, seria possível apenas adicionar uma função para a classe? Eu acho que você pode fazer isso em C # com uma coisa chamada "classe parcial."

Foi útil?

Solução

É chamado macaco patching . Mas, PHP não tem suporte nativo para ele.

No entanto, como os outros também têm apontado, o href="http://docs.php.net/runkit" rel="noreferrer"> biblioteca está disponível para adicionar suporte para o idioma e é o sucessor Classkit . E, embora parecesse ter sido abandonado por seu criador (tendo afirmado que não era compatível com PHP 5.2 ou posterior), o projeto que agora parecem ter um nova casa e mantenedor .

não pode dizer que eu sou um fã de sua abordagem. Fazer modificações através da avaliação de strings de código sempre pareceu-me ser potencialmente perigosos e difíceis de depuração.

Ainda assim, runkit_method_redefine parece ser o que você 're procurando, e um exemplo de seu uso pode ser encontrado em /tests/runkit_method_redefine.phpt no repositório:

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

Outras dicas

runkit parece ser uma boa solução, mas não é ativado por padrão e partes de que ainda são experimentais. Então eu cortei juntos uma pequena classe que substitui as definições de função em um arquivo de classe. Exemplo de utilização:

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

Em seguida, incluir a classe a ser modificada e redefinir os seus métodos:

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

Em seguida eval o novo código:

eval($objPatch->getCode());

Um pouco bruto, mas ele funciona!

Por uma questão de exaustividade - patching macaco está disponível no PHP através runkit . Para mais detalhes, consulte runkit_method_redefine().

Como cerca de envolvê-lo em uma outra classe como

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

Sim, ele é chamado extend:

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

Eu prefixado com "sd". ; -)

Tenha em mente que quando você estender uma classe para métodos de substituição, a assinatura do método tem de coincidir com o original. Por exemplo, se o original disse buggy_function($foo, $bar), tem que corresponder aos parâmetros da classe estendendo-o.

PHP é bastante detalhado sobre isso.

Para as pessoas que ainda estão à procura de esta resposta.

Você deve usar estende em combinação com namespaces .

como este:

namespace MyCustomName;

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

Depois de usá-lo fazer assim:

use MyCustomName\third_party_library;

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

Zend Studio e PDT (eclipse ide base) têm alguns construída em ferramentas refractoring. Mas não há são construídos em métodos para fazer isso.

Além disso, você não gostaria de ter um código ruim em seu sistema em tudo. Uma vez que poderia ser chamado por engano.

Se a biblioteca é criar explicitamente o mau classe e não usando um sistema localizador ou dependência que você está fora de sorte. Não há nenhuma maneira de substituir um método em outra classe, a menos que subclasse. A solução poderia ser a de criar um arquivo de patch que corrige a biblioteca, para que possa atualizar a biblioteca e re-aplicar o patch para corrigir esse método específico.

Você pode ser capaz de fazer isso com runkit. http://php.net/runkit

Há que se estende sempre a classe com uma nova, adequada, método e chamar essa classe em vez de um buggy.

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

(Desculpem a formatação de baixa qualidade)

Você pode fazer uma cópia da classe biblioteca, com tudo o mesmo, exceto o nome da classe. Em seguida, substituir essa classe renomeado.

Não é perfeito, mas ele faz melhorar a visibilidade das mudanças da classe de extensão. Se você buscar a biblioteca com algo como compositor, você tem de se comprometer a cópia para controle de origem e atualizá-lo quando você atualizar a biblioteca.

No meu caso era uma versão antiga do https://github.com/bshaffer/ oauth2-server-php . Eu modifiquei autoloader da biblioteca para buscar o meu arquivo de classe em vez disso. Meu arquivo de classe assumiu o nome original e estendeu uma versão copiada de um dos arquivos.

Uma vez que você sempre tem acesso ao código-base, PHP, redefinir as principais funções de classe que você deseja substituir o seguinte, este deve deixar suas interfaces intacta:

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

Você pode, por alguma razão usar uma variável privada, mas então você só será capaz de acessar a função estendendo a classe ou a lógica dentro da classe. Da mesma forma, é possível que você gostaria de ter diferentes objetos da mesma classe têm funções diferentes. Se assim for, do't uso estático, mas geralmente você quer que ele seja estática para que você não duplicar o uso de memória para cada objeto feito. O código 'ranOnce' apenas garante que você só precisa inicializar uma vez para a classe, não para cada $myObject = new third_party_library()

Agora, mais tarde, em seu código ou outra classe - sempre que a lógica atinge um ponto em que você precisa para substituir a função - basta fazer o seguinte:

$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'];

Como uma nota lateral, se você fizer todas as suas funções de classe desta forma e usar um armazenamento de chave / valor-base string como public static $functions['function_name'] = function(...){...}; isso pode ser útil para a reflexão. Não tanto em PHP como outras línguas, porque embora você já pode pegar os nomes de classe e função, mas você pode salvar alguns usuários de processamento e futuras da sua classe pode usar substituições em PHP. É, no entanto, um nível extra de engano, por isso gostaria de evitar usá-lo em classes primitivas, sempre que possível.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top