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

Nik*_*Nik 56 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
Run Code Online (Sandbox Code Playgroud)

现在我的问题是,

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

test1
test2
test1
test3
Run Code Online (Sandbox Code Playgroud)

目前我正在获得输出

test2
test3
Run Code Online (Sandbox Code Playgroud)

我不能在每个函数定义中调用'test1'函数,因为可能有很多函数.我需要一种方法来在调用类的任何函数之前自动调用函数.

任何替代方式也可以.

Kri*_*ard 65

你最好的选择是魔术方法__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
*/
Run Code Online (Sandbox Code Playgroud)

请注意,test2并且test3在因调用protected和调用它们的上下文中不可见private.如果方法是公开的,则上述示例将失败.

test1不必申报private.

ideone.com示例可以在这里找到

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


Ocr*_*ius 20

由于http://ocramius.github.io/presentations/proxy-pattern-in-php/#/71所有以前的尝试基本上都是有缺陷的

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

class BankAccount { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

这是我们的"可怜"拦截器逻辑:

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

    public function __call($method, $args) {
        return call_user_func_array(
            $this->wrapped,
            $args
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

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

function pay(BankAccount $account) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

那么这将不起作用:

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

pay($account); // KABOOM!
Run Code Online (Sandbox Code Playgroud)

这适用于建议实施"代理"的所有解决方案.

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

Kristoffer提供的解决方案没有考虑public方法,这也是一个问题,因为你不能重写你的API以使其全部privateprotected.

这是一个解决方案,部分解决了这个问题:

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() { /**/ }
}
Run Code Online (Sandbox Code Playgroud)

以下是您使用它的方式:

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

pay($account); // WORKS!
Run Code Online (Sandbox Code Playgroud)

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

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

为了让您了解这类问题的复杂程度,我可以告诉您,我编写了一个完整的库来解决这些问题,而一些更聪明,更聪明的老年人甚至去发明了一种完全不同的范例,称为"面向方面"编程"(AOP).

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

  • 使用ProxyManager的" 访问拦截器 ",它基本上是一种代理类型,允许您在调用其他方法时运行闭包(示例).以下是如何代理对$object公共API的所有调用的示例:

    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)
                )
            ); 
    }
    
    Run Code Online (Sandbox Code Playgroud)

    然后只是随心所欲地使用build_wrapper.

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

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


mar*_*lle 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
    }
}
Run Code Online (Sandbox Code Playgroud)

现在简单的测试类:

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

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

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

}
Run Code Online (Sandbox Code Playgroud)

你现在需要做的就是:

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

$obj->methodOne();
$obj->methodTwo();
$obj->methodThree(); // This will fail, methodThree is private
Run Code Online (Sandbox Code Playgroud)

好处:

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

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