2010-09-15 28 views
44
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 

Bây giờ câu hỏi của tôi là,Làm thế nào để tự động gọi hàm trong php cho mỗi chức năng cuộc gọi khác

Làm thế nào tôi có thể gọi một chức năng trước khi bất kỳ thực hiện chức năng gọi là? Trong trường hợp trên, làm thế nào tôi có thể tự động gọi 'test1' chức năng cho mỗi cuộc gọi chức năng khác, để tôi có thể nhận được đầu ra như,

test1 
test2 
test1 
test3 

Hiện tại tôi đang nhận được đầu ra như

test2 
test3 

Tôi không thể gọi chức năng 'test1' trong mọi định nghĩa chức năng vì có thể là nhiều chức năng. Tôi cần một cách để tự động gọi một chức năng trước khi gọi bất kỳ chức năng nào của một lớp học.

Mọi cách thay thế cũng sẽ được thực hiện.

Trả lời

50

Tốt nhất là phương pháp kỳ diệu __call, xem dưới đây ví dụ:

<?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 
*/ 

Xin lưu ý rằng test2test3 không nhìn thấy được trong bối cảnh mà chúng được gọi là do protectedprivate. Nếu các phương thức được công khai, ví dụ trên sẽ thất bại.

test1 không cần phải khai báo private.

ideone.com example can be found here

Cập nhật: Thêm liên kết đến ideone, thêm ví dụ với giá trị trả về.

+0

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

+0

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ệ'. –

+0

Nên 'return'in' __call' – Alex2php

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... 

} 

?> 

Hãy thử thêm phương pháp trọng này trong lớp ...

+0

cảm ơn đề xuất nhưng tôi không thể gọi hàm 'test1' trong mọi định nghĩa hàm vì có thể có nhiều hàm. Tôi cần một cách để tự động gọi một hàm trước khi gọi bất kỳ hàm nào của một lớp. – Nik

+0

+1 vì đây là cách tốt nhất để làm điều đó mà không vi phạm nguyên tắc OOP bằng cách chỉ định các phương thức công khai là riêng tư mặc dù chúng vẫn có thể truy cập công khai. – poke

+0

@Yogesh, hãy xem câu trả lời được cập nhật, vui lòng ... – Otar

0

Cách duy nhất để làm điều này là sử dụng sự kỳ diệu __call. Bạn cần phải đặt tất cả các phương thức riêng tư để chúng không thể truy cập từ bên ngoài. Sau đó, xác định phương thức __call để xử lý các cuộc gọi phương thức. Trong __call, bạn có thể thực thi bất kỳ hàm nào bạn muốn trước khi gọi gọi hàm được cố ý gọi.

-2

Cho phép có một đi vào cái này:

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')); 

Thats những gì tôi sẽ làm gì

2

Nếu bạn đang thực sự, thực sự dũng cảm, bạn có thể làm cho nó với phần mở rộng runkit. (http://www.php.net/manual/en/book.runkit.php). Bạn có thể chơi với runkit_method_redefine (bạn có thể cần Reflection cũng để lấy định nghĩa phương thức) hoặc có thể kết hợp runkit_method_rename (hàm cũ)/runkit_method_add (hàm mới kết thúc cuộc gọi hàm test1 của bạn và hàm cũ)

5

Có lẽ nó là một chút hơi lỗi thời nhưng ở đây đến 2 xu của tôi ...

Tôi không nghĩ rằng việc cấp quyền truy cập vào các phương thức riêng thông qua __call() là một ý tưởng hay. Nếu bạn có một phương pháp mà bạn thực sự không muốn được gọi bên ngoài đối tượng của bạn, bạn không có cách nào để tránh nó xảy ra.

Tôi nghĩ rằng một giải pháp thanh lịch hơn nên tạo một số loại proxy/trang trí phổ dụng và sử dụng __call() bên trong nó. Hãy để tôi chỉ cho cách:

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 
    } 
} 

Bây giờ một lớp học thử nghiệm đơn giản:

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

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

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

} 

Và tất cả các bạn cần làm bây giờ là:

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

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

Ưu điểm:

1) Bạn chỉ cần một lớp proxy và nó sẽ làm việc với tất cả các đối tượng của bạn. 2) Bạn sẽ không thiếu tôn trọng các quy tắc trợ năng. 3) Bạn không cần thay đổi các đối tượng đã được sửa đổi.

Bất lợi: Bạn sẽ mất giao diện/hợp đồng sau khi gói đối tượng gốc. Nếu bạn sử dụng loại gợi ý với frequence có thể nó là một vấn đề.

16

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.

+0

Ngoài ra còn có một thư viện độc lập khác https://github.com/koriym/Ray.Aop được Bear.Sunday sử dụng. –

3

Có lẽ cách tốt nhất cho đến nay là để tạo ra phương pháp gọi của riêng bạn và quấn xung quanh bất cứ điều gì bạn cần trước và sau khi phương pháp:

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(); 

Và đây là kết quả:

 
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! 

Bạn thậm chí có thể đi xa hơn và tạo ra một danh sách trắng các phương pháp nhưng tôi để nó như thế này vì một ví dụ đơn giản hơn.

Bạn sẽ không còn bị buộc phải đặt các phương thức riêng tư và sử dụng chúng thông qua __call(). Tôi giả định rằng có thể có những tình huống mà bạn sẽ muốn gọi các phương thức mà không có trình bao bọc hoặc bạn muốn IDE của mình vẫn tự động hoàn tất các phương thức mà hầu hết sẽ không xảy ra nếu bạn khai báo các phương thức là riêng tư.