PHP 5 中的方法拦截。

Method interception in PHP 5.*

提问人: 提问时间:4/23/2010 最后编辑:12 revs, 2 users 70%Rolf 更新时间:8/10/2013 访问量:3511

问:

我正在为 PHP 实现日志系统,但我有点卡住了。

所有配置都在一个 XML 文件中定义,该文件声明要记录的每个方法。XML 被很好地解析并转换为多维数组 ()。目前为止,一切都好。classname => array of methods

让我们举一个简单的例子:

#A.php
class A {
    public function foo($bar) {
        echo ' // Hello there !';
    }

public function bar($foo) {
    echo " $ù$ùmezf$z !";
}
}

#B.php
class B {
public function far($boo) {
    echo $boo;  
}
}

现在,假设我有这个配置文件:

<interceptor>
<methods class="__CLASS_DIR__A.php">
        <method name="foo">
    <log-level>INFO</log-level>
    <log-message>Transaction init</log-message>
        </method>
</methods>  
<methods class="__CLASS_DIR__B.php">
        <method name="far">
    <log-level>DEBUG</log-level>
    <log-message>Useless</log-message>
        </method>
</methods>
</interceptor>

我想要的 AT RUNTIME ONLY(一旦 XML 解析器完成了他的工作)是:

#Logger.php (its definitely NOT a final version) -- generated by the XML parser
class Logger {
public function __call($name,$args) {
    $log_level = args[0];
    $args = array_slice($args,1);
    switch($method_name) {
        case 'foo':
        case 'far':
        //case .....
            //write in log files
            break;

    }
    //THEN, RELAY THE CALL TO THE INITIAL METHOD
 }
}

    #"dynamic" A.php
class A extends Logger {
    public function foo($log_level, $bar) {
    parent::foo($log_level, $bar);
        echo ' // Hello there !';
    }

public function bar($foo) {
    echo " $ù$ùmezf$z !";
}
}

#"dynamic" B.php
class B extends Logger {
public function far($log_level, $boo) {
    parent::far($log_level, $bar);
    echo $boo;  
}
}

这里最大的挑战是,一旦 XML 解析器完成其工作,将 A 和 B 转换为它们的“动态”版本。

理想的情况是在不修改 A 和 B 的代码的情况下实现这一点(我的意思是,在文件中)——或者至少在程序完成后找到一种方法来恢复它们的原始版本。

需要明确的是,我想找到在PHP中拦截方法调用的最合适的方法。

你对此有什么看法?

PS:当然,客户端代码不应该有任何后果(是否启用拦截没有区别)。

PHP 拦截器 XML 解析

评论

0赞 Pekka 4/23/2010
我还不完全确定我是否了解你的情况。你能添加一些更多细节,也许展示一些相关的代码吗?这听起来也许应该使用不同的方法来完成。
0赞 fbiville 4/24/2010
注意:已编辑并(希望)澄清
0赞 CLo 1/5/2012
@Rolf 如果你的类已经是子类,你也会有问题吗?使用这种方法,您需要将 parent:: 调用传播到链上。

答:

0赞 awgy 4/23/2010 #1

您可以使用实际定义类,但您应该非常小心。该功能可能非常危险。eval()eval()

像这样的东西:

$parentName = 'Bar';

eval('class Foo extends ' . $parentName . ' { }');

http://php.net/eval

评论

0赞 Annika Backstrom 4/23/2010
+1,直到有人发布一种在不使用 eval() 的情况下定义新类的方法。请注意,runkit 可以通过 runkit_class_adopt() 动态更改继承。
0赞 fbiville 4/24/2010
不幸的是,runkit_class_adopt是外部库的一部分,一旦应用程序托管,它就会影响其使用......关于 eval,我的类已经在运行时之前定义好了,我想避免在配置文件中可能存在的每个类主题中添加变量......看看最初的帖子,我以更好的方式解释了我的情况......
0赞 icio 5/2/2010 #2

这个解决方案再次使用了 eval,但无论如何我都会发布它供您考虑,因为我认为这是一种非常好的动态继承方式。

这里的方法是使用一个中间类,它扩展了一些可以更改的默认类(在这种情况下,扩展到另一个类,也扩展了默认值)。

我不确定您的设置中不允许这种工作的原因是什么 - 如果您澄清这一点,我也许可以提供更好的建议。

<?php

/*
 * One of the following classes will be the superclass of the Child
 */

class Ancestor {
    function speak() {
        echo 'Ancestor <br />';
    }
}

class Mum extends Ancestor {
    function speak() {
        parent::speak();
        echo 'Mum <br />';
    }
}

class Dad extends Ancestor {
    function speak() {
        parent::speak();
        echo 'Dad <br />';
    }
}

/*
 * Decide on which class we wish to make the superclass of our Child
 */

$parentClass = null;

if (isset($_GET['parent'])) {
    $parentClass = $_GET['parent'];
    if (!class_exists($parentClass)) {
        $parentClass = "Ancestor";
    }
}

if (!is_null($parentClass)) {
    eval("class ChildParent extends $parentClass {};");
} else {
    class ChildParent extends Ancestor {};
}

if (class_exists('ChildParent')) {
class Child extends ChildParent
{
    function speak() {
        parent::speak();
        echo 'Child <br />';
    }
}
}

/*
 * Show what's going on
 */

echo '<a href="?">Either</a> | <a href="?parent=Mum">Mum</a> | <a href="?parent=Dad">Dad</a> <br />';

$child = new Child();
$child->speak();*

评论

0赞 fbiville 5/3/2010
我更新了最初的主题(及其标题)。你应该看一看,以了解整个画面。多亏了你的例子,我想我明白了我将如何进行动态继承(一个默认的超类,一个由 XML 解析器生成)。
0赞 icio 5/4/2010
如果不在某处更改PHP代码,您将无法做到这一点。它要么在类结构中,要么在对象本身的实例化中。第一种方法如上所述。第二种方法代替 .那么到底是哪一种呢?Neu::MyClass();new MyClass();