如何在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'函数,因为可能有许多函数。 我需要一种方法来在调用任何类的函数之前自动调用一个函数。
任何替代方式也将是做的。
你最好的select是__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 */
请注意,由于protected
和private
, test2
和test3
在被调用的上下文中不可见。 如果方法是公开的,上面的例子将失败。
test1
不必被宣布为private
。
ideone.com的例子可以在这里find
更新 :添加链接到ideone,添加示例返回值。
所有以前的尝试基本上是有缺陷的,因为http://ocramius.github.io/presentations/proxy-pattern-in-php/#/71
以下是我的幻灯片中的简单示例:
class BankAccount { /* ... */ }
这里是我们的“穷人”拦截器逻辑:
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!
这适用于所有build议实施“代理”的解决scheme。
build议明确使用其他方法然后调用您的内部API的解决scheme是有缺陷的,因为它们会强制您更改您的公共API来更改内部行为,并且会降低types安全性。
由Kristoffer提供的解决scheme没有考虑到public
方法,这也是一个问题,因为您不能重写您的API以使其全部private
或protected
。
这是一个解决scheme,部分解决了这个问题:
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() { /**/ } }
这里是你如何使用它:
$account = new BankAccountProxy(new BankAccount()); pay($account); // WORKS!
这是一个types安全的,干净的解决scheme,但它涉及到很多编码,所以请仅以此为例。
编写这个样板代码并不好玩,所以你可能需要使用不同的方法。
为了让你了解这类问题有多复杂,我可以告诉你,我写了一个完整的图书馆来解决这个问题,一些更聪明,更聪明的老年人甚至去了,发明了一个完全不同的范式,叫做“面向方面编程“(AOP) 。
因此,我build议你看看这3个解决scheme,我认为可以用更简洁的方式解决你的问题:
-
使用ProxyManager的“ 访问拦截器 ”,它基本上是一个代理types,允许您在其他方法被调用时运行闭包( 例子 )。 以下是如何将所有调用代理到
$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) ) ); }
那么只要你喜欢使用
build_wrapper
。 -
使用GO-AOP-PHP ,这是一个实际的AOP库,完全用PHP编写,但是将这种逻辑应用于所有定义点削减的类的实例。 这可能是也可能不是你想要的,如果你的
$callOnMethod
只适用于特定的实例,那么AOP不是你正在寻找的。 -
使用PHP AOP扩展 ,我不认为这是一个很好的解决scheme,主要是因为GO-AOP-PHP以更优雅/可debugging的方式解决了这个问题,并且因为PHP中的扩展本质上是一团糟(这是归因于PHP内部,而不是扩展开发者)。 此外,通过使用扩展,您可以使应用程序尽可能不可移植(尝试说服系统pipe理员安装编译的PHP版本,如果您敢的话),并且您不能在酷的新引擎上使用您的应用程序,例如HHVM。
也许这是有点过时了,但是这里来了我的2美分…
我不认为通过__call()访问私有方法是一个好主意。 如果你有一个方法,你真的不希望在你的对象之外被调用,你无法避免它发生。
我认为一个更优雅的解决scheme应该是创build某种通用代理/装饰器,并在其中使用__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 } }
现在简单的testing课:
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)你不需要改变代理对象。
缺点:在包装原始对象之后,你将失去地位/契约。 如果你使用types提示频率也许这是一个问题。
<?php class test { public function __call($name, $arguments) { $this->test1(); // Call from here return call_user_func_array(array($this, $name), $arguments); } // methods here... } ?>
尝试添加这个方法重写在类中…
如果你真的很勇敢,你可以使用runkit扩展。 ( http://www.php.net/manual/en/book.runkit.php )。 您可以使用runkit_method_redefine(您可能需要Reflection来检索方法定义),也可以组合runkit_method_rename(旧函数)/ runkit_method_add(包含调用test1函数和旧函数的新函数)
也许到目前为止最好的方法是创build你自己的方法调用者,并在方法之前和之后包装任何你需要的东西:
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:我将执行这一行,然后调用MyClass-> functionAA() functionAA:我被叫了 callMethod:我完成了MyClass-> functionAA(),现在我执行这一行 callMethod:我将执行这一行,然后调用MyClass-> functionBB() functionBB:我已经被调用这些参数(1,2,3) callMethod:我完成了MyClass-> functionBB(),现在我执行这一行 callMethod:方法MyClass-> functionCC()不存在 callMethod:没有指定方法!
你甚至可以进一步创build一个白名单的方法,但我留下这样一个更简单的例子。
您将不再被迫通过__call()来使这些方法保持私有状态。 我假设可能会出现这种情况,如果您不想使用包装来调用这些方法,或者您希望您的IDE仍然自动完成如果将这些方法声明为私有方法,那么很可能不会发生的方法。
唯一的方法是使用魔法__call
。 你需要使所有的方法都是私人的,所以他们不能从外部访问。 然后定义__call方法来处理方法调用。 在__call中,你可以在调用被故意调用的函数之前执行你想要的任何函数。
让我们来看看这个:
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'));
那就是我会做的