【问题标题】:Redefine Class Methods or Class重新定义类方法或类
【发布时间】:2010-09-13 07:32:18
【问题描述】:

有没有什么方法可以在不使用典型继承的情况下重新定义一个类或它的一些方法?例如:

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 岁了。

编辑:

我应该澄清一下,由于框架限制,我无法将类从 third_party_library 重命名为 magical_third_party_library 或其他任何名称。

出于我的目的,是否可以只在类中添加一个函数?我认为您可以在 C# 中使用称为“部分类”的东西来做到这一点。

【问题讨论】:

  • PHP 不支持。您可以扩展该类并重新使用它。这就对了。对不起。
  • 你能重命名原来的越野车类吗?将其重命名为 buggy_third_party,然后自己扩展它,为您的班级提供原始名称。

标签: php class methods redefine


【解决方案1】:

Zend Studio 和 PDT(基于 Eclipse 的 ide)有一些内置的折射工具。但是没有内置的方法可以做到这一点。

此外,您根本不希望系统中有错误的代码。因为它可能会被错误地调用。

【讨论】:

    【解决方案2】:

    它叫做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 中找到其使用示例:

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

    【讨论】:

    • 这被接受为答案:这是否意味着 PHP 支持它?更多信息会很好。
    • @nickf:“我不相信”是否定的;所以OP的意思是“PHP不支持”。据我所知,PHP 不支持猴子补丁
    • @Piskvor,嗯,是的 - 我可以阅读,但奇怪的是,这个答案比其他提供某种形式指导的答案更受欢迎。
    【解决方案3】:

    是的,它叫extend

    <?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 对此非常冗长。

    【讨论】:

    • 这通常可以工作,但是由于我使用的框架是如何设置的,我无法更改类名
    • 那么答案是,你不能。
    • 这在基类调用其中一个函数时不起作用。
    • OP 询问如何“没有典型继承”。即他正在搜索类似 Java 的反射
    【解决方案4】:

    总是用一种新的、正确的方法来扩展这个类,并调用那个类而不是那个有问题的类。

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

    (对不起,糟糕的格式)

    【讨论】:

    • 你读过这个问题吗?他很清楚无法使用此解决方案。
    【解决方案5】:

    如果库显式地创建了错误的类并且没有使用定位器或依赖系统,那么你就不走运了。除非您子类化,否则无法覆盖另一个类的方法。 解决方案可能是创建一个修复库的补丁文件,这样您就可以升级库并重新应用补丁来修复该特定方法。

    【讨论】:

      【解决方案6】:

      您也许可以使用 runkit 来做到这一点。 http://php.net/runkit

      【讨论】:

        【解决方案7】:

        为了完整起见 - 通过runkit 在 PHP 中提供猴子补丁。详情请见runkit_method_redefine()

        【讨论】:

          【解决方案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);
           }
          }
          

          【讨论】:

          • 这非常适合包装一个主要类。您也可以将原始的 php 变量传递给构造函数,并用 $this 将其替换掉。 (对于主要课程来说很方便,对于很多实例来说不是那么方便)
          • 我想看看如何使用这个的例子
          【解决方案9】:

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

          然后包含要修改的类并重新定义其方法:

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

          然后评估新代码:

          eval($objPatch->getCode());
          

          有点粗糙,但很有效!

          【讨论】:

          • 那是一次旅行! eval 语句然后定义具有替换函数的类?所以我需要做的就是实例化对象并使用它运行?
          • 是的,先生!这就是您需要做的所有事情。
          • @SeanDowney ,这似乎很容易成为现实。这门课能开箱吗?你得到预期的结果了吗@SeanDowney?会让我的生活更轻松。
          • @JPhilly 我尝试了这种方法,但得到一个致命错误:无法重新声明类。我应该在哪里运行 eval 代码?
          • 您不应该首先加载该类,因此您仍然需要控制是否需要该类(如果它是自动加载的,那么您很幸运,如果没有,那将不起作用)此外,此类可以改进很多(正则表达式缺少多个空格检查),如果该类在其方法中打印了 html 部分或在类中间打开/关闭了 php 标签,它将无法工作
          【解决方案10】:

          您可以复制库类,除了类名之外的所有内容都相同。然后覆盖那个重命名的类。

          它并不完美,但它确实提高了扩展类更改的可见性。如果您使用 Composer 之类的工具获取库,则必须将副本提交到源代码管理并在更新库时对其进行更新。

          就我而言,它是https://github.com/bshaffer/oauth2-server-php 的旧版本。我修改了库的自动加载器来获取我的类文件。我的类文件采用了原始名称并扩展了其中一个文件的复制版本。

          【讨论】:

            【解决方案11】:

            对于仍在寻找此答案的人。

            您应该将extendsnamespaces 结合使用。

            像这样:

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

            然后像这样使用它:

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

            【讨论】:

            • 这是一个有趣的解决方案。我看到的唯一缺点是当您需要在使用它的地方更改原始类的行为时。
            • 你救了我项目的命 :)。谢谢你的主意!在我的框架中,我定义了 FrameworkFunction\RestrictContent,并且我允许其他开发人员在需要覆盖某些内容而不触及核心框架代码的情况下执行 CustomFunction\RestrictContent。然后当我使用 if(class_exists()) 检查新命名空间是否已实现并使用它时,否则默认返回框架的类。
            【解决方案12】:

            由于您始终可以访问 PHP 中的基本代码,因此请重新定义要覆盖的主要类函数,如下所示,这应该使您的接口保持不变:

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

            您可能出于某种原因使用私有变量,但您只能通过扩展类或类中的逻辑来访问该函数。同样,您可能希望同一类的不同对象具有不同的功能。如果是这样,请不要使用静态,但通常您希望它是静态的,这样您就不会复制每个对象的内存使用。 “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 中使用覆盖。然而,它是一个额外的间接级别,所以我会尽可能避免在原始类上使用它。

            【讨论】:

              【解决方案13】:

              我已根据@JPhilly 的答案修改了代码,并可以重命名已修补的类以避免错误。

              另外,我更改了标识即将被替换的函数的正则表达式,以适应被替换函数在其名称前没有任何类访问修饰符的情况

              希望对你有帮助。

              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]);
              
                      // capture the function with its body and replace it with the new function
                      if ( preg_match('/((private|protected|public)?\s?function ' . $func_name .'[\w\W\n]+?)(private|protected|public|function|class)/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 renameClass($old_name, $new_name) {
              
                      $new_code = str_replace("class $old_name ", "class $new_name ", $this->_code);
              
                      $this->setCode($new_code);
              
                  }
              
                  function getCode() {
                      return $this->_code;
                  }
              }
              

              这是我用来修补 Wordpress 插件的方法:

              $objPatch = new Patch(ABSPATH . 'wp-content/plugins/a-plugin/code.php');
              $objPatch->renameClass("Patched_AClass", "Patched_Patched_AClass"); // just to avoid class redefinition
              $objPatch->redefineFunction("
                  function default_initialize() {
                      echo 'my patched function';
                  }");
              eval($objPatch->getCode());
              $result = new Patched_AClass();
              

              【讨论】:

                猜你喜欢
                • 2012-04-07
                • 1970-01-01
                • 1970-01-01
                • 2019-07-22
                • 1970-01-01
                • 1970-01-01
                • 2012-05-03
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多