외부 클래스가있는 remove_action 또는 remove_filter?


59

플러그인이 클래스 내에서 메소드를 캡슐화 한 후 해당 메소드 중 하나에 대해 필터 또는 조치를 등록한 경우 해당 클래스의 인스턴스에 더 이상 액세스 할 수없는 경우 조치 또는 필터를 어떻게 제거합니까?

예를 들어, 이것을 수행하는 플러그인이 있다고 가정하십시오.

class MyClass {
    function __construct() {
       add_action( "plugins_loaded", array( $this, 'my_action' ) );
    }

    function my_action() {
       // do stuff...
    }
}

new MyClass();

이제 인스턴스에 액세스 할 수있는 방법이 없다는 점에 유의하면 클래스 등록을 어떻게 취소합니까? 이것은 : remove_action( "plugins_loaded", array( MyClass, 'my_action' ) );올바른 접근법이 아닌 것 같습니다-적어도 내 경우에는 효과가 없었습니다.


N / P. 아래는 당신을 위해 작동합니까?
카이저

답변:


16

여기서 가장 좋은 방법은 정적 클래스를 사용하는 것입니다. 다음 코드는 설명이 필요합니다.

class MyClass {
    function __construct() {
        add_action( 'wp_footer', array( $this, 'my_action' ) );
    }
    function my_action() {
        print '<h1>' . __class__ . ' - ' . __function__ . '</h1>';
    }
}
new MyClass();


class MyStaticClass {
    public static function init() {
        add_action( 'wp_footer', array( __class__, 'my_action' ) );
    }
    public static function my_action() {
        print '<h1>' . __class__ . ' - ' . __function__ . '</h1>';
    }
}
MyStaticClass::init();

function my_wp_footer() {
    print '<h1>my_wp_footer()</h1>';
}
add_action( 'wp_footer', 'my_wp_footer' );

function mfields_test_remove_actions() {
    remove_action( 'wp_footer', 'my_wp_footer' );
    remove_action( 'wp_footer', array( 'MyClass', 'my_action' ), 10 );
    remove_action( 'wp_footer', array( 'MyStaticClass', 'my_action' ), 10 );
}
add_action( 'wp_head', 'mfields_test_remove_actions' );

플러그인에서이 코드를 실행하면 StaticClass의 메소드와 함수가 wp_footer에서 제거됩니다.


7
포인트를 취했지만 모든 클래스를 정적으로 변환 할 수있는 것은 아닙니다.
Geert

오토의 답변이 가장 좋은 방법이지만 질문에 가장 직접 답변하기 때문에이 답변을 수락했습니다. 나는 당신이 명시 적으로 정적을 선언 할 필요가 없다고 생각합니다. 정적 배열 ( 'MyClass', 'member_function')처럼 함수를 처리 할 수 ​​있고 종종 'static'키워드없이 작동하는 것은 내 경험 (잘못되었을 수도 있지만)입니다.
Tom Auger

@TomAuger 아니요 정적 클래스로 추가 된 경우에만 remove_action함수 를 사용할 수 있습니다 . 그렇지 않으면 작동하지 않습니다. 그래서 정적 클래스가 아닌 경우 처리 할 수있는 자체 함수를 작성해야했습니다. 이 답변은 귀하의 질문이 귀하의 코드와 관련이있는 경우에만 최고 일 것입니다. 그렇지 않으면 다른 사람의 코드베이스에서 다른 필터 / 액션을 제거하려고 시도 할 것이며 정적으로 변경할 수 없습니다.
sMyles

78

플러그인이를 만들 때마다 new MyClass();고유 한 이름을 가진 변수에 할당해야합니다. 그렇게하면 클래스의 인스턴스에 액세스 할 수 있습니다.

그래서 그가하고 있다면 $myclass = new MyClass();, 당신은 이것을 할 수 있습니다 :

global $myclass;
remove_action( 'wp_footer', array( $myclass, 'my_action' ) );

플러그인이 전역 네임 스페이스에 포함되어 있기 때문에 플러그인 본체의 암시 적 변수 선언은 전역 변수입니다.

플러그인이 새로운 클래스의 식별자를 어딘가에 저장하지 않으면 기술적으로 버그입니다. 객체 지향 프로그래밍의 일반적인 원칙 중 하나는 어떤 변수가 참조하지 않는 객체는 정리 또는 제거 될 수 있다는 것입니다.

PHP는 절반 정도의 OOP 구현이기 때문에 PHP는 특히 Java와 같이 이것을하지 않습니다. 인스턴스 변수는 고유 한 객체 이름을 가진 문자열 일뿐입니다. 변수 함수 이름 상호 작용이 ->연산자 와 작동하는 방식 때문에 작동합니다 . 따라서 그렇게하는 new class()것은 실제로 어리석게도 완벽하게 작동 할 수 있습니다. :)

따라서 결론은 절대하지 마십시오 new class();. 수행 $var = new class();과 다른 비트를 참조하기위한 몇 가지 방법이 $ var에 액세스 할 수 있도록.

편집 : 몇 년 후

내가 플러그인을 많이 본 것은 "Singleton"패턴과 비슷한 것을 사용하는 것입니다. 클래스의 단일 인스턴스를 얻기 위해 getInstance () 메소드를 작성합니다. 이것은 아마도 내가 본 최고의 솔루션 일 것입니다. 플러그인 예 :

class ExamplePlugin
{
    protected static $instance = NULL;

    public static function getInstance() {
        NULL === self::$instance and self::$instance = new self;
        return self::$instance;
    }
}

getInstance ()를 처음 호출하면 클래스를 인스턴스화하고 포인터를 저장합니다. 이를 사용하여 작업에 연결할 수 있습니다.

이것의 한 가지 문제는 생성자를 사용할 경우 생성자 안에서 getInstance ()를 사용할 수 없다는 것입니다. $ instance를 설정하기 전에 new가 생성자를 호출하므로 생성자에서 getInstance ()를 호출하면 무한 루프가 발생하고 모든 것이 중단됩니다.

한 가지 해결 방법은 생성자를 사용하지 않거나 최소한 getInstance ()를 사용하지 말고 클래스에 "init"함수를 사용하여 작업 등을 설정하는 것입니다. 이처럼 :

public static function init() {
    add_action( 'wp_footer', array( ExamplePlugin::getInstance(), 'my_action' ) );
}

다음과 같이 파일 끝에서 클래스가 모두 정의 된 후 플러그인 인스턴스화는 다음과 같이 간단 해집니다.

ExamplePlugin::init();

Init은 액션을 추가하기 시작하고, getInstance ()를 호출하여 클래스를 인스턴스화하고 그 중 하나만 존재하는지 확인합니다. init 함수가 없으면 클래스를 처음 인스턴스화하기 위해 대신 수행합니다.

ExamplePlugin::getInstance();

원래 질문을 해결하기 위해 외부에서 (다른 플러그인에서) 액션 후크를 제거하면 다음과 같이 수행 할 수 있습니다.

remove_action( 'wp_footer', array( ExamplePlugin::getInstance(), 'my_action' ) );

plugins_loaded이것을 액션 훅에 연결된 것에 넣으면 원래 플러그인에 의해 훅되는 액션을 취소합니다.


3
Tru 데이터 +1 이것은 분명히 모범 사례입니다. 우리는 모두 플러그인 코드를 그런 식으로 작성하려고 노력해야합니다.
Tom Auger

3
+1이 지침은 싱글 톤 패턴 클래스에서 필터를 제거하는 데 실제로 도움이되었습니다.
Devin Walker

하나는,하지만 난 당신이 일반적으로 후크해야한다고 생각 wp_loaded하지, plugins_loaded너무 일찍 호출 할 수있다.
EML

4
아니요, plugins_loaded올바른 장소입니다. wp_loaded동작은 이후에 발생하는 init액션, 그래서 당신의 플러그인에 어떤 작업을 수행하는 경우 init(대부분이 할), 당신은 플러그인을 초기화하고 그 전에 그것을 설정하고 싶습니다. plugins_loaded후크는 시공 단계에 적합한 장소이다.
Otto

13

"익명"클래스로 필터 / 액션을 제거 할 수있는 2 개의 작은 PHP 함수 : https://github.com/herewithme/wp-filters-extras/


매우 멋진 기능. 여기에 게시 해 주셔서 감사합니다!
Tom Auger

아래 내 게시물에 언급 된 다른 사람들처럼 WordPress 4.7에서 중단됩니다 (레포가 업데이트되지 않지만 2 년이
지나지

1
wp-filters-extras repo가 ​​실제로 v4.7 및 WP_Hook 클래스에 대해 업데이트되었다는 점에 유의하십시오.
Dave Romsey

13

다음은 클래스 객체에 액세스 할 수 없을 때 필터를 제거하기 위해 작성한 문서화 기능입니다 (4.7+를 포함한 WordPress 1.2 이상에서 작동).

https://gist.github.com/tripflex/c6518efc1753cf2392559866b4bd1a53

/**
 * Remove Class Filter Without Access to Class Object
 *
 * In order to use the core WordPress remove_filter() on a filter added with the callback
 * to a class, you either have to have access to that class object, or it has to be a call
 * to a static method.  This method allows you to remove filters with a callback to a class
 * you don't have access to.
 *
 * Works with WordPress 1.2+ (4.7+ support added 9-19-2016)
 * Updated 2-27-2017 to use internal WordPress removal for 4.7+ (to prevent PHP warnings output)
 *
 * @param string $tag         Filter to remove
 * @param string $class_name  Class name for the filter's callback
 * @param string $method_name Method name for the filter's callback
 * @param int    $priority    Priority of the filter (default 10)
 *
 * @return bool Whether the function is removed.
 */
function remove_class_filter( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    global $wp_filter;

    // Check that filter actually exists first
    if ( ! isset( $wp_filter[ $tag ] ) ) return FALSE;

    /**
     * If filter config is an object, means we're using WordPress 4.7+ and the config is no longer
     * a simple array, rather it is an object that implements the ArrayAccess interface.
     *
     * To be backwards compatible, we set $callbacks equal to the correct array as a reference (so $wp_filter is updated)
     *
     * @see https://make.wordpress.org/core/2016/09/08/wp_hook-next-generation-actions-and-filters/
     */
    if ( is_object( $wp_filter[ $tag ] ) && isset( $wp_filter[ $tag ]->callbacks ) ) {
        // Create $fob object from filter tag, to use below
        $fob = $wp_filter[ $tag ];
        $callbacks = &$wp_filter[ $tag ]->callbacks;
    } else {
        $callbacks = &$wp_filter[ $tag ];
    }

    // Exit if there aren't any callbacks for specified priority
    if ( ! isset( $callbacks[ $priority ] ) || empty( $callbacks[ $priority ] ) ) return FALSE;

    // Loop through each filter for the specified priority, looking for our class & method
    foreach( (array) $callbacks[ $priority ] as $filter_id => $filter ) {

        // Filter should always be an array - array( $this, 'method' ), if not goto next
        if ( ! isset( $filter[ 'function' ] ) || ! is_array( $filter[ 'function' ] ) ) continue;

        // If first value in array is not an object, it can't be a class
        if ( ! is_object( $filter[ 'function' ][ 0 ] ) ) continue;

        // Method doesn't match the one we're looking for, goto next
        if ( $filter[ 'function' ][ 1 ] !== $method_name ) continue;

        // Method matched, now let's check the Class
        if ( get_class( $filter[ 'function' ][ 0 ] ) === $class_name ) {

            // WordPress 4.7+ use core remove_filter() since we found the class object
            if( isset( $fob ) ){
                // Handles removing filter, reseting callback priority keys mid-iteration, etc.
                $fob->remove_filter( $tag, $filter['function'], $priority );

            } else {
                // Use legacy removal process (pre 4.7)
                unset( $callbacks[ $priority ][ $filter_id ] );
                // and if it was the only filter in that priority, unset that priority
                if ( empty( $callbacks[ $priority ] ) ) {
                    unset( $callbacks[ $priority ] );
                }
                // and if the only filter for that tag, set the tag to an empty array
                if ( empty( $callbacks ) ) {
                    $callbacks = array();
                }
                // Remove this filter from merged_filters, which specifies if filters have been sorted
                unset( $GLOBALS['merged_filters'][ $tag ] );
            }

            return TRUE;
        }
    }

    return FALSE;
}

/**
 * Remove Class Action Without Access to Class Object
 *
 * In order to use the core WordPress remove_action() on an action added with the callback
 * to a class, you either have to have access to that class object, or it has to be a call
 * to a static method.  This method allows you to remove actions with a callback to a class
 * you don't have access to.
 *
 * Works with WordPress 1.2+ (4.7+ support added 9-19-2016)
 *
 * @param string $tag         Action to remove
 * @param string $class_name  Class name for the action's callback
 * @param string $method_name Method name for the action's callback
 * @param int    $priority    Priority of the action (default 10)
 *
 * @return bool               Whether the function is removed.
 */
function remove_class_action( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
    remove_class_filter( $tag, $class_name, $method_name, $priority );
}

2
질문-4.7에서 이것을 테스트 했습니까? 최신 필터에 콜백이 등록되는 방식이 일부 변경되었습니다. 나는 당신의 코드를 깊이 살펴 보지 않았지만, 당신이 확인하고 싶을 수도있는 것입니다 : make.wordpress.org/core/2016/09/08/…
Tom Auger


아! 아니요,하지만 감사합니다.이 부분을 살펴보고이를 업데이트하여 호환되도록 (필요한 경우) 업데이트하십시오
sMyles

1
@TomAuger 감사합니다! 나는 기능을 업데이트하고 WordPress 4.7 이상에서 작동하도록 테스트했습니다 (이전 버전과의 호환성은 여전히 ​​유지됨)
sMyles

1
핵심 내부 제거 방법 (중반 반복 처리 및 PHP 경고 방지)을 사용하도록 방금 업데이트
sMyles

2

위의 솔루션은 구식 인 것처럼 보였고 직접 작성해야했습니다 ...

function remove_class_action ($action,$class,$method) {
    global $wp_filter ;
    if (isset($wp_filter[$action])) {
        $len = strlen($method) ;
        foreach ($wp_filter[$action] as $pri => $actions) {
            foreach ($actions as $name => $def) {
                if (substr($name,-$len) == $method) {
                    if (is_array($def['function'])) {
                        if (get_class($def['function'][0]) == $class) {
                            if (is_object($wp_filter[$action]) && isset($wp_filter[$action]->callbacks)) {
                                unset($wp_filter[$action]->callbacks[$pri][$name]) ;
                            } else {
                                unset($wp_filter[$action][$pri][$name]) ;
                            }
                        }
                    }
                }
            }
        }
    }
}

0

이 기능은 @Digerkam 답변을 기반으로합니다. $def['function'][0]문자열이 비교 인 경우 추가 하고 마침내 나를 위해 일했습니다.

또한 사용 $wp_filter[$tag]->remove_filter()하면보다 안정적이어야합니다.

function remove_class_action($tag, $class = '', $method, $priority = null) : bool {
    global $wp_filter;
    if (isset($wp_filter[$tag])) {
        $len = strlen($method);

        foreach($wp_filter[$tag] as $_priority => $actions) {

            if ($actions) {
                foreach($actions as $function_key => $data) {

                    if ($data) {
                        if (substr($function_key, -$len) == $method) {

                            if ($class !== '') {
                                $_class = '';
                                if (is_string($data['function'][0])) {
                                    $_class = $data['function'][0];
                                }
                                elseif (is_object($data['function'][0])) {
                                    $_class = get_class($data['function'][0]);
                                }
                                else {
                                    return false;
                                }

                                if ($_class !== '' && $_class == $class) {
                                    if (is_numeric($priority)) {
                                        if ($_priority == $priority) {
                                            //if (isset( $wp_filter->callbacks[$_priority][$function_key])) {}
                                            return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                        }
                                    }
                                    else {
                                        return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                    }
                                }
                            }
                            else {
                                if (is_numeric($priority)) {
                                    if ($_priority == $priority) {
                                        return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                    }
                                }
                                else {
                                    return $wp_filter[$tag]->remove_filter($tag, $function_key, $_priority);
                                }
                            }

                        }
                    }
                }
            }
        }

    }

    return false;
}

사용법 예 :

정확히 일치

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', 'MyClass', 'my_action', 0);
});

모든 우선 순위

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', 'MyClass', 'my_action');
});

모든 클래스와 우선 순위

add_action('plugins_loaded', function() {
    remove_class_action('plugins_loaded', '', 'my_action');
});

0

이것은 일반적인 대답은 아니지만 Avada 테마 및 WooCommerce 에만 해당되는 것으로 다른 사람들이 유용하다고 생각합니다.

function remove_woo_commerce_hooks() {
    global $avada_woocommerce;
    remove_action( 'woocommerce_single_product_summary', array( $avada_woocommerce, 'add_product_border' ), 19 );
}
add_action( 'after_setup_theme', 'remove_woo_commerce_hooks' );
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.