如何在php中为每个其他函数调用自动调用函数

时间:2010-09-15 10:13:07

标签: php

Class test{
    function test1()
    {
        echo 'inside test1';
    }

    function test2()
    {
        echo 'test2';
    }

    function test3()
    {
        echo 'test3';
    }
}

$obj = new test;
$obj->test2();//prints test2
$obj->test3();//prints test3

现在我的问题是,

如何在任何被调用的函数执行之前调用另一个函数? 在上面的例子中,如何为每个其他函数调用自动调用'test1'函数, 所以我可以得到输出,

test1
test2
test1
test3

目前我的输出为

test2
test3
  

我无法调用'test1'函数   可能的每个函数定义   有很多功能。我需要一种方法   在呼叫之前自动呼叫一个功能   任何类的功能。

任何替代方式也可以。

8 个答案:

答案 0 :(得分:62)

你最好的选择是魔术方法__call,例如见下文:

<?php

class test {
    function __construct(){}

    private function test1(){
        echo "In test1", PHP_EOL;
    }
    private function test2(){
        echo "test2", PHP_EOL;
    }
    protected function test3(){
        return "test3" . PHP_EOL;
    }
    public function __call($method,$arguments) {
        if(method_exists($this, $method)) {
            $this->test1();
            return call_user_func_array(array($this,$method),$arguments);
        }
    }
}

$a = new test;
$a->test2();
echo $a->test3();
/*
* Output:
* In test1
* test2
* In test1
* test3
*/

请注意,由于test2test3protectedprivate在调用它们的上下文中不可见。如果方法是公开的,则上述示例将失败。

test1不必声明为private

ideone.com example can be found here

已更新:添加指向ideone的链接,添加带返回值的示例。

答案 1 :(得分:20)

由于http://ocramius.github.io/presentations/proxy-pattern-in-php/#/71

,之前的所有尝试都存在缺陷

这是一个简单的例子,取自我的幻灯片:

class BankAccount { /* ... */ }

这是我们的穷人&#34;拦截器逻辑:

class PoorProxy {
    public function __construct($wrapped) {
        $this->wrapped = $wrapped;
    }

    public function __call($method, $args) {
        return call_user_func_array(
            $this->wrapped,
            $args
        );
    }
}

现在,如果我们要调用以下方法:

function pay(BankAccount $account) { /* ... */ }

然后这不会起作用:

$account = new PoorProxy(new BankAccount());

pay($account); // KABOOM!

这适用于所有建议实施&#34;代理&#34;。

的解决方案

建议明确使用其他方法然后调用内部API的解决方案存在缺陷,因为它们会强制您更改公共API以更改内部行为,并且会降低类型安全性。

Kristoffer提供的解决方案并不考虑public方法,这也是一个问题,因为您无法重写API以使其全部private或{{ 1}}。

这是一个可以解决这个问题的解决方案:

protected

以下是您使用它的方式:

class BankAccountProxy extends BankAccount {
    public function __construct($wrapped) {
        $this->wrapped = $wrapped;
    }

    public function doThings() { // inherited public method
        $this->doOtherThingsOnMethodCall();

        return $this->wrapped->doThings();
    }

    private function doOtherThingsOnMethodCall() { /**/ }
}

这是一种类型安全,干净的解决方案,但它涉及大量编码,因此请仅将其作为示例。

编写此样板代码并不好玩,因此您可能希望使用不同的方法。

为了让您了解这类问题有多复杂,我可以告诉您,我写了an entire library来解决这些问题,一些更聪明,更聪明的老年人甚至去发明了一个完全不同的范例,称为"Aspect Oriented Programming" (AOP)

因此,我建议您研究一下我认为可以用更清洁的方式解决问题的3个解决方案:

  • 使用ProxyManager&#39; access interceptor&#34;,这基本上是一种代理类型,允许您在调用其他方法时运行闭包( example)。以下是有关如何代理对$account = new BankAccountProxy(new BankAccount()); pay($account); // WORKS! 公共API的所有调用的示例:

    $object

    然后根据需要使用use ProxyManager\Factory\AccessInterceptorValueHolderFactory; function build_wrapper($object, callable $callOnMethod) { return (new AccessInterceptorValueHolderFactory) ->createProxy( $object, array_map( function () use ($callOnMethod) { return $callOnMethod; }, (new ReflectionClass($object)) ->getMethods(ReflectionMethod::IS_PUBLIC) ) ); }

  • 使用GO-AOP-PHP,这是一个完全用PHP编写的实际AOP库,但会将这种逻辑应用于您为其定义点切割的所有类实例。这可能是您想要的,也可能不是,如果您的build_wrapper仅适用于特定情况,那么AOP不是您想要的。

  • 使用PHP AOP Extension,我不相信这是一个很好的解决方案,主要是因为GO-AOP-PHP以更优雅/可调试的方式解决了这个问题,并且因为PHP本身就是一团糟(这要归功于PHP内部,而不是扩展开发人员)。 此外,通过使用扩展,您可以使您的应用程序尽可能不可移植(如果您敢于尝试说服系统管理员安装PHP的编译版本),并且您无法在酷炫的新引擎上使用您的应用程序例如HHVM。

答案 2 :(得分:7)

也许它有点过时了,但这里来了我的2美分......

我不认为通过__call()访问私有方法是个好主意。如果你有一个方法,你真的不想在你的对象之外调用,你就无法避免它发生。

我认为一个更优雅的解决方案应该是创建某种通用代理/装饰器并在其中使用__call()。让我说明一下:

class Proxy
{
    private $proxifiedClass;

    function __construct($proxifiedClass)
    {
        $this->proxifiedClass = $proxifiedClass;
    }

    public function __call($methodName, $arguments)
    {

        if (is_callable(
                array($this->proxifiedClass, $methodName)))
        {
            doSomethingBeforeCall();

            call_user_func(array($this->proxifiedClass, $methodName), $arguments);

            doSomethingAfterCall();
        }
        else
        {
            $class = get_class($this->proxifiedClass);
            throw new \BadMethodCallException("No callable method $methodName at $class class");
        }
    }

    private function doSomethingBeforeCall()
    {
        echo 'Before call';
        //code here
    }

    private function doSomethingAfterCall()
    {
        echo 'After call';
        //code here
    }
}

现在只是一个测试类:

class Test
{
    public function methodOne()
    {
        echo 'Method one';
    }

    public function methodTwo()
    {
        echo 'Method two';
    }

    private function methodThree()
    {
        echo 'Method three';
    }

}

现在你需要做的就是:

$obj = new Proxy(new Test());

$obj->methodOne();
$obj->methodTwo();
$obj->methodThree(); // This will fail, methodThree is private

优点:

1)您只需要一个代理类,它将适用于您的所有对象。 2)您不会不尊重可访问性规则。 3)您无需更改代理对象。

缺点:包装原始对象后,您将失去界面/合约。如果您使用带有频率的类型提示,则可能是一个问题。

答案 3 :(得分:3)

<?php

class test
{

    public function __call($name, $arguments)
    {
        $this->test1(); // Call from here
        return call_user_func_array(array($this, $name), $arguments);
    }

    // methods here...

}

?>

尝试在课程中添加此方法覆盖...

答案 4 :(得分:3)

到目前为止,最好的方法可能是创建自己的方法调用方并在方法之前和之后包装您需要的任何内容:

class MyClass {

    public function callMethod()
    {
        $args = func_get_args();

        if (count($args) == 0) {
            echo __FUNCTION__ . ': No method specified!' . PHP_EOL . PHP_EOL;;
        } else {
            $method = array_shift($args); // first argument is the method name and we won't need to pass it further
            if (method_exists($this, $method)) {
                echo __FUNCTION__ . ': I will execute this line and then call ' . __CLASS__ . '->' . $method . '()' . PHP_EOL;
                call_user_func_array([$this, $method], $args);
                echo __FUNCTION__ . ": I'm done with " . __CLASS__ . '->' . $method . '() and now I execute this line ' . PHP_EOL . PHP_EOL;
            } else
                echo __FUNCTION__ . ': Method ' . __CLASS__ . '->' . $method . '() does not exist' . PHP_EOL . PHP_EOL;
        }
    }

    public function functionAA()
    {
        echo __FUNCTION__ . ": I've been called" . PHP_EOL;
    }

    public function functionBB($a, $b, $c)
    {
        echo __FUNCTION__ . ": I've been called with these arguments (" . $a . ', ' . $b . ', ' . $c . ')' . PHP_EOL;
    }
}

$myClass = new MyClass();

$myClass->callMethod('functionAA');
$myClass->callMethod('functionBB', 1, 2, 3);
$myClass->callMethod('functionCC');
$myClass->callMethod();

这是输出:

callMethod: I will execute this line and then call MyClass->functionAA()
functionAA: I've been called
callMethod: I'm done with MyClass->functionAA() and now I execute this line 

callMethod: I will execute this line and then call MyClass->functionBB()
functionBB: I've been called with these arguments (1, 2, 3)
callMethod: I'm done with MyClass->functionBB() and now I execute this line 

callMethod: Method MyClass->functionCC() does not exist

callMethod: No method specified!

你甚至可以进一步创建一个方法的白名单,但为了一个更简单的例子,我把它留下来。

您将不再被迫将方法设为私有,并通过__call()使用它们。 我假设在某些情况下您可能希望在没有包装器的情况下调用方法,或者您希望IDE仍然自动完成方法,如果将方法声明为私有方式,则很可能不会发生这些方法。

答案 5 :(得分:2)

如果你真的非常勇敢,你可以使用runkit扩展来实现它。 (http://www.php.net/manual/en/book.runkit.php)。您可以使用runkit_method_redefine(您也可以使用Reflection来检索方法定义)或者组合runkit_method_rename(旧函数)/ runkit_method_add(包含对test1函数和旧函数的调用的新函数)

答案 6 :(得分:0)

执行此操作的唯一方法是使用魔法__call。您需要将所有方法设为私有,以便无法从外部访问它们。然后定义__call方法来处理方法调用。在__call中,您可以在调用有意调用的函数之前执行您想要的任何函数

答案 7 :(得分:-2)

让我们来看看这个:

class test
{
    function __construct()
    {

    }

    private function test1()
    {
        echo "In test1";
    }

    private function test2()
    {
        echo "test2";
    }

    private function test3()
    {
        echo "test3";
    }

    function CallMethodsAfterOne($methods = array())
    {
        //Calls the private method internally
        foreach($methods as $method => $arguments)
        {
            $this->test1();
            $arguments = $arguments ? $arguments : array(); //Check
            call_user_func_array(array($this,$method),$arguments);
        }
    }
}

$test = new test;
$test->CallMethodsAfterOne('test2','test3','test4' => array('first_param'));

这就是我要做的事情