【问题标题】:monkey patching in phpphp中的猴子补丁
【发布时间】:2012-04-21 08:29:39
【问题描述】:

我正在尝试弄清楚猴子补丁是如何工作的,以及如何让它在我自己的对象/方法上工作。

我一直在研究这个库,它完全符合我自己的要求: https://github.com/antecedent/patchwork

使用它,您可以从对象重新定义方法。它为此使用了“猴子补丁”技术。但是通过查看源代码,我无法真正弄清楚到底发生了什么。

所以假设我有以下对象:

//file: MyClass.php
namespace MyClass;

class MyClass {

    public function say()
    {
        echo 'Hi';
    }
}

我想做这样的事情:

Monkeypatch\replace('MyClass', 'say', function() {
    echo 'Hello';
});

$obj = new MyClass();
$obj->say();  // Prints: 'Hello'

但我不确定如何编写实际的修补部分。我知道在这种情况下命名空间很重要。但这究竟是如何让我修补某种方法的呢?我是否需要在某处使用 eval()(如果需要,如何使用)?

我真的找不到任何关于这个问题的好例子,除了: http://till.klampaeckel.de/blog/archives/105-Monkey-patching-in-PHP.html

但我真的不知道如何将其应用于我自己的对象/方法。我希望有一个很好的解释或例子。

【问题讨论】:

    标签: php monkeypatching


    【解决方案1】:

    您可以使用runkit 进行运行时类修改。更具体地说,您可以使用runkit_method_redefine

    【讨论】:

    • 我不想使用runkit,它是一个pecl,它不能安装在很多主机上。此外,我对猴子修补的工作原理很感兴趣。如果我只是想要一个库,我会在拼凑上使用 runkit。我只是想知道猴子修补如何与修补我自己的对象一起工作。
    【解决方案2】:

    http://till.klampaeckel.de/blog/archives/105-Monkey-patching-in-PHP.html 的情况下,真正不同的是第二个strlen 前面使用的\ 字符。

    当您使用命名空间时,您可以use 命名空间并直接调用命名空间中声明的方法/类:

    use TheNamespace;
    $var = new TheClass();
    

    或者通过使用类似的方式显式调用该类:

    $var = new \TheNamespace\TheClass();

    因此,通过调用 \strlen() 而不是 strlen(),您明确要求 PHP 使用默认的 strlen 而不是为此命名空间定义的 strlen。

    至于猴子补丁,您可以使用 runkit (http://ca.php.net/runkit)。此外,关于拼凑,他们的网站 (http://antecedent.github.com/patchwork/docs/examples.html) 中有大量示例。您可以查看在类中替换函数的魔术方法示例。

    【讨论】:

    • 我理解了来自 webstie 的 strlen 示例。但不是如何将其应用于我自己的对象。它没有说明如何像拼凑而成的那样重新定义方法。我也不是在寻找像 runkit 等任何其他库。我只是想知道如何用普通的 PHP 给自己打补丁。
    • @w00 网站中提到的示例不是您可以理解的猴子补丁,因为您通过命名空间了解了全部要点。这就是为什么您没有在类中获得任何覆盖函数的示例。如果您对猴子补丁的兴趣仅限于单元测试,那么为什么不在您的开发服务器上使用 runkit?否则你为什么不试试拼凑呢?但是我不知道它是否可以很好地扩展。
    【解决方案3】:

    从 PHP 5.6 开始,仍然不支持猴子补丁;然而 PHP 5.3 引入了匿名函数。这个答案并不完全是您正在寻找的,可能会有所改进,但总体思路是使用数组 anonymous functionsreferences 创建一个自包含、自引用的数组(一个“对象",如果你愿意的话):

    test.php

    $inner = require('test2.php');
    $inner['say'](); // Hi!
    
    $inner['data']['say'] = 'Bye!';
    $inner['say'](); // still says Hi!
    
    $inner['set_say']('Bye!');
    $inner['say'](); // Bye!
    
    $inner = require('test2.php');
    $inner['say'](); // Hi!
    

    test2.php

    $class = array(
        'data' => array(
            'say' => 'Hi!'
        ),
    
        'say' => function() use (&$class){
            echo $class['data']['say'].'<br />';
        },
    
        'set_say' => function($msg) use (&$class){
            $class['data']['say'] =& $msg; 
        }
    );
    
    return $class;
    

    另外,这里有一个免责声明,上面的代码(以及 PHP 中的猴子补丁)几乎总是一个糟糕的主意,但有时这是绝对必要的。

    【讨论】:

      【解决方案4】:

      我已经使用 eval() 和命名空间对一个类进行了猴子补丁。 但是,这可能不是您的答案,因为如果您正在猴子修补的类已经在命名空间中,这将不起作用。 除了从 eval 字符串中修剪命名空间声明之外,我还没有想出如何解决这个问题。但是,这样做可能会破坏类方法中任何依赖于命名空间的代码。

      就我而言,我正在对核心 PDO 类进行猴子修补,以便对依赖于数据库交互的类进行单元测试。但是,也许看看我的技术会帮助你弄清楚如何让它适合你的情况。

      我在这里的博客文章中有代码 sn-ps: http://chrisgriffing.com/coding/php/2012/04/12/how-to-mock-pdo-and-other-objects/

      【讨论】:

        【解决方案5】:

        您可能已经想通了,但仅供参考,他们使用的是流包装器,

        http://php.net/manual/es/function.stream-wrapper-register.php

        基本上,他们在文件和 phar 上注册了一个流包装器,所以当代码加载时,他们可以操纵它,它不适用于从 opcache 加载的代码

        【讨论】:

          猜你喜欢
          • 2017-03-28
          • 2016-09-01
          • 2012-09-16
          • 2012-12-18
          • 2020-01-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多