2010-09-15 1 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 

Теперь мой вопрос,Как автоматически вызов функции в PHP для любого другого вызова функции

Как я могу вызвать другую функцию перед любой вызываемой функции? В описанном выше случае, как я могу автоматический вызов функции «test1» для каждого вызова другой функции, , так что я могу получить результат, как,

test1 
test2 
test1 
test3 

В настоящее время я получаю выход как

test2 
test3 

Я не могу назвать функцию «test1» в определением каждой функции, поскольку там может быть множество функций . Мне нужно, чтобы автоматически вызывал функцию перед вызовом любой функции класса.

Любой альтернативный способ также будет выполнен.

ответ

50

Лучше всего метод магии __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 
*/ 

Пожалуйста, обратите внимание, что test2 и test3 не видны в контексте, где они называются из-за protected и private. Если методы общедоступны, приведенный выше пример не будет выполнен.

test1 не обязательно должно быть объявлено private.

ideone.com example can be found here

Обновлено: Добавить ссылку на ideone, добавить пример с возвращаемым значением.

+0

На самом деле, а) у вас могут быть методы, также как и защищенные, просто не публичные, и б) вам не нужен _ – ktolis

+0

. Вы правы, защита также будет работать, я удалил '_' в примере и сделал 'test3'' protected'. –

+0

Должно ли '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... 

} 

?> 

Попробуйте добавить этот метод переопределение в классе ...

+0

спасибо за предложение, но я не могу назвать функцию «test1» в каждом определении функции, так как может быть много функций. Мне нужен способ автоматического вызова функции перед вызовом любой функции класса. – Nik

+0

+1, поскольку это лучший способ сделать это, не нарушая принципов ООП, указав публичные методы как частные, хотя они все еще доступны как общедоступные. – poke

+0

@Yogesh, см. Обновленный ответ, пожалуйста ... – Otar

0

Единственный способ сделать это с помощью волшебной __call. Вы должны сделать все методов частных, поэтому они недоступны извне. Затем определите метод __call для обработки вызовов метода. В __call вы можете выполнить любую функцию, которую вы хотите , до, вызывая функцию, которая была специально вызвана.

-2

Позволяет иметь идти на этот:

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

То, что я хотел бы сделать

2

Если вы действительно, действительно храбрый, вы можете сделать это с расширением runkit. (http://www.php.net/manual/en/book.runkit.php). Вы можете играть с runkit_method_redefine (вам может понадобиться Reflection также для извлечения определения метода), или, может быть, комбинация runkit_method_rename (старая функция)/runkit_method_add (новая функция, которая обертывает вызовы вашей функции test1 и старую функцию)

5

Возможно, это немного бит устарел, но вот приходят мои 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 
    } 
} 

Теперь просто тест класса:

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) Вам не нужно менять проксированные объекты.

Недостаток: вы потеряете кондуктор/контракт после упаковки исходного объекта. Если вы используете тип hinting с частотой, возможно, это проблема.

16

Все предыдущие попытки в основном испорчен из-за 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! 

Это относится ко всем решениям, которые предлагают внедряющих «прокси».

Решения, предлагающие явное использование других методов, которые затем называют ваш внутренний API, являются ошибочными, поскольку они заставляют вас менять свой общедоступный API для изменения внутреннего поведения и снижают безопасность типов.

Решение, предлагаемое Kristoffer не учитывает public методов, что также является проблемой, так как вы не можете переписать свой API, чтобы сделать все это private или protected.

Вот решение, которое решает эту проблему частично:

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! 

Это тип-безопасное, чистое решение, но оно вовлекает много кодирования, поэтому, пожалуйста, возьмите его только в качестве примера.

Написание этого шаблонного кода НЕ весело, поэтому вы можете использовать разные подходы.

Чтобы дать вам представление о том, как сложна эта категория проблем, я могу вам сказать, что я написал an entire library решить их, а некоторые умнее, мудрее, пожилые люди даже пошли и изобрели совершенно иную парадигму, называется "Aspect Oriented Programming" (AOP) ,

Поэтому я предлагаю вам взглянуть на эти 3 решения, которые я думаю, что может быть в состоянии решить вашу проблему в более чистым способом:

  • Использование ProxyManager «s„access interceptor“, который является в основном прокси тип, который позволяет запускать закрытие при вызове других методов (example). Вот пример того, как прокси все вызовы к общественному API $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) 
          ) 
         ); 
    } 
    

    затем просто использовать build_wrapper, как вам нравится.

  • Использовать GO-AOP-PHP, который является реальной библиотекой AOP, полностью написанной на PHP, но будет применять эту логику для ВСЕХ экземпляров классов, для которых вы определяете точечные сокращения. Это может быть или не быть тем, что вы хотите, и если ваш $callOnMethod должен применяться только для определенных экземпляров, тогда AOP не то, что вы ищете.

  • Используйте PHP AOP Extension, что я не верю, чтобы быть хорошим решением, в основном потому, что GO-AOP-PHP решает эту проблему в более элегантном/отладочном образом, и потому, что расширения в PHP по своей сути беспорядок (который должен быть приписан к внутренним PHP, а не к разработчикам расширений). Кроме того, используя расширение, вы делаете свое приложение как можно более портативным (попробуйте убедить sysadmin, чтобы установить скомпилированную версию PHP, если вы смеете), и вы не можете использовать свое приложение в крутых новых двигателях, таких как как HHVM.

+0

Существует еще одна автономная библиотека https://github.com/koriym/Ray.Aop, которая используется Bear.Sunday. –

3

Возможно, лучший способ до сих пор, чтобы создать свой собственный метод вызывающему и обернуть вокруг того, что вам нужно до и после метода:

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: 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! 

Вы даже можете пойти дальше и создать белый список методов, но я оставляю его таким образом для более простого примера.

Вы больше не будете вынуждены делать частные методы и использовать их через __call(). Я предполагаю, что могут возникнуть ситуации, когда вы захотите вызвать методы без оболочки или вы хотите, чтобы ваша среда IDE все еще автозаполняла методы, которые, скорее всего, не произойдут, если вы объявите методы как частные.

Смежные вопросы