All previous attempts are basically flawed because of
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);
这适用于所有建议实现“代理”的解决方案。
那些建议显式使用其他方法来调用内部API的解决方案是有缺陷的,因为它们强制你更改公共API以更改内部行为,并且它们会降低类型安全性。
Kristoffer提供的解决方案没有考虑到public方法,这也是一个问题,因为你不能重写API使其全部为private或protected。
以下是部分解决此问题的解决方案:
class BankAccountProxy extends BankAccount {
public function __construct($wrapped) {
$this->wrapped = $wrapped;
}
public function doThings() {
$this->doOtherThingsOnMethodCall();
return $this->wrapped->doThings();
}
private function doOtherThingsOnMethodCall() { }
}
这是如何使用它的:
$account = new BankAccountProxy(new BankAccount());
pay($account);
这是一种类型安全、干净的解决方案,但涉及大量编码,请只将其作为一个示例。
编写这个样板代码并不有趣,所以您可能想使用不同的方法。
为了让您了解这类问题有多么复杂,我可以告诉您,我编写了
整个库 来解决它们,一些更聪明、更有智慧、更年长的人甚至发明了一个完全不同的范例,称为
"面向切面编程" (AOP)。
因此,我建议您研究这三种解决方案,我认为它们可以以更加清晰的方式解决您的问题。
Use ProxyManager's "access interceptor", which is basically a proxy type that allows you to run a closure when other methods are called (example). Here is an example on how to proxy ALL calls to an $object
's public 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)
)
);
}
then just use build_wrapper
as you like.
Use GO-AOP-PHP, which is an actual AOP library, completely written in PHP, but will apply this sort of logic to ALL instances of classes for which you define point cuts. This may or may not be what you want, and if your $callOnMethod
should be applied only for particular instances, then AOP is not what you are looking for.
Use the PHP AOP Extension, which I don't believe to be a good solution, mainly because GO-AOP-PHP solves this problem in a more elegant/debuggable way, and because extensions in PHP are inherently a mess (that is to be attributed to PHP internals, not to the extension developers).
Additionally, by using an extension, you are making your application as un-portable as possible (try convincing a sysadmin to install a compiled version of PHP, if you dare), and you can't use your app on cool new engines such as HHVM.
__call
中应该使用return
吗? - Alex2php__callStatic
构建类似的东西。 - Kristoffer Sall-Storgaard