猴子修补在PHP中

时间:2012-04-08 18:22:14

标签: php monkeypatching

我正在试图弄清楚猴子补丁是如何工作的,以及我如何使它适用于我自己的对象/方法。

我一直在看这个lib,它完全符合我自己想做的事情: 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

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

5 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:3)

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)中有相当多的例子。您可以检查替换类中函数的魔术方法示例。

答案 2 :(得分:1)

从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中的猴子修补)几乎总是一个糟糕的主意,但有时候这绝对是必要的。

答案 3 :(得分:0)

我使用eval()和命名空间修补了一个类。 但是,这可能不适合您,因为如果您正在修补猴子的类已经在命名空间中,则这不起作用。 除了从eval字符串中修剪名称空间声明之外,我还没弄清楚如何解决这个问题。但是,这样做可能会破坏类方法中任何与命名空间相关的代码。

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

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

答案 4 :(得分:-1)

你可能已经想到了这一点,但是,仅供参考,他们正在使用流包装器,

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

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