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笔记69 - 什么是猴子补丁(Monkey Patch)?