php中的猴子补丁

Posted

技术标签:

【中文标题】php中的猴子补丁【英文标题】:monkey patching in php 【发布时间】: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

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

【问题讨论】:

【参考方案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 functions 和 references 创建一个自包含、自引用的数组(一个“对象",如果你愿意的话):

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 加载的代码

【讨论】:

以上是关于php中的猴子补丁的主要内容,如果未能解决你的问题,请参考以下文章

Python 中的鸭子类型和猴子补丁

Python中的猴子补丁

python笔记69 - 什么是猴子补丁(Monkey Patch)?

猴子补丁的应用,猴子补丁来改变日志。

如何绕过 Angular 使用自己的猴子补丁来撤消我的猴子补丁?

您如何检测到 Ruby 中发生了猴子补丁?