Подтвердить что ты не робот

Как функция автоматического вызова в 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" в каждое определение функции, поскольку быть много функций. Мне нужен способ автоматический вызов функции перед вызовом любая функция класса.

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

4b9b3361

Ответ 1

Лучше всего использовать магический метод __ 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 можно найти здесь

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

Ответ 2

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

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

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

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

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

  • Используйте ProxyManager "перехватчик доступа", который в основном является прокси-типом, который позволяет запускать закрытие при вызове других методов (example). Ниже приведен пример того, как проксировать ВСЕ вызовы в $object 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)
                )
            ); 
    }
    

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

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

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

Ответ 3

Может быть, это немного устарело, но вот приходят мои 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) Вам не нужно изменять проксированные объекты.

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

Ответ 4

<?php

class test
{

    public function __call($name, $arguments)
    {
        $this->test1(); // Call from here
        return call_user_func_array(array($this, $name), $arguments);
    }

    // methods here...

}

?>

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

Ответ 5

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

Ответ 6

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

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 все еще автозаполняла методы, которые, скорее всего, не произойдут, если вы объявите методы как частные.

Ответ 7

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

Ответ 8

Позволяет перейти к следующему:

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

Вот что я буду делать