IT Share you

다른 모든 함수 호출에 대해 PHP에서 함수를 자동 호출하는 방법

shareyou 2021. 1. 7. 19:54
반응형

다른 모든 함수 호출에 대해 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'함수를 호출 할 수 없습니다. 클래스의 함수를 호출하기 전에 함수를 자동 호출하는 방법이 필요합니다.

다른 방법도 있습니다.


가장 좋은 방법은 마법 방법 __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
*/

제발 통지 그 test2test3그들로 인해 호출되는 컨텍스트에 표시되지 않습니다 protectedprivate. 메서드가 공용이면 위의 예제는 실패합니다.

test1선언 할 필요가 없습니다 private.

ideone.com 예제는 여기에서 찾을 수 있습니다.

업데이트 됨 : ideone에 링크 추가, 반환 값이있는 예제 추가.


이전의 모든 시도는 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!

이것은 형식이 안전하고 깨끗한 솔루션이지만 많은 코딩이 필요하므로 예제로만 사용하십시오.

이 상용구 코드를 작성하는 것은 재미 있지 않으므로 다른 접근 방식을 사용하는 것이 좋습니다.

이 범주의 문제가 얼마나 복잡한 지에 대한 아이디어를 제공하기 위해 저는 문제 를 해결하기 위해 전체 라이브러리작성 했으며 더 똑똑하고 현명한 노인들이 가서 "Aspect Oriented" 라는 완전히 다른 패러다임을 발명 했다고 말할 수 있습니다. 프로그래밍 "(AOP) .

따라서 훨씬 더 깨끗한 방법으로 문제를 해결할 수 있다고 생각하는 다음 세 가지 솔루션을 살펴볼 것을 제안합니다.

  • 사용 ProxyManager 의 " 액세스 인터셉터 다른 방법 (호출 될 때 당신은 폐쇄를 실행할 수있는 프록시 유형 기본적으로" ). 다음은 $object의 공용 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원하는대로 사용하십시오 .

  • 완전히 PHP로 작성된 실제 AOP 라이브러리 인 GO-AOP-PHP를 사용하십시오 . 그러나 이러한 로직은 포인트 컷을 정의하는 모든 클래스 인스턴스에 적용됩니다. 이것은 당신이 원하는 것일 수도 있고 아닐 수도 있으며, $callOnMethod특정 인스턴스에만 적용되어야 한다면 AOP는 당신이 찾고있는 것이 아닙니다.

  • 좋은 해결책이라고 생각하지 않는 PHP AOP Extension을 사용하십시오. 주로 GO-AOP-PHP 가이 문제를보다 우아하고 디버그 가능한 방식으로 해결하고 PHP의 확장이 본질적으로 엉망이기 때문입니다 (즉, 확장 개발자가 아닌 PHP 내부에 기인 함). 또한 확장을 사용하면 애플리케이션을 가능한 한 이식 할 수 없게 만들고 (감히 시스템 관리자에게 컴파일 된 PHP 버전을 설치하도록 설득) 다음과 같은 멋진 새 엔진에서 앱을 사용할 수 없습니다. HHVM.


조금 구식일지도 모르지만 여기에 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) 근접 개체를 변경할 필요가 없습니다.

단점 : 원래 개체를 래핑 한 후에는 인퍼 페이스 / 계약이 손실됩니다. 유형 힌팅을 빈도와 함께 사용하면 문제가 될 수 있습니다.


지금까지 가장 좋은 방법은 자신 만의 메서드 호출자를 만들고 메서드 전후에 필요한 모든 것을 감싸는 것입니다.

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 :이 줄을 실행 한 다음 MyClass-> functionAA ()를 호출합니다.
functionAA : 저는
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!

You can even go further and create a whitelist of methods but I leave it like this for the sake of a more simple example.

You will no longer be forced to make the methods private and use them via __call(). I'm assuming that there might be situations where you will want to call the methods without the wrapper or you would like your IDE to still autocomplete the methods which will most probably not happen if you declare the methods as private.


<?php

class test
{

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

    // methods here...

}

?>

Try adding this method overriding in the class...


If you are really, really brave, you can make it with runkit extension. (http://www.php.net/manual/en/book.runkit.php). You can play with runkit_method_redefine (you can need Reflection also to retrieve method definition) or maybe combination runkit_method_rename (old function) / runkit_method_add (new function which wraps calls to your test1 function and an old function )


The only way to do this is using the magic __call. You need to make all methods private so they are not accessable from the outside. Then define the __call method to handle the method calls. In __call you then can execute whatever function you want before calling the function that was intentionally called.


Lets have a go at this one :

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 what I would do

ReferenceURL : https://stackoverflow.com/questions/3716649/how-to-auto-call-function-in-php-for-every-other-function-call

반응형