Tất cả các nỗ lực trước đó về cơ bản là thiếu sót vì http://ocramius.github.io/presentations/proxy-pattern-in-php/#/71
Dưới đây là ví dụ đơn giản, lấy từ slide của tôi:
class BankAccount { /* ... */ }
Và đây là "nghèo" đánh chặn luận lý của chúng tôi:
class PoorProxy {
public function __construct($wrapped) {
$this->wrapped = $wrapped;
}
public function __call($method, $args) {
return call_user_func_array(
$this->wrapped,
$args
);
}
}
Bây giờ, nếu chúng tôi có phương pháp sau để được gọi:
function pay(BankAccount $account) { /* ... */ }
Sau đó, điều này sẽ không làm việc:
$account = new PoorProxy(new BankAccount());
pay($account); // KABOOM!
này áp dụng cho tất cả các giải pháp đề nghị thực hiện một "proxy".
Giải pháp đề xuất sử dụng rõ ràng các phương pháp khác sau đó gọi API nội bộ của bạn là thiếu sót, vì chúng buộc bạn thay đổi API công khai của mình để thay đổi hành vi nội bộ và giảm an toàn loại.
Giải pháp được cung cấp bởi Kristoffer không tính đến phương pháp public
, cũng là một vấn đề, vì bạn không thể viết lại API của mình để làm cho tất cả private
hoặc protected
.
Đây là một giải pháp mà không giải quyết vấn đề này một phần:
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() { /**/ }
}
Đây là cách bạn sử dụng nó:
$account = new BankAccountProxy(new BankAccount());
pay($account); // WORKS!
Đây là một loại an toàn, giải pháp sạch, nhưng nó liên quan đến một rất nhiều mã hóa, vì vậy hãy lấy nó chỉ làm ví dụ.
Viết mã bản mẫu này không vui, vì vậy bạn có thể muốn sử dụng các cách tiếp cận khác nhau.
Để cung cấp cho bạn ý tưởng về mức độ phức tạp của vấn đề này, tôi có thể chỉ cho bạn biết rằng tôi đã viết an entire library để giải quyết chúng và một số người già hơn, thông minh hơn, thậm chí đã phát minh ra một mô hình hoàn toàn khác, gọi là "Aspect Oriented Programming" (AOP) .
Vì vậy, tôi đề nghị bạn nhìn vào những 3 giải pháp mà tôi nghĩ rằng có thể giải quyết vấn đề của bạn một cách sạch hơn nhiều:
Sử dụng ProxyManager 's 'access interceptor', mà về cơ bản là một proxy loại cho phép bạn chạy một đóng cửa khi các phương pháp khác được gọi là (example). Dưới đây là một ví dụ về cách để proxy TẤT CẢ các cuộc gọi đến API công cộng một $object
's:
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)
)
);
}
sau đó chỉ cần sử dụng build_wrapper
như bạn muốn.
Sử dụng GO-AOP-PHP, thư viện AOP thực, được viết hoàn toàn bằng PHP, nhưng sẽ áp dụng loại logic này cho TẤT CẢ các trường hợp lớp mà bạn xác định điểm cắt. Điều này có thể hoặc không thể là những gì bạn muốn, và nếu $callOnMethod
của bạn chỉ nên được áp dụng cho các trường hợp cụ thể, thì AOP không phải là những gì bạn đang tìm kiếm.
Sử dụng PHP AOP Extension, mà tôi không tin là một giải pháp tốt, chủ yếu là vì GO-AOP-PHP giải quyết vấn đề này một cách thanh lịch hơn/debuggable, và vì phần mở rộng trong PHP là vốn dĩ là một mớ hỗn độn (có nghĩa là để được gán cho PHP internals, không cho các nhà phát triển mở rộng). Ngoài ra, bằng cách sử dụng phần mở rộng, bạn đang làm cho ứng dụng của bạn trở nên không thể tháo rời càng tốt (cố gắng thuyết phục sysadmin cài đặt phiên bản PHP đã biên dịch, nếu bạn dám) và bạn không thể sử dụng ứng dụng của mình trên các công cụ mới thú vị như vậy như HHVM.
Trên thực tế, a) bạn cũng có thể có các phương pháp cũng như bảo vệ, không công khai và b) bạn không cần _ – ktolis
Bạn đúng, được bảo vệ cũng sẽ hoạt động, tôi đã xóa '_' trong ví dụ và thực hiện 'test3'' được bảo vệ'. –
Nên 'return'in' __call' – Alex2php